こなごころ

きもちをつづるばしょ

壊リスexの開発小話 Vol.8 CPU対戦の話

wikiに書くような内容ではないけど、せっかくだからアウトプットしておきたい。

あわよくば、誰かがゲーム開発をするときに何か参考になれば嬉しい。

そんな話。

 

壊リスexはこちらからダウンロード。

壊リスex@wikiはこちら。

 

今回はCPU対戦のゲームモードについて。

 

CPU対戦のイメージカラーは無機質感のあるグレー

まあCPU対戦は他の5つのゲームモードの実装後に追加したものであったため、イメージカラーとして使える色があまり残っていなかった感じはある。

 

CPU対戦も壊リスexで新設したゲームモード。

名前の通り、CPUと対戦ができる。

ルールはオンライン対戦とほぼ同じで、先にゲームオーバーになった方が負け、生き残った方が勝ち。

コンボを決めると相手におじゃまを送ることができ、逆に自分側におじゃまが送られている時にはコンボを決めれば相殺することができる。

オンラインランキングは無いが、トロフィーとして指定のレベル以上のCPUに3連勝するというものがある。

 

CPU対戦の実装が難しかった理由

壊リス壊でもCPU対戦の機能は考案されており実装中だったようだが、結局最後まで追加されることは無かった。

その一因としては、対戦AIの思考調整が非常に難しいという問題があったため。

 

壊リスはコンボを論理的に(確実に)構築することがほぼ不可能である。

その理由としては、

  • ブロック一つ一つにHPがある
  • ブロック同士が複雑に連結している
  • ミノ1つの操作だけでも無数の置き方がある
  • コンボによって落下したブロックにもそれぞれバラバラにダメージが入る
  • ブロックのダメージ調整がシビアで、ダメージを与え過ぎれば消えてしまうし、十分にダメージが入らなければ連結が外れない

などが挙げられる。

こういったことから、二度とブロックの配置が同じ状況というのは存在せず、連鎖後の状態を先読みすることも困難、逆に言えばミノを一つ置き間違えても次の一手でどうとでもなるため、状況に応じた最適解を導き出すことができないのだ。

 

また、CPUの場合、特に避けたいのが自滅である。

もちろん、単純に自滅を回避するなら、ブロックをほとんど積まずにひたすらラインを決めるようにすれば良い。

しかし、リスクを負わないプレイングではコンボが決まらず、まともに攻撃できないというジレンマが発生する。

この攻撃と自滅リスクのバランスというのも非常に難しい問題である。

 

自滅に関して言えばさらに、どうやったら自滅が回避できるか、という問題もある。

ゲームの性質上、自滅を回避しようとしてかえって状況が悪化するというケースがしばしば発生する。

例えば、

  • 勢いよくミノをぶつけたら意図せず積んであるブロックに半端な空洞ができてしまい、コンボの起爆点までダメージが通らなくなって詰む
  • コンボを起爆したつもりが全然コンボにならず、残ったブロックで次のコンボが起爆できなくて詰む
  • 次に来るミノを使えば解決できたのに今あるミノで何とかしようとして詰む

みたいなことがかなりある。

AI的な観点で言えば、自滅対策はコンボを構築するよりも難しいと言っても過言では無かったりする。

 

さらに他にも、対戦では相手がいるためにおじゃまが送られてきた時の対応も必要になる。

おじゃまが送られてきた時の対応としては、

  • 即座にコンボを決めて対処する
  • おじゃまが降ってくる直前までブロックを積んでからコンボを始動する
  • あえておじゃまを降らせてしまう

といったことが考えられる。

これもまたあらゆる状況が考えられるため、どういった対応が最適かは判別が非常に難しい。

特に「おじゃまを降らせてしまう」というのはランダム要素が関わってくることもあり、論理的に答えを出す際に選択肢に入れにくい。

 

というわけで、テトリスぷよぷよのように明確なセオリーがあって操作対象の配置も限られている落ち物ゲームとは違い、不確定要素満載の壊リスではAIの構築が非常に難しいのだ。

 

対戦AIの実装

色々と困難な問題を抱えているが、それでもCPU対戦は実装した。

果たしてどうなったのかというところを書いておこう。

長いので読み飛ばしても良いと思うが。

 

まず、俺自身のプレイングにおいて、どういう理屈でミノを配置しているのかというのを言語化、つまり言葉で説明できるようにする必要があった。

(そもそもコーディングにおいて言語化できることは大事だが。)

 

