LoginSignup
2
0

オセロのロジックをReact+TypeScriptに落とし込んだ

Last updated at Posted at 2024-01-06

React+TypeScriptの勉強がてらオセロを作成してみたら案外苦戦したので、概要とReact特有の難しかった点を書いておきます。

  • ステップバイステップで作成してみよう!みたいな内容ではありません(需要があったら作ります)
  • デザイン性は皆無です。あくまでロジックに焦点を当てて作成しました。
  • 初学者ゆえ、もし不備がありましたら遠慮なくお申し付けください。

成果物

Github Pages (実際にプレイできます)
https://yudatchshouga.github.io/react-test/
Github (ソースコード)
https://github.com/yudatchshouga/react-test
制作期間:2日

test.gif

概要

オセロのルール周りに関しては省略します。

  • 盤面は4*4である(コード上で簡単に変更可能)
  • 盤面には現在のプレイヤーが駒を置ける場所をハイライト表示する
  • 盤面の上部にはゲームステータスが表示される
    • プレイヤーが駒を置ける場合、現在のプレイヤー: ⚫︎のように表示
    • 片方のプレイヤーが駒を置けなくなったらパスと表示
    • 両者駒を置けなくなったらゲームオーバーと表示
  • パスが発生した場合、1秒間ステータスにパスと表示されターン交代する
  • CPUは今回は実装していない(チャレンジ予定)

余談ですが、ChatGPTに聞いたところ、駒を置ける箇所の精査にはどうしても$O(N^3)$かかってしまうらしいです($N$は盤面の一辺のサイズ)。

React特有の難しかった点

状態管理

最初はGameという変数に色々な状態を集約して、Gameを1つの状態として扱ってました。ですが、様々な判定を加えていくにつれて再レンダリングが何重にも発生してしまうという事態になったので、状態を分離しました。最終的には以下の状態を採用することにしました。

// 盤面
const [board, setBoard] = useState<string[][]>(initBoard);
// 盤面に駒が置けるかどうかのフラグ
const [putables, setPutables] = useState<boolean[][]>(initPutables);
// プレイヤー
const [player, setPlayer] = useState<string>(initPlayer);
// ステータス
const [gameStatus, setGameStatus] = useState<string>("playing");

なお、ステータスは以下のように定義しました。

  • 駒を置ける状態 → playing
  • 現在のプレイヤーが駒を置けず、次のプレイヤーが駒を置ける状態 → pass
  • 両者が駒を置けない状態 → gameover

状態更新とuseEffectについて

ReactではuseStatesetXxxで状態更新をします。
単純な更新ならいいのですが、オセロのようにロジックが複雑になってくると、

  1. boardの状態を更新(駒をひっくり返す)
  2. playerを更新(ターン交代)
  3. board,playerを使ってputablesの更新(置ける場所の確認)
  4. putablesを使ってステータスを更新(pass, gameoverの判定)

のように更新された状態を用いて別の状態を更新する処理が多発します。

この時、愚直に1,2,3,4と処理を順番に書くと、2でplayerの更新が終わっていないのに3の処理に進んでしまうというバグが起きてしまいます。
どうやらReactの状態更新処理は非同期処理であることに起因しているようです。

これらの問題を解決してくれるのがuseEffectです。
先ほどの2→3の処理が順番に行われない問題は以下のように書くことで解決します。

// プレイヤー交代したら置ける場所を更新する
useEffect(() => {
    // 置ける場所を探す
    setPutables(() => calculatePutables(board, player));
}, [player]);

このコードでは、playerを監視対象に設定し、playerが変更されたらuseEffectの中身が実行されます。

このようにuseEffectをうまく利用することで、非同期処理である状態更新の順序をうまく制御することができます。
useEffectは、パスの際に1秒待機する処理を書く際にも活躍してくれました。

最後に

今回はオセロをまる2日ほどかけて作成してみました。最終的にはCPUやAIを実装したいので、ロジックはバックエンドに移すかもしれませんが、とりあえずReactオンリーで書きました。
あと、本当はカッコつけて色んなクラスを作成して作っていたのですが、基礎ができていないうちにやるもんじゃないなと思いました。(自戒)

最近X始めたので、フォローいただけると嬉しいです!
https://twitter.com/yudatchshouga

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0