角をとっただけでは勝てないリバーシの亜種を作った
「オセロ(リバーシ)って、角取ったら勝ちなんでしょ?」という風潮に抗うべく、角をとっただけでは勝てない『POW POW REVERSI』というリバーシの亜種を作った。
これ。
早送りでお送ります。
なにが違うのかというと、2つの追加ルールがある。
- 石に点数がある。最初は2点で1回ひっくり返るごとに2,4,8,16,32...と点数が倍になる。
- 合計1000点を超えたらその時点で勝ち。 盤面が埋まったら点数が大きいほうが勝ち。
表計算ソフトのPOW関数のように2乗で増えていくからPOW POW REVERSI。
どんなに盤面を制圧されても2^10=1024を1個でも作れば一発逆転できる。逆もまたあり。四隅をとっても一手たりとも油断できない。
【問題】
たとえばこの局面、黒は26点、白は860点、白が2隅を押さえた黒番。一見すると白が圧倒的に勝っているように見えるが...黒には一手で勝負を決める会心の一手が残されている。おわかりいただけただろうか?
このゲームは中央付近は頻繁にひっくり返るから高得点化し、角や辺はあまりひっくり返らないから点数が低い傾向がある。オセロの既成概念がひっくり返る。
ここで遊べます。
細部解説
盤面の描画
対局画面。これはSVGでできている。
こんな感じでReactで動的にSVGを組み立てている。
SVGは内部の要素にも部分的にCSSをかけられるので、こういうちょっとしたアニメーションも簡単に実現できる。
先読み
思考エンジンはミニマックス法(を改良したアルファベータ法)を採用している。
ミニマックス法とは、自分と相手の取れる手を木構造で探索し、『双方は必ず最善手を取る』という前提のもと、相手がもっとも得できない手を選ぶ手法。
Wikipediaの説明の図 [1] がイメージしやすい。奇数の段(自分)は評価値を最大化する手を探索し、偶数の段(相手プレイヤー)は評価値を最小化する手を探索している。これを繰り返すと将棋の『先読み』のようなことが実現できる。
ミニマックス法は筆者が書いた本でも扱ってるので、気になった人は買ってください。喜びます。
評価関数
探索をするにはそもそも「どんな局面が高評価か」という局面に点数をつける『評価関数』が必要になる。
レベル1〜3までは、単純に盤面の点数をそのまま評価値にした。相手の点数は×-1をして計算する。これでもそこそこ賢く戦える。
しかしそれだけでは強さが頭打ちになる。このゲームは角を取っただけでは勝てないが、角は取らないより取ったほうがいい。点数の読み合い+ポジショニングの両方を考慮しないと勝てない。
そこでレベル4以上は「ポジショニングの強さ」も評価に加えるようにした。
こちらのサイトで知ったのだが、オセロの盤面は下の図 [2] のような有名な評価表があるらしい。角は中央の40倍の価値がある。「オセロって、角取ったら勝ちなんでしょ?」という現実を突きつける表だ。
レベル4以上ではこれを使い、普段は『ポジショニングの強さ』で評価値を付けつつ、詰みがありうる局面では『石の点数の合計』で判断するというハイブリッド方式を採用した。むちゃくちゃ強い。
棋譜再生&シェア機能
このゲームは対局した棋譜をシェアする機能がある。たとえば以下のリンクを踏むと棋譜が再生される。
仕組みは簡単で、URLに棋譜を突っ込んでいる。これによりDBやバックエンドを用意せずとも完全にフロントエンドだけで棋譜のシェアを実現している。
https://kurehajime.github.io/powpow_reversi/?replay=1&player=1&level=1&log=44.45.37.29.19.18.17.9.20.43.52.21.34.33.12
ぷるぷるアイコン
絵の下手さを誤魔化すために対戦相手のアイコンをぷるぷる震えるようにしている。
対戦が始まった時点ではキャラクターはじっとしているが、戦況が進むについて激しく動く。
これにはSVGのフィルター機能を使っている。
こんな感じのSVGフィルターを用意して
<svg aria-hidden="true" style={{ position: 'absolute', width: 0, height: 0 }}>
<filter id="distortionFilter">
<feTurbulence type="fractalNoise" baseFrequency="0.03" numOctaves="2" seed="1" result="noise">
<animate attributeName="seed" values="1;2;3;4;5;6;7;8;9;10" dur="2s" repeatCount="indefinite" calcMode="discrete" />
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="noise" scale={jitterScale} />
</filter>
</svg>
filterとしてidを指定するとうねうね動く。
<img
src={src}
alt={alt}
style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: 8, filter: 'url(#distortionFilter)' }}
/>
feTurbulenceという画像を乱すノイズエフェクトをかけるフィルターのシード値を数秒ごとに変えてうねうね動かしている。
ここのサイトで紹介されているテクニックを参考にした。ここにあるサンプルはもっと凄い。
おわりに
自分は最高レベルの『こわいドラゴン』を倒すのに数日かかった。
みなさんも挑戦してみてください。
Discussion
はじめまして
ちょっとはまっており、今1時間くらいやってます。
僭越ながら、個人的な希望なのですが書かせてください。
(いえ、CPUの戦略部分とかの実装に重きをおいてるだけで、そんなUXが目的のアプリではないことは分かってるんですけどね…。ただ、知育にもよいかもと少しだけ思いました。子供がオセロを親に無限にねだる期間ってあるじゃないですか。あそこで足し算掛け算を考えれれば、子供が数の扱いに慣れる&1手に時間をかけるので親が相手するのが楽という。)
追記
ちょっと笑いました。可愛いなと思ってましたがそんな裏話があるとは。
遊んでいただきありがとうございます!
ダークモード見落としてました!これ見えないですね。修正しました。
あとCPUのキャラの下に勝敗数を表示するようにしました!
早速の対応ありがとうございます!
まさか対応していただけるとは思ってなかったのでびっくりしました。
見やすくなりました。
vs龍やってます!負け越していることが記録として分かるようになりました…笑