Diary?

2009-04-25
Sat

(20:07)

檜山さんの「有限集合と写像の圏も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 はまた後で。

(21:54)

PMapFO と RelMapFO も書いた。コードを見せる前に、超大雑把に PMapFO と RelMapFO の説明。

PMapFO
域の要素の中に使わない、つまり対応が未定義のものがあってもいい。
RelMapFO
域の要素と余域の要素が 1:n 対応していてもよい。あと未定義なマッピングがあっても良かったはず。

それぞれの凡例はこんな感じ。

それじゃあ 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 へのマッピングを手で書いて追いながら検証してたからな。単純に射を作るだけなら難しい話じゃないけど、結合が絡むと途端に怪しくなった。まあ、手で圏の実例を触りながらやった方が勉強になるっちゃそうか。

(23:36)

ロックス・クエストをクリアした。最終的な感想は前にちょろと書いたの時と同じく、非常に満足。それでこのゲームは一体何が面白いのかだけど、そのキーワードの一つは恐らく「緊張感」だろうな。建築パートも戦闘パートも時間制限が大変な緊張感を生み出していて、そしてその緊張感の性質はまるで違う。

建築パート
制限時間以内に要塞を築く必要がある。「あと○○秒で終わっちまう!」という緊張感。
戦闘パート(防衛)
制限時間が過ぎるまで敵の攻撃を防がなければならない。「どうにかあと○○秒耐えないと!」という緊張感。
戦闘パート(拠点制圧・ボス戦)
制限時間以内に拠点を制圧するかボスを倒すかしなければならない。上記二つの入り交じった緊張感。

と逆ベクトルの緊張感が混在しているので、非常にメリハリのあるプレイ感覚だ。

面白さのもう一つは「トレードオフ」。要塞を築く上では大まかに分けると以下の四種類のパーツを組み合わせて行く。

普通の壁。安価に作れるが、それだけでは本当に壁でしかない。
砲台
敵に直接ダメージを与える。壁と組み合わせると強度が上がる、というか組み合わせないとリソースが足りなくなる。
ヘルパー
要塞を修復したり、砲台の性能を上げたりする。これだけではクソの役にも立たないが、これを上手く活用できないと苦戦は必至。
トラップ
地雷の類。極めて安価で効果が高いが、一戦限りの効力しかないので使いすぎるとリソースが足りなくなる。

それぞれに確固たる役割があり、出現する敵の編成によっては神のような強さになったりゴミになったりするパーツもある。例えば地下を潜って奇襲をかけてくるユニットには対地下のトラップが極めて有効だが、そいつが湧いてこないことには設置するだけ無駄になる。また、地下に潜られる前に倒すこともできたりする。同じ事がいえるパーツは対空砲など他にもあり、敵の編成によっては全部の特殊用途のパーツを配置することが無理だったりする。そのため、自分の戦闘技術や敵の編成から取捨選択しなければならず、これがまた非常に悩ましくも面白い。

ちなみに俺のプレイスタイルは要塞の周りを氷結トラップで固め、敵が通行する通りに酸性トラップを仕掛けまくるというものだった。ラスボスに至っては酸性トラップと地雷を通り道にギッチリ敷き詰め、こちらの拠点に到達するころには体力を90%近くけずっていたりした。

というわけで、ロックス・クエストは俺的には名作に決定。カートゥーン調のバタ臭いデザインで敬遠するにはもったいないゲームだぞ。

Creative Commons
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。