檜山さんの「有限集合と写像の圏もJavaScriptで書いてみた、遊んでみてね」に倣って、 MapFO を Python で書いてみた。例外処理とか投げやりだけど、そこは気にしない方向で。
class FO(list):
def __init__(self, num):
list.__init__(self)
ls = range(1, num + 1)
self.extend(ls)
class MapFO(object):
def __init__(self, a, b=None, map=None):
if not (isinstance(a, FO) and isinstance(b, FO) and isinstance(map, list)):
raise Exception()
if len(a) != len(map):
raise Exception()
if max(b) < max(map):
raise Exception()
self.dom = a
self.cod = b
self.map = map
def __add__(self, another):
if self.cod == another.dom:
newmap = []
for i in self.map:
newmap.append(another.map[i - 1])
return MapFO(self.dom, another.cod, newmap)
else:
raise Exception()
def __eq__(self, another):
if self.dom != another.dom: return False
if self.cod != another.cod: return False
if self.map != another.map: return False
return True
def __str__(self):
s = '[%d] --> [%d]\n' % (self.dom[-1], self.cod[-1])
for i, j in zip(self.dom, self.map):
s += ' %d | --> %d\n' % (i, j)
s += 'dom=%d cod=%d value=[%d]\n' % (self.dom[-1], self.cod[-1], len(self.map))
return s
@classmethod
def id(cls, fo):
return MapFO(fo, fo, list(fo))
使い方及び実行結果はこんな感じになる。まずは普通に射の生成。
>>> f = MapFO(FO(2), FO(3), [2, 1])
>>> print f
[2] --> [3]
1 | --> 2
2 | --> 1
dom=2 cod=3 value=[2]
>>> g = MapFO(FO(3), FO(3), [2, 3, 3])
>>> print g
[3] --> [3]
1 | --> 2
2 | --> 3
3 | --> 3
dom=3 cod=3 value=[3]
>>> h = MapFO(FO(3), FO(4), [4, 3, 1])
>>> print h
[3] --> [4]
1 | --> 4
2 | --> 3
3 | --> 1
dom=3 cod=4 value=[3]
続いて射の合成。
>>> print f + g
[2] --> [3]
1 | --> 3
2 | --> 2
dom=2 cod=3 value=[2]
>>> print f + g + h
[2] --> [4]
1 | --> 1
2 | --> 3
dom=2 cod=4 value=[2]
それでもって俺がしりとりの圏で醜態を晒す原因となった恒等射。
>>> i = MapFO.id(FO(2))
>>> print i
[2] --> [2]
1 | --> 1
2 | --> 2
dom=2 cod=2 value=[2]
>>> print i + f
[2] --> [3]
1 | --> 2
2 | --> 1
dom=2 cod=3 value=[2]
>>> print i + f == f
True
PMapFO と RelMapFO はまた後で。
PMapFO と RelMapFO も書いた。コードを見せる前に、超大雑把に PMapFO と RelMapFO の説明。
それぞれの凡例はこんな感じ。
それじゃあ PMapFO の実装から。
class PMapFO(object):
def __init__(self, a, b, map):
if not (isinstance(a, FO) and isinstance(b, FO) and isinstance(map, dict)):
raise Exception()
if max(b) < max(map.values()):
raise Exception()
self.dom = a
self.cod = b
self.map = map
def __add__(self, another):
if self.cod == another.dom:
newmap = {}
for k in self.map.keys():
v = self.map[k]
if v in another.map:
newmap[k] = another.map[v]
return PMapFO(self.dom, another.cod, newmap)
else:
raise Exception()
def __eq__(self, another):
if self.dom != another.dom: return False
if self.cod != another.cod: return False
if self.map != another.map: return False
return True
def __str__(self):
s = '[%d] --> [%d]\n' % (self.dom[-1], self.cod[-1])
for i in self.dom:
if i in self.map:
s += ' %d | --> %d\n' % (i, self.map[i])
s += 'dom=%d cod=%d value=[%d]\n' % (self.dom[-1], self.cod[-1], len(self.map))
return s
@classmethod
def id(cls, fo):
return PMapFO(fo, fo, dict(zip(list(fo), list(fo))))
MapFO と違って辞書をマッピングに使うので使い方及び実行結果はこんな感じ。
>>> f = PMapFO(FO(3), FO(3), {1:1, 2:3})
>>> print f
[3] --> [3]
1 | --> 1
2 | --> 3
dom=3 cod=3 value=[2]
結合と恒等は MapFO と似たり寄ったりなんで、まあいいや。そこは飛ばして RelMapFO のコード。
class RelMapFO(object):
def __init__(self, a, b, map):
if not (isinstance(a, FO) and isinstance(b, FO) and isinstance(map, dict)):
raise Exception()
for vs in map.values():
if max(b) < max(vs):
raise Exception()
self.dom = a
self.cod = b
self.map = map
def __add__(self, another):
if self.cod == another.dom:
newmap = {}
for k in self.map.keys():
vs = self.map[k]
for v in vs:
if v in another.map:
newmap[k] = another.map[v]
return RelMapFO(self.dom, another.cod, newmap)
else:
raise Exception()
def __eq__(self, another):
if self.dom != another.dom: return False
if self.cod != another.cod: return False
if self.map != another.map: return False
return True
def __str__(self):
s = '[%d] --> [%d]\n' % (self.dom[-1], self.cod[-1])
for i, vs in self.map.items():
for v in vs:
s += ' %d | --> %d\n' % (i, v)
s += 'dom=%d cod=%d value=[%d]\n' % (self.dom[-1], self.cod[-1], len(self.map))
return s
@classmethod
def id(cls, fo):
map = {}
for i in fo:
map[i] = [i]
return RelMapFO(fo, fo, map)
「PMapFO もそうだったけど、コピペだらけじゃねーかこの野郎」とお怒りの方への説明:ぶっちゃけこれ全部一個もリファクタリングしてないっす。いやー、最初は MapFO からの継承構造を考えてたんだけど、処理をやたら細切れにしないと効果的じゃないのとか、例えば PMapFO が MapFO のサブクラスだったら MapFO の射と PMapFO の射って結合できんのかよという問題(OOP的にはできても良い。域と余域の辻褄はあってるから圏論的にもできると思うけど)とか、いろいろあってやめた。
それでこの RelMapFO だけど、今までは int から int への辞書だったのがリストへの辞書に変わっている。なので使い方はこんな感じ。
>>> f = RelMapFO(FO(3), FO(3), {1:[1, 2], 2:[3]})
>>> print f
[3] --> [3]
1 | --> 1
1 | --> 2
2 | --> 3
dom=3 cod=3 value=[2]
とりあえず一通り実装してみたけど、実装が正しいかどうか確認するのが手間だった。先に出したノートの写真みたく、 FO から FO へのマッピングを手で書いて追いながら検証してたからな。単純に射を作るだけなら難しい話じゃないけど、結合が絡むと途端に怪しくなった。まあ、手で圏の実例を触りながらやった方が勉強になるっちゃそうか。
ロックス・クエストをクリアした。最終的な感想は前にちょろと書いたの時と同じく、非常に満足。それでこのゲームは一体何が面白いのかだけど、そのキーワードの一つは恐らく「緊張感」だろうな。建築パートも戦闘パートも時間制限が大変な緊張感を生み出していて、そしてその緊張感の性質はまるで違う。
と逆ベクトルの緊張感が混在しているので、非常にメリハリのあるプレイ感覚だ。
面白さのもう一つは「トレードオフ」。要塞を築く上では大まかに分けると以下の四種類のパーツを組み合わせて行く。
それぞれに確固たる役割があり、出現する敵の編成によっては神のような強さになったりゴミになったりするパーツもある。例えば地下を潜って奇襲をかけてくるユニットには対地下のトラップが極めて有効だが、そいつが湧いてこないことには設置するだけ無駄になる。また、地下に潜られる前に倒すこともできたりする。同じ事がいえるパーツは対空砲など他にもあり、敵の編成によっては全部の特殊用途のパーツを配置することが無理だったりする。そのため、自分の戦闘技術や敵の編成から取捨選択しなければならず、これがまた非常に悩ましくも面白い。
ちなみに俺のプレイスタイルは要塞の周りを氷結トラップで固め、敵が通行する通りに酸性トラップを仕掛けまくるというものだった。ラスボスに至っては酸性トラップと地雷を通り道にギッチリ敷き詰め、こちらの拠点に到達するころには体力を90%近くけずっていたりした。
というわけで、ロックス・クエストは俺的には名作に決定。カートゥーン調のバタ臭いデザインで敬遠するにはもったいないゲームだぞ。
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。