壊リスに定石は無いが、ベターな置き方、ある程度のセオリーはある

コンボの構築に関して言えば、

  • 両端に置かない
  • なるべく隙間ができないように置く
  • 同色のブロックは離して置く
  • 全体的にまんべんなく置く
  • ダメージを分散させたい時はミノの底面の接触面積を大きくする
  • ダメージを集中させたい時はミノの底面の接触面積を小さくする
  • メタルブロックの穴の位置を考慮する
  • ある程度の高さまで積んだら起爆する

などといったことが挙げられる。

 

もちろんそれだけではダメで、自滅しないように考慮する必要もある。

自滅の回避に関して言えば、

  • ゲームオーバーラインを越えないようにする
  • 高く積み過ぎない
  • ブロックが浮かないようにする
  • 必要に応じてずらし置きをする
  • ピンチの時はカラーブロックのみでもラインして消す
  • 起爆点となるブロックにダメージが入るように積む
  • 起爆点となるブロックにダメージが入らない状態の時は上部のブロックを壊す

などといったことが挙げられる。

 

これらを基に、アルゴリズムを構築していくのだ。

 

ミノの配置の決め方は、左右どちらか一方へ移動→そのまま落下させる操作を行った時のすべての配置パターンに対してそれぞれ評価点を付けるという方法を取った。

具体的には以下の通り。(実際のアルゴリズムはもっと複雑だが文字起こしするのは難しいので割愛。)

  • 両端の1つ手前の位置は加点(+3pt)
  • 両端にはみ出す位置は減点(-6pt)
  • 低い位置ほど加点(+高さ/2pt)
  • すでに置かれているブロックの高さが低い列ほど加点(+高さに応じたpt)
  • メタルブロックの穴にそのまま入ると加点または減点(±10pt)
  • メタルブロックの穴に既存のブロックを落とせる位置なら加点(+10pt)
  • 同色が接触すると減点(-接触面数×3pt)
  • 斜め方向に同色のブロックがあると減点(-個数pt)
  • 底面の接触面積に応じて加点(接触面数×2pt)
  • 左右の接触面積に応じて加点(接触面数*pt)
  • メタルブロックの穴にブロックが落とせなくなる位置は減点(-15pt)
  • メタルブロックの穴の隣接列でブロックが落とせなくなる位置は減点(-8pt)
  • ブロックが浮く場合は減点(-浮いたブロック数の3乗pt)
  • 上から2段目のメタルブロックの穴の位置に対してブロックが浮く場合は減点(-6pt)
  • 上から3段目のメタルブロックの穴の位置に対してブロックが浮く場合は減点(-3pt)
  • 接触面積が小さい落とし方をする必要がある時は接触面積に応じて加算(5~20pt)
  • ゲームオーバーラインを越えていて左移動ができない位置は減点(-5pt)
  • ゲームオーバーラインを越えていて右移動ができない位置は減点(-5pt)
  • ゲームオーバーライン直下の3段目までをラインできる場合は加点(+30pt)

 

評価点を決める際には、コンボを起動するかどうかも考慮する必要がある。

コンボを起動するかどうかは、

  • 最も高く積まれているブロックの高さ
  • 積まれているブロックの平均の高さ
  • おじゃまが送られている場合の猶予回数

を基に決定している。

 

この結果、評価点の最も高かった配置パターンに決定する。同点の場合はランダム。

また、レベルによってはずらし置きをするのだが、ずらし置きをしないケースではゲームオーバーラインを超えない候補のみに絞って配置を決定する。(すべての配置がゲームオーバーラインを超えてしまう場合は除く)

 

次にミノを落とす高さ(ブロックに与えるダメージ)を決定する。

すでに積まれているブロックをすべて考慮することは困難なため、特定のブロックをチェックしてダメージ調整することになる。

 

基本は、ミノごとに決まっている基準のブロック(大体ミノの中央)が置かれる列の一番下にあるブロックをチェック対象としている。

ただし、メタルブロックの一番上の穴のある列もしくはその両隣にミノを置く場合は、その列の一番下のブロックがチェック対象となる。

 

ミノの落下で与えるダメージは、以下のように決定する。

  • 起爆する必要があるときは最大で残り5%までHPを減らす
  • まだ起爆しない時点で起爆点へダメージを入れる場合は45%までHPを減らす
  • それ以外は配置ブロックの高度が高いほど残りHPを減らす(10%~60%)

 

