SDBtctb5RMYlUy0x.qrunch.io読者になる2はじめてのPythonで「パネルでポン」の解法ツールを作ってみたPython投稿日:2018-12-24最終更新:2018-12-24筆者のブログで記事をみるこんにちは、べんぞうさんです。Pythonを勉強し始めて早2ヶ月。基本的な文法をざっと覚えたので何か作ってみたいと思い、「パネルでポン」の解法ツールに挑戦してみました。「パネルでポン」とはなんぞや?1995年にスーパーファミコンで発売されたパズルゲームです。SYSTEMS出典:任天堂ホームページhttps://www.nintendo.co.jp/titles/20010000001545上図中央がゲーム画面です。)のパネルが配置されています。プレイヤーは横に並んだ2つのパネルを入れ替え、縦または横に同じ色を3つ以上揃えることで対象のパネルを消すことができます。画像では四隅を白枠で囲まれた2つのパネル(紫・水)がありますが、これがプレイヤーの選択しているパネルです。この状態でパネルを入れ替えると紫が縦に3つ揃って消えることになります。プレイヤーの操作が横に並んだパネルの入替に限られているので、解法ツールも作りやすそうです。今回は「パネルでポン」の1人用モードに収録されている「パズルモード」のステージ59を解いていこうと思います。余談になりますが、「パネルでポン」はミニスーパーファミコンに収録されていて、このステージ59はどうしても自力で解けずに諦めてしまった苦い思い出のステージです(パズルモードは全60ステージなのでクリア目前だった......)。Pythonの力を借りてリベンジするのだ!!ステージ59とクリア条件まずはステージ59の画像から。SYSTEMS右上に「04」とあるのはパネルの入替可能回数です。ステージ59は4回のパネル入替ですべてのパネルを消す必要があります。次にパネルの色を数値に置き換えてみます。パネルなし:0そして、ステージ59のパネル配置を上記の数値に置き換えます。[0,0,0,0,0,0][0,0,0,0,0,0][0,0,0,0,0,0][0,0,5,0,0,0][0,0,1,0,0,0][0,0,3,0,0,0][0,1,4,0,0,0][0,2,4,2,0,0][0,4,1,5,0,0][0,2,1,5,0,0][0,1,3,1,0,0][0,1,3,1,0,0]それっぽくなってきました。4回のパネル入替でこの72マスをすべて0にすればクリアです。必要な機能を実装していきましょう。機能の実装パネルに対する入替・落下・消え判定がメインになります。左下を(x,y)=(0,0)、右上を(x,y)=(5,11)として、選択箇所をx,y座標で表現します。選択している横2マスの左側を(x,y)、右側を(x+1,y)とします。この左右のパネルを入れ替えます。ステージ59全体のパネル情報をNumpy配列で記録しています。対象のパネルをx,yで指定しているのですが、xとyがひっくり返っているのが気持ち悪いですね......。これはステージ59のデータをdifficult=np.array([[0,1,3,1,0,0],[0,1,3,1,0,0],[0,2,1,5,0,0],[0,4,1,5,0,0],[0,2,4,2,0,0],[0,1,4,0,0,0],[0,0,3,0,0,0],[0,0,1,0,0,0],[0,0,5,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]])こんな形式で保持しているためです。これ......逆になるな......」と気付きました。ように縦1列のまとまりで考えればよかった。直観的に画面のイメージと繋がらなかったので最初のまま作ってしまいましたが、後々まで残るややこしさの元になってしまったのが反省点です。入替用の変数(leftpanel,rightpanel)を作っていますが、こんなことをしなくても要素の入替はできるような気がする......。対象パネルの落ちるマス数として、縦1列ずつを下からチェックしていきます。1まずチェックするマスにパネルがあるかをチェック。なければemptypanelをインクリメントします。パネルがある時はemptypanelの値を調べ、0であればパネルは落ちないので何もしない。0以外であればy座標からemptypanelを引いた場所に対象パネルの要素を移し、元々のパネル位置には0を入れます。以上のチェックを72マスすべてで実行します。うーむ......底になるy座標を記録する変数、bottomlineは不要だったのね......。パネルの消え判定ここはいろいろと工夫しました。self.panel_maskまずゲーム画面と同じ大きさのNumpy配列panel_maskを作りました。panel_maskのすべての要素を1で初期化しておき、パネルが消えると判定された位置は要素を0にします。全72マスの判定が終わったらpanel_statusとpanel_maskを掛け合わせ、消えるパネルを0にした配列でpanel_statusを更新しています。最初はpanel_statusを直接更新していたのですが、パネルが同時に4つ・5つと消えたり、縦・横が同時に消える時の場合分けがうまく整理できなかったのでワンクッション置く方式を取りました。対象のパネルが消えるか消えないかだけ分かればいいので、下図の6パターンをチェックすればいいことになります。シンプルになったはずなのに、消えるかどうかの判定コードがずいぶん冗長になってしまいました。x=0の時、左と下はチェックしなくていいなどの場合分けが美しくないので、ここはもう少し工夫したいところです。クラス完成!必要なパーツは揃いました。ここまでの機能をTrialStateクラスとしてまとめます。chkパネルを初期設定の配置に戻すrollback_panel、チェックするパネルの位置を移動するmove_next、すべてのパネルが消えたかを判定するclear_checkなどを追加しています。このクラスを使ってステージ59を解いてみますよ~。解析スタート!!パターンの総当たりで解答を探していきます。TrialStateクラスからインスタンスを4つ(入替可能回数)作成し、Trial1に初期パネル配置を設定。左下の(0,0)と(1,0)を入れ替え、パネルの消え・落ちの判定を行った後のパネル配置をTrial2の初期配置に渡します。Trial2でも同様に(0,0)(1,0)のパネルを入れ替え、パネルの消え・落ちの判定後、パネル配置をTrial3に渡します。Trial3からTrial4も同じ。Trial4ではパネルの入れ替え・消え・落ちの判定後、パネルがすべて消えたかの判定を行います。パネルがすべて消えなかった場合はTrial4のパネル配置を初期値(Trial3から受け取った時点)に戻し、パネル選択位置をx方向に1つ移動します。Trial4の全パターンをチェックしたらTrial3を1つ移動して、Trial4を最初からチェック。Trial3の全パターンをチェックしたらTrial2、Trial2をチェックしたらTrial1と戻っていって全パターンを探索します。文章だとちょっとややこしいので図にまとめてみました。途中でパネルがすべて消える組み合わせを見つけたらクリア。その時点のTrial1~Trial4のパネル選択位置(x,y)を表示して処理終了となります。以上の処理をこんな感じで実装してみました。処理を走らせ、ゾンビランドサガを見ながら待ちます。3と表示されていました。とメモを取り、ミニスーファミを立ち上げます。座標が0スタートであることを忘れないようにして、該当のパネルを入れ替えていきます。ステージ59クリアで~す!!!反省会というわけで「パネルでポン」の解法ツールが完成しました。最初は2~3日でできると思っていましたが、なんだかんだで2週間ほどかかってしまいました。パネル消え判定のアイデアを思いついたのはちょっと自分を褒めてあげたい。メイン処理のWhileループを抜け出すやり方はネットで見つけたスマートなやり方を参照しました。これまで我流でコードを書いていましたが、一般的なパターンを勉強して、いわゆる「車輪の最発明」を避けるようにする必要があると感じました。自分のコードは無駄が多くて古くさい印象です。具体的な反省として、Pythonでの変数の取り扱いがまだよくわかっていないなと実感しました。deepcopyしておけば大丈夫だろうという感じだったので、変数については知識の縦糸を通しておきたいです。range()を使いましたが、Pythonなのでイテレータで記述したいところでした。今回はとりあえず動くものをPythonで作ることが目標だったので、ここからの作りこみはしないつもりですが、3ヶ月くらいたってもう少し強くなったら色々改良してみたいです。上記の「とりあえず動くものをPythonで作る」という他に「作成物と作成経過を何かしらの形で公開する」というのが大きな目的だったんですが、こういった技術的な文章を書くのにこんなに時間がかかるとは思いませんでした。ツールを作った時間の倍は使っています......。慣れてはきたので、次はもう少し時間短縮をしたいですね。1ヶ月以上かかったミッション終了~。Qrunch.netサーバー提供フィードバックを送信する。