@ゲー単走部

ローグライク雑記。変愚蛮怒、DCSSなど。

Iteratorパターン

イテレータイテレータってよく聞くけど、要するにこんな感じなんだろう。

class MyList:
    def __init__(self, mylist): # 引数mylistは通常のリストを想定
        self.mylist = mylist

    def getsize(self):
        return len(self.mylist)

    def __getitem__(self, index): # お遊び
        index2 = self.getsize() - 1 - index
        return self.mylist[index2]

    def myiter(self):
        return MyIterList(self)

    def __iter__(self):
        return MyIterList(self)

class MyIterList:
    def __init__(self, mylist): # 引数mylistはMyList型を想定
        self.mylist = mylist # 自フィールドがMyList型インスタンスを参照するようにする
        self.count = 0

    def hasNext(self):
        return self.count < self.mylist.getsize()

    def mynext(self):
        nextvalue = self.mylist[self.count]
        self.count += 1
        return nextvalue

    def __next__(self):
        if not self.hasNext():
            raise StopIteration()
        return self.mynext()


if __name__ == '__main__':
    test = MyList([1, 2, 3, 4, 5])
    for i in range(5):
        print(test[i])
    print('')

    print('myiter_test')
    itertest = test.myiter()
    while itertest.hasNext():
        print( itertest.mynext() )

    print('my__iter__test')
    for i in test:
        print(i)

Python3で引っかかるところ~print関数~

リスト内包表記の中にprint書けたりするの気持ち悪すぎでしょ。
純粋関数型言語でない以上、戻り値なしで副作用しかない関数も通常の関数として扱えるわけで、
その意味でprintを文としておくのは一貫性がないということで関数にしたんだろうけど。

>>>p = [print(n) for n in range(5)]
0
1
2
3
4
>>>p
[None, None, None, None, None]
>>>print(p)
[None, None, None, None, None]
>>>print( [print(n) for n in range(5)] )
0
1
2
3
4
[None, None, None, None, None]

p = [print(n) for n in range(5)] なのに、

  • print(p) と
  • print( [print(n) for n in range(5)] )

で挙動違うの気持ち悪すぎ。
何で?

<追記>
記事を投稿してから冷静に考え直してみれば、挙動違うの当たり前だった。
リスト内包表記の中にprint書けるのは気持ち悪いが・・・

  • p = [print(n) for n in range(5)]
    • print関数を5回適用して、[None, None, None, None, None]を変数pに代入する
  • print(p)
    • リストpを表示するだけ
  • print( [print(n) for n in range(5)] )
    • print関数を5回適用して、[None, None, None, None, None]を表示する


だから何もおかしいところはなかった。

p = [print(n) for n in range(5)] が、実質2つの副作用を持っているのに、1行にまとまっているのがわかりにくかった。
変数pにはただリストが代入されてるだけなんだよね、printの処理は代入されない。

Python3で引っかかるところ~リストとイテレータ~

mapやzipがリストではなくイテレータを返すようになったので、挙動がややこしい。

>>>a = [10, 20, 30]
>>>b = [1, 2, 3]
>>>z = zip(a, b)
>>>list(z)
[(10, 1), (20, 2), (30, 3)]
>>>list(z)
[]
>>>list(z)
[]

zip(a, b)がリストではなくイテレータオブジェクトであり、それにlist関数を適用している。
1回listを適用した時点でイテレータが終了するので、2回目以降listを適用しても戻り値は空。

>>>a = [10, 20, 30]
>>>b = [1, 2, 3]
>>>list(zip(a, b))
[(10, 1), (20, 2), (30, 3)]
>>>list(zip(a, b))
[(10, 1), (20, 2), (30, 3)]
>>>list(zip(a, b))
[(10, 1), (20, 2), (30, 3)]

この場合、毎回新しいイテレータオブジェクトzip(a, b)を生成しているので、戻り値は期待通り。

>>>a = [10, 20, 30]
>>>b = [1, 2, 3]
>>>z = list(zip(a, b))
>>>z
[(10, 1), (20, 2), (30, 3)]
>>>z
[(10, 1), (20, 2), (30, 3)]
>>>z
[(10, 1), (20, 2), (30, 3)]

この場合、イテレータzip(a, b)をlistに変換したものを変数zに保存しているので、zの値は変わらず、出力は期待通り。