さらにここから、そのままではゲームオーバーになってしまう配置の場合やメタルブロックの穴にブロックが落とせない状態の場合、ずらし置きの移動先も考える。

前述の評価点を付けた配置パターンの中からずらし置き可能なパターンに絞って、評価点の最も高かった配置をずらし置き後の最終配置地点にする。

ずらし置き後の落下によるダメージは微々たるものなので考慮しない。

 

というわけで、以上のようなアルゴリズムでCPU君は戦っている。

かなりごちゃごちゃしているが、これは実際にCPU対戦を何度もテストプレイして、問題個所を一つ一つ改善していった結果。

 

CPUのレベルごとの違い

CPUのレベルは1~10が選択でき、レベルを高くするほど強くなる。

CPUのレベルごとの違いは以下の通り。

落下速度

ミノの落下速度。

Lv.7以上はミノの落下速度が早くなる。

 

起爆判定の高さ

ブロックをどの程度の高さまで積むとコンボを起爆するかの判定基準。

Lvが高いほど高く積むようになるため、コンボ数が多くなりやすい。

 

メタルブロックの穴への配置

メタルブロックの穴に直接ミノを差し込んでラインするケースをチェックするかどうか。

メタルブロックを直接ラインするとコンボ数が少なくなりやすい。

Lv.2以下はメタルブロックの穴に直接ミノが入る置き方はプラス評価判定になる。

逆にLv.3以上はマイナス評価判定になる。

 

両端への配置

ブロックプールの両端にブロックを配置するケースをチェックするかどうか。

両端にブロックを配置すると、カラーブロックのみのラインで消えてしまったり、

隣接する同色のブロックと連結してコンボを止めてしまったりする可能性が高くなる。

Lv.3以上はブロックプールの両端にブロックが配置される置き方はマイナス評価判定になる。

 

同色の接触

同色のブロックが隣接する配置をチェックするかどうか。

同色のブロックを隣接させて配置すると、連結して塊になり落下しにくくなる。

また、同色のブロックはライン巻き込みで消えてしまうため、

ライン巻き込みでブロックが消え過ぎるとコンボ数が伸びにくくなる。

Lv.7以上は同色のブロックが隣接する置き方がマイナス評価判定になる。

 

ハードドロップの使用

ハードドロップの操作を使用するかどうか。

ハードドロップを使用した方がミノを置くのが早くなるため、使用した方が強くなる

Lv.5以上はハードドロップを使用するようになる。

 

クイックハードドロップの使用

クイックハードドロップの操作を使用するかどうか。

クイックハードドロップはミノを置いた後の遊び時間(左右移動や回転ができる時間)が無いため、ハードドロップよりもさらに速くなる。

Lv.8以上はクイックハードドロップを使用するようになる。

 

ゼロドロップの使用

ミノの底面がメタルブロックに接する場合にダメージを入れるように置くかどうか。

Lv.6以上はダメージが入らないように置くようになる。

また、Lv.6~8はゼロハードドロップ、Lv.9, 10はゼロクイックハードドロップを使用する。

 

ずらし置きの使用

ミノを一旦着地させた後、左右にずらして置くかどうか。

ずらし置きをした方がブロックのダメージを調整しやすくなったり、自滅を回避しやすくなる。

Lv.5以上はずらし置きを使用するようになる。

 

ダメージの誤差

ミノを落とす際に与えるダメージの誤差の値。

誤差の値はランダムだが、誤差の範囲はLvが高いほど小さくなる。

 

インターバルフレーム

1つの操作を行ってから次の操作を行うまでのフレーム数。

Lvが高いほどインターバルは短くなり、速く動く。

 

対戦AIの問題点

色々と書いたが、この方法もまだまだ完璧ではない。

次のミノの考慮はしていないなど、ある程度妥協している部分もある。

壊リスのゲームの性質上、低レベルでも大連鎖を出してしまうことがあるなど、レベルの調整がやや甘い部分もある。

 

特に自滅の回避処理については、高レベルほど自滅しやすい問題がある。

しかし事はそう単純ではなく、原因部分となったところを改善しようとすると、コンボが構築しにくくなったり、別のケースで自滅しやすくなったりするなどの問題が発生してしまうのだ。

というわけで、ある程度の妥協はせざるを得ないのだ。

 

まあ壊リスに限ったことではなく、複雑なシステムを持つゲームにおける対戦AIのアルゴリズムは大抵完璧にはできないものなので仕方ない。

 

今回はここまで。