はじめに
MYJLab Advent Calendar 2024の1日目を担当する3年の丸です。
初日を選んだ過去の自分をタコ殴りにしたい今日この頃ですがなんとか形になりました。
来週再来週と立て続けに発表があるためさっさとやろうとしたのですが、にしても初日は先輩に任せるべきだろと思っています。
さて今回は某水曜日の番組で行われていた電気イスゲームをReactで作ってみました。
基本ルール
プレイヤー1がイスに電流を仕掛け、プレイヤー2が安全なイスを選ぶ心理戦ゲームです。 その後に以下の条件でゲームが終了します:
- どちらかが電流に3回当たったとき、3回当たった人が負け
- イスごとに点数がありどちらかの合計得点が40点を超えたとき、超えた人の勝ち
- 電流にあたらずに座ることができたイスは消え、イスが残り1つになったとき、得点の高い方が勝者
コードの説明
1. イスの配置とクリックイベント*
イスを円形に配置し、それぞれがクリック可能な状態を実現しました。ChairGrid
コンポーネントでは、以下のように各イスを動的に生成しています:
const chairs = Array.from({ length: 12 }, (_, i) => i + 1);
{chairs.map((chairNumber, index) => {
const angle = -Math.PI / 2 + (index / chairs.length) * 2 * Math.PI;
const x = 500 + 300 * Math.cos(angle);
const y = 400 + 300 * Math.sin(angle);
return (
<g
key={chairNumber}
onClick={() => handleChairClick(chairNumber)}
>
<circle cx={x} cy={y} r="45" fill="#121212" stroke="#d4af37" />
<text x={x} y={y + 5} textAnchor="middle">{chairNumber}</text>
</g>
);
})}
ポイント
- 円周上に配置するため、三角関数を使用して座標を計算
- イスごとにクリックイベントを設定
2. ターンの進行とスコア管理
このゲームはターン制で進行します。それぞれのターンで、以下の流れが実現されています:
- Player 1 が、12個のイスの中から電流を仕掛けるイスを選択
- Player 2 が、どのイスに座るかを選択
- Player 2の選択に基づき、得点または電流命中が判定され、スコアや電流命中回数が更新
実装コード
const handleChairClick = (chairNumber) => {
const currentPlayer = state.turn;
const newScores = { ...state.scores };
const newScoreHistory = { ...state.scoreHistory };
const newElectricHits = { ...state.electricHits };
if (currentPlayer === 1) {
// Player 1が電流を仕掛ける
setState((prevState) => ({
...prevState,
electricChair: chairNumber,
message: `Player 1が電流をイス${chairNumber}に仕掛けました!Player 2は座るイスを選んでください。`,
turn: 2, // Player 2のターンに切り替え
}));
} else if (currentPlayer === 2) {
// Player 2が座るイスを選択
const isElectric = state.electricChair === chairNumber;
if (isElectric) {
addEffectToQueue('⚡ 電流を受けました ⚡');
newScores[currentPlayer] = 0; // スコアをリセット
newScoreHistory[currentPlayer].push('⚡');
newElectricHits[currentPlayer] += 1;
} else {
const points = chairNumber; // イス番号に応じたポイント
addEffectToQueue(`+${points} 点!`);
newScores[currentPlayer] += points;
newScoreHistory[currentPlayer].push(points);
}
// 次のターンへ
setState((prevState) => ({
...prevState,
scores: newScores,
scoreHistory: newScoreHistory,
electricHits: newElectricHits,
remainingChairs: prevState.remainingChairs.filter(
(chair) => chair !== chairNumber
),
message: `Player 2がイス${chairNumber}に座りました。次はPlayer 1のターンです。`,
turn: 1,
}));
}
};
3. 勝敗の判定とゲーム終了条件
const checkGameOver = () => {
if (state.electricHits[2] >= 3) {
// Player 2が電流を3回受けた場合
setState((prevState) => ({
...prevState,
gameOver: true,
message: 'Player 2が電流を3回受けて敗北しました!',
winner: 1,
}));
return true;
}
if (state.scores[2] > 40) {
// Player 2が40点を超えた場合
setState((prevState) => ({
...prevState,
gameOver: true,
message: 'Player 2が40点を超えて勝利しました!',
winner: 2,
}));
return true;
}
if (state.remainingChairs.length === 1) {
// イスが1つだけ残った場合
const winner =
state.scores[1] > state.scores[2] ? 1 : 2;
setState((prevState) => ({
...prevState,
gameOver: true,
message: `残り1つのイス!得点が高いPlayer ${winner}の勝利!`,
winner,
}));
return true;
}
return false; // ゲーム続行
};
この関数は、イスが選択されるたびに実行されます:
if (!checkGameOver()) {
setCurrentTurn((prevTurn) => (prevTurn === 1 ? 2 : 1));
}
ポイント
-
スコアとイスの動的管理:
- 状態管理フックを用いて、ゲームの進行状況をリアルタイムで更新
-
エフェクトによる視覚的演出:
- 電流を受けた、スコアを獲得、勝敗がついたときに画面全体にエフェクトを登場させた
完成したゲームの公開
GitHub Pagesを使用してデプロイしました。友達と何も賭けずに遊んでみてください!
おわりに
だいぶ前にReactの復習も兼ねて作ってみようと思い作り途中で終わったものを完成させるいい機会になりました。
今までゲームっぽいゲームを作ってこなかったので初めて作ってみて意外と楽しかったです。
イスの配置ぐらいまでは楽しみながら自分でコードを書いていたのですが、途中から時間とエラーに悩まされGPTのアニキに聞くと、気づいたらほとんど書いてもらってました。
GitHubでの公開も簡単にできたので今後何でもかんでも公開してやろうかなって思いました。
初日としてはしょぼい記事になってしまいましたがカレンダー完成に向けてみなさんがんばってください!