>>>a = range(5)
>>>list(a)
[0, 1, 2, 3, 4]
>>>list(a)
[0, 1, 2, 3, 4]
>>>list(a)
[0, 1, 2, 3, 4]

はぁ?
Python3だとrange関数はリストではなくイテレータを返すはずなので、2回目以降のlist(a)は空になるんじゃないの?
何で?


<追記>
公式リファレンスを見たが、rangeは組み込み関数ではなく組み込み型、クラスだった模様。
それにイテレータオブジェクトというわけではないようだ。と思うしかない。xrangeと統合されてメモリ使用効率がよくなっただけ。
と考えないと↓の挙動との違いが説明できないし。

>>>a = iter([1, 3, 5])
>>>list(a)
[1, 3, 5]
>>>list(a)
[]
>>>list(a)
[]

Python 勉強 ~5桁の数字を漢数字に変換してみる~

つくってみた。5桁なのは、6桁以上だと規則性が変わってむずそうだから。
というか桁数なんて際限がないしとりあえず5桁で。

# 1-99999 の5桁の数字を漢数字に直す

def char_number(number):
    if not 1 <= number <= 99999:
        return "Error"

    answer = ""

    dict_number = { 1:'一', 2:'二', 3:'三', 4:'四', 5:'五', 6:'六', 7:'七', 8:'八', 9:'九', 0:'' }
    base_stlist = ['万', '千', '百', '十', '一']

    number_list = [int(n) for n in str(number)] # 各桁の数字をリストに
    stlist = [dict_number[i] for i in number_list] # 各桁の数字を漢数字に

    z = zip(stlist, base_stlist)
    for index, one_tuple in enumerate(z):
        st1, st2 = one_tuple

        if st1 == '': # 0の桁は飛ばす
            continue

        if index == 0:
            answer += st1 + st2
        elif 1 <= index <= 3:
            if st1 == '一': # 一百、一十とは言わない 一千は言うこともあるが、言わなくてもよい
                answer += st2
            else:
                answer += st1 + st2
        else: # 一の位
            answer += st1

    return answer

単方向連結リストの実装

あー疲れたー。
プログラミング・アルゴリズムの勉強がてら、連結リストを試しに実装してみたが、色々引っかかって数時間があっという間に飛んでいってしまった。
だがまあ、こうやって苦労してこそ理解も深まるというもの。決して無駄ではなかったと思いたい。

  • 言語選択の理由は特にないが、やっぱりスクリプト言語は余計な記述をしなくて済むので書くのも読むのも楽
    • とはいっても、型の情報くらいは書きたい気もする。動的型付け言語だと突然宣言されていない型も不明な怪しげな変数が出てくるので、読んでたり書いてたりするときにこれなんだよってなることはある気がする
  • 色々いわれる例のself, やっぱり(書くのが)面倒くさい。インスタンス変数を弄るときにselfを書くのはともかく、メソッドの第一引数にいちいちselfを書くのは気味が悪い。
    • 書き忘れでエラーが出るとムキーとなる
    • 書くのは面倒だがその分読みやすいのは確か
  • インスタンスを生成するときにnew演算子なりnewメソッドを使わないのが気持ち悪い。明示をよしとする言語でnew演算子がないのには、何か深い理由がありそうだが?
  • range関数も正直わかりにくいし好きじゃないなぁ
  • 最初addやdelete_lastメソッドは個別に実装していたが、insertやdeleteで流用できることに気づく。if A return B で Aが満たされないならNoneが返るのでこれでよいのだ。
  • こうして苦労しながらコードを書いてみると、連結リストがシーケンシャルアクセスであるということが身にしみて理解できる。
  • Pythonイテレータへの理解がまだあやふや。for文で呼び出したら、まず__iter__()を実行して、次にraise StopIteration()されるまで__next__()が実行されて順番に値が取り出される、ってことかな?
class MyList:
    def __init__(self, value = "Dummy"):
        self.value = value
        self.pointer = None
        self.size = 0 # list[0]="Dummy", list[1]~list[self.size]

    def getindex_cellobject(self, index): # index番目のセルオブジェクトを返す
        if index <= self.size:
            tmp = self
            for i in range(index):
                tmp = tmp.pointer
            return tmp

    def __getitem__(self, index): # index番目のセルオブジェクトに含まれる値をself[index]でアクセスできるようにする
        if index <= self.size:
            return self.getindex_cellobject(index).value

    def add(self, value): # リスト最後尾に追加
        return self.insert(self.size + 1, value)

    def delete_last(self): # 最後尾を削除
        return self.delete(self.size)

    def insert(self, index, value): # index番目に挿入
        if 1 <= index <= self.size + 1:
            leftcell  = self.getindex_cellobject(index - 1)
            rightcell = leftcell.pointer

            newcell = MyList(value)
            leftcell.pointer = newcell
            newcell.pointer = rightcell

            self.size += 1
        return self

    def delete(self, index): # index番目を削除
        if 1 <= index <= self.size:
            leftcell  = self.getindex_cellobject(index - 1)
            rightcell = leftcell.pointer.pointer
            leftcell.pointer = rightcell

            self.size -= 1
        return self

    def show(self, a, b): # list[a] ~ list[b]
        if 0 <= a <= b <= self.size:
            for i in range(a, b + 1):
                print(self[i])

    def showall(self):
        self.show(0, self.size)

    def addlist(self, anotherlist): # リストの結合
        if anotherlist.size > 0:
            lastcell = self.getindex_cellobject(self.size)
            lastcell.pointer = anotherlist.pointer
            self.size += anotherlist.size

    def __iter__(self):
        self.tmp = self
        return self

    def __next__(self):
        if self.tmp == None:
            raise StopIteration()
        result = self.tmp.value
        self.tmp = self.tmp.pointer
        return result


testList = MyList().add("aaa").add("bbb").add("ccc").insert(2, "2").delete_last().delete(1).add("LAST")
another = MyList().add(1).add(10).add(100)
testList.addlist(another)

for value in testList:
    print(value)

# 出力
# Dummy
# 2
# bbb
# LAST
# 1
# 10
# 100

DCSS Fsimの使い方

この記事はRoguelike Advent Calendarの12/11分です。


 

今回はDCSSのFsim(やそれに関連するwizard mode)の使い方をまとめてみる。
というのは、存在自体知らないとか、あるいは知っていても使い方を知らないという人が意外といるのではないかと推測されるため。

 

 

・そもそもFsimって何よ?

Wizard modeで使用できる一機能であるFighting Simulatorの略称。
これを使うことで、今の@のステ・スキルでこの武器をこの敵に使うと大体どれくらいのダメージが期待できるか、という具体的な数値がわかる。非常に便利な機能である。

よく、@の溜まり場や海外の公式Forumで、今のこのスキルではどの武器を選ぶのがいいのかわからない、と初心者が質問しているが、
そんなことわざわざ人に聞かなくてもFsimかければわかるよっていうのが今回の記事の主旨。


  それでは、実際にFsimを使ってみよう。以下で手順を詳述する。

・まずはゲームのダウンロードから
そこからかよ、って突っ込みが入りそうだが、これは一部のプレイヤーにとってとても大切なステップ。
というのは、DCSSはWebtileでもプレイできるのだが、WebtileではWizard modeが使えない。
なので、Webtile playerがFsimを使おうとしたら、まずゲームをダウンロードするというステップを踏む必要があるのだ。

ゲーム本体のダウンロードはこちらから → http://crawl.develz.org/trunk/
trunk(unstable)0.20のダウンロードができる。ここのWindows Tiles Buildでとりあえず話を進める。

f:id:radinms:20161211110526p:plain

 

 

・ゲーム起動、キャラ作成、Wizardモードに入る
&と入力することで、Wizardモードに入ることができる。
その際、Wizard modeに入ることでスコア対象外になるというご丁寧な警告文が出てくる。yesと入力してWizard modeに入りましょう。
ちなみに、Wizard modeに入ると、画面右上に *WIZARD* とご丁寧に表示される。

 

・Wizard modeのコマンドを実行する
&でWizar modeに入れると上で書いたが、実は&はWizard modeのコマンドを実行するときに最初に打つキーである。
Fsimを回す前に、まずは色々設定する必要があるので、適宜コマンドを入力してステータスやスキルなどを設定していく。

よく使う代表的なコマンドを挙げる。

&@:ステータスを設定する。@ 20 15 18と入力すれば、Str20 Int15 Dex18に設定される

f:id:radinms:20161211110612p:plain


&l:レベルを設定する。最初に何レベルにしたいか聞かれ、その後レベルアップに伴ってスキルを鍛えたいか聞かれる(yかnで返事)

f:id:radinms:20161211110706p:plain


&s:スキルを設定する。最初にどのスキルの値を変えるか聞かれ、その後変更後のスキルの値を聞かれる

f:id:radinms:20161211110751p:plain


&o:指定のアイテムを入手できる。まず最初に種類を問われる。対応する記号を入力すると、今度は具体的な名前を問われ、最後にもし必要であればエゴ名を入力する。

f:id:radinms:20161211110837p:plain

 

 

     たとえば、battleaxe of freezingを入手したいなら、&o -> ) -> battleaxe -> freezingと入力すればいい。

f:id:radinms:20161211111036p:plain


  その後、修正値を+9まで強化したいなら、&o -> ? -> enchant weapon と入力すればenchant weapon巻が12枚手に入る。


&r:種族を変更する

f:id:radinms:20161211110913p:plain


&p:別の形態に変異する(Dragon FormやStatue Formのように変異術魔法として変異できるものに限らず、fungsとかpigとかにも変異できる)

f:id:radinms:20161211110940p:plain

 

他にもコマンドはあるが、Fsim回す程度ならこのくらいのコマンドを知っておけば十分事足りる。

・Fsimを実際に使ってみる
レベル、ステータス、スキル、武器防具の設定を上記のコマンドで終えれば、いよいよFsimだ。
Fsimを回すには、&fと入力する。入力すると、どの敵に対してFsimを回すか問われるので、適当に敵の名前を入力する。
筆者はStone giantで試すのが好きなので、とりあえずStone giantで試してみる。

f:id:radinms:20161211111131p:plain

 

すると、ログにFsimの結果が表示される。
上のAttakingが、@からStone giantに対する物理攻撃の算出値。
下のDefendingが、Stone giantから@に対する物理攻撃の算出値。

f:id:radinms:20161211111200p:plain

 

各値の意味は、大体名前を見ればわかると思うが、
AvHitDamが、物理攻撃が命中したときの期待ダメージ
MaxDamが、最大ダメージ
Accuracyが、命中率
AvDamが、AvHitDam * Accuracy によって算出される値で、命中率を考慮した期待ダメージ
AvTimeが、1回の物理攻撃にかかるターン数(の100倍の値)を示しており、
AvSpeedは、1ターンに何回物理攻撃ができるかの数を示している(AvTime * AvSpeed = 100である)。
そして、一番右のAvEffDamという値が、最終的な1ターンにおける期待ダメージを示している。

 

今回は、&fコマンドでのFsimを試したが、
これは要するに@のスキルを現在値に固定したときにどのくらいのダメージが期待できるかを示したものに過ぎない。

 

スキルを変動させながら期待ダメージの変化を眺めたいときは、&Fコマンドを利用する。
&Fを入力すると、AttackとDefenseどちらの値が欲しいか問われるので、欲しい値を選択する。今回はAttackを選んでおく。
選ぶと、少々時間がかかるのだが、ログにスキルの変動(今回の例であれば斧スキル)を考慮したFsim結果が表示される。

f:id:radinms:20161211111242p:plain

ちなみに、これと同じ結果が、DCSSのcrawl.exeが入っているフォルダに、fsim.txtに出力されるようになっている。
複数回Fsimをかければ、その結果は既存のfsim.txtに追記されるので、Fsimを何回もかけるときに昔のFsim結果を逐一別ファイルに保存する必要はない。便利である。

 

 

以上が、基本的なFsimの使い方である。細かい設定・使い方などはまだあるが、いちいち書くときりがないし、とりあえずこの程度の操作ができるだけでも新たに見えてくるものはあるので、Fsimの使い方解説はここまでとしておく。

 

明日のカレンダーの記事は、dplusplus氏の「DCSS client luaを触る」です。

皆さん是非是非ご参加くださいな。

東方変愚 混沌の領域(オベロン後の127Fまで)雑感&騎兵雑感

<100~127F雑感>

 

東方変愚、ラスボスは一応オベロンなのだが、真・勝利するためには127Fにいる混沌のサーペント(以下Jと略記)を倒す必要がある。

東方変愚のJは、本家より大幅に強化されており、殴り合いで勝とうとするとかなりの確率で事故ってしまうことで非常に有名である。

 

どこが強いのか?

 

・殴り火力

大体本家の二倍くらいあるといわれている。

本家の殴りスペックは以下:

22d10 のダメージで体当たりして粉砕し、 22d10 のダメージで体当たりして粉
砕し、 10d12 のダメージで噛んで全ステータスを減少させ、触って充填魔力を
吸収する。

それに対して、東方変愚での殴りスペックは次の通り:

22d15 のダメージで体当たりして粉砕し、 22d15 のダメージで体当たりしてカオスで攻撃し、
15d15 のダメージで噛んで全ステータスを減少させ、 10d10 のダメージで飲み込んで魔力を吸い取る。

軒並みダイス目は増えるわ、AC軽減できない殴りが増えるわ、と凶悪すぎる。

 

・ヘル・ファイア

東方変愚には耐性で軽減不可能な地獄の業火属性という属性が存在する。

Powerfulフラグ持ちの敵によるヘル・ファイアのダメージは、

    Lv * 4 + 100 + 10d10

である。JのLvは127なので、なんと618~708ものダメージをくらってしまう。

この数字がとんでもない値であることは、本家変愚をプレイしたことがある人なら一目で理解してくれるはずだ。

本家変愚であれば、HP800が安全ラインとされ、この数値を維持していれば即死に至ることは滅多にありえない。

一方、東方変愚では、この馬鹿げたダメージ数値を見ればわかる通り、ヘル・ファイア絡みで連続行動されたらかなりの高確率で死んでしまう。

HP1300程度を維持できれば、まあそうそう死なないだろうが、そんな状況は特殊な手段を使わない限り到底不可能である。

 

波動砲

本家Jのクソ行動といえばなんといっても分解ブレス。近接戦なら、分解ブレスを吐かれ、予め整地した地形を崩されたら、戦場を変えるしかない。

東方変愚のJは、恐ろしいことにこの分解ブレスが超強化された波動砲を使ってくる。

分解ブレスのダメージはMAX150だが、波動砲はMAX555ダメージ。こんなトンデモダメージに壁分解効果がついているなんて、ひど過ぎて接近戦なんてたまったもんじゃない。

 

・他取り巻き

勝手版独特の要素として視界外隣接テレポートというものがあり、これを使ってくる深層ユニークが存在する。

ユニークだけならまだいいが、非ユニークまで使ってくるので……。

たとえばレッサー・アザー・ゴッドの群れがJ戦で現れるようなら阿鼻叫喚である。

その確率を少しでも下げるために、アザトートを120Fで狩っておく(アザトートの護衛として大量にレッサー・アザー・ゴッドが生成されることが多い)のが定石なのだが、このアザトートも曲者。何が強いって、大量に生成され視界外隣接テレポートを決めてくるレッサー・アザー・ゴッドもさることながら、アザトート本体の因果混乱ブレスも脅威の一言である(時空耐性がなければMAXで600もくらってしまう)。

 

また、127Fには必ずモルゴスと秩序のユニコーンが生成され、こいつらがJ戦に乱入してくる。モルゴスは*破壊*しても即座に復活、秩序のユニコーンは若干のタイムラグの後に復活。クッソだるい。

 

以上のような理由で、東方変愚のJは本家より大幅に強化されているのである。

遠距離戦なら本家とさほど難易度は変わらないのだが、近接戦なんてたまったもんじゃない。やられる前にやるくらいの高火力がなければ、ヘル・ファイア絡みの連続行動を引いて成仏である。

 

<騎兵雑感>

 

本家の騎兵は未プレイなので、本家との比較はし辛いのであるが、勝手版でも騎兵は強いしむしろ強化されているまでありうる。

弓と殴りを両立させるのが難しくなっているのは確かに弱体化ポイントなのだが、むしろ両手持ちが強くなっているので、本家以上の打点を出せる。

というか本家の騎兵って両手持ちしてもちゃんと馬操れたっけ?(勝手版では最終的には可能になる)

あと、本家では最終的な馬は変幻の魔公が一般的だったが、東方変愚では時空ワイアームが強い。本家から大幅に強化されており、酸耐性抜けという弱点はあるものの、HP8500という高耐久に加え、耐性なしの敵を階層からいなかったことにできる因果混乱ブレスが凶悪すぎる。

 

あと騎兵は射撃が得意で、両手に追加射撃をはめる余裕があるので、遠距離戦でJを戦い抜ける。実際、真・勝利間近まで達した。

うっかり箱開けて召喚即死したけどな!

 

f:id:radinms:20161030184037j:plain

 

 後で知ったのだが、箱も含め召喚トラップを発動させてしまったときは、召喚された敵からターンが始まる。なので、大広間でうっかり召喚箱を開けてしまうと、このようにターンが@に回ってくる前に即死してしまうのだ……。