はじめに
こんにちは!今回は Python の Flask と JavaScript を使って、一人用の「○×ゲーム」を簡単に作成する方法を紹介します。AIとの対戦が可能で、シンプルなデザインながら、楽しめるウェブアプリケーションを作ります。
🎮 完成イメージ
-
ゲームの流れ:
- プレイヤー(X)がマスをクリックして記号を配置。
- AI(O)が自動で次のマスに記号を配置。
- 勝敗または引き分けが判定されると、ゲームがリセット。
-
シンプルな操作:
- クリックして遊ぶだけ!
- デザインも基本的なCSSで作成。
🛠 必要なツール
- Python 3.x
- Flask
- HTML/CSS/JavaScript
📁 プロジェクト構成
以下のような構成でファイルを作成します。
project/
├── static/
│ ├── style.css # CSSファイル
│ └── script.js # JavaScriptファイル
├── templates/
│ └── index.html # HTMLテンプレート
└── app.py # Flaskアプリケーション
🔧 コード解説
1. Flaskアプリケーション (app.py
)
以下のコードを app.py
に保存します。AI(O)の動きを含めたロジックを組み込んでいます。
from flask import Flask, render_template, jsonify, request
import random
app = Flask(__name__)
# 初期化
board = [" "] * 9
current_player = "X"
def check_winner(board, player):
win_combinations = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], # 横
[0, 3, 6], [1, 4, 7], [2, 5, 8], # 縦
[0, 4, 8], [2, 4, 6] # 斜め
]
for combo in win_combinations:
if all(board[i] == player for i in combo):
return True
return False
def ai_move():
empty_cells = [i for i, cell in enumerate(board) if cell == " "]
return random.choice(empty_cells) if empty_cells else -1
@app.route("/")
def index():
return render_template("index.html")
@app.route("/play", methods=["POST"])
def play():
global board, current_player
# データを受け取る
data = request.json
cell = data.get("cell")
if board[cell] != " ":
return jsonify({"status": "error", "message": "Invalid move"})
# プレイヤーの手を配置
board[cell] = current_player
# 勝敗判定
if check_winner(board, current_player):
winner = current_player
board[:] = [" "] * 9 # ボードリセット
current_player = "X" # 次ゲームは常にXが開始
return jsonify({"status": "win", "winner": winner, "board": board})
# 引き分け判定
if " " not in board:
board[:] = [" "] * 9 # ボードリセット
current_player = "X" # 次ゲームは常にXが開始
return jsonify({"status": "draw", "board": board})
# プレイヤー交代(AIのターン)
current_player = "O" if current_player == "X" else "X"
if current_player == "O":
ai_choice = ai_move()
if ai_choice != -1:
board[ai_choice] = current_player
if check_winner(board, current_player):
winner = current_player
board[:] = [" "] * 9 # ボードリセット
current_player = "X" # 次ゲームは常にXが開始
return jsonify({"status": "win", "winner": winner, "board": board})
elif " " not in board:
board[:] = [" "] * 9 # ボードリセット
current_player = "X" # 次ゲームは常にXが開始
return jsonify({"status": "draw", "board": board})
# プレイヤー交代
current_player = "X"
return jsonify({"status": "continue", "board": board, "current_player": current_player})
@app.route("/reset", methods=["POST"])
def reset():
global board, current_player
board = [" "] * 9
current_player = "X"
return jsonify({"status": "reset", "board": board})
if __name__ == "__main__":
app.run(debug=True)
AIロジックの解説
AIが動作する部分は ai_move()
関数で定義されています。この関数の仕組みは以下の通りです:
AIの動作
-
空いているマスを取得:
empty_cells = [i for i, cell in enumerate(board) if cell == " "]
-
board
から空いているマスのインデックスを取得します。
-
-
ランダムでマスを選択:
return random.choice(empty_cells) if empty_cells else -1
- 空いているマスからランダムで1つ選びます。
- 空きがない場合は
-1
を返します(ゲーム終了時の処理)。
改良案
現在のロジックは「空きマスにランダムで配置」ですが、以下のように改良することでより強いAIを実現できます:
- 勝利可能なマスがあれば優先して配置。
- プレイヤーの勝利を防ぐためのブロックを配置。
- 中央や角を優先的に選択する戦略。
2. HTMLテンプレート (templates/index.html
)
以下を templates/index.html
に保存します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>まるばつゲーム</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h1>まるばつゲーム</h1>
<p id="status">現在のプレイヤー: X</p>
<div class="board" id="board"></div>
<button id="reset">リセット</button>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
3. CSS (static/style.css
)
以下を static/style.css
に保存します。
body {
font-family: Arial, sans-serif;
text-align: center;
}
.board {
display: grid;
grid-template-columns: repeat(3, 100px);
gap: 10px;
justify-content: center;
margin: 20px auto;
}
.cell {
width: 100px;
height: 100px;
font-size: 2rem;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #000;
cursor: pointer;
background-color: #f9f9f9;
}
.cell:hover {
background-color: #e0e0e0;
}
button {
padding: 10px 20px;
font-size: 1rem;
cursor: pointer;
}
4. JavaScript (static/script.js
)
以下を static/script.js
に保存します。
document.addEventListener("DOMContentLoaded", () => {
const boardElement = document.getElementById("board");
const statusElement = document.getElementById("status");
const resetButton = document.getElementById("reset");
function renderBoard(board) {
boardElement.innerHTML = "";
board.forEach((cell, index) => {
const cellElement = document.createElement("div");
cellElement.classList.add("cell");
cellElement.textContent = cell;
if (cell === " ") {
cellElement.addEventListener("click", () => playMove(index));
}
boardElement.appendChild(cellElement);
});
}
async function playMove(cell) {
const response = await fetch("/play", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cell }),
});
const data = await response.json();
if (data.status === "win") {
alert(`${data.winner} の勝利!`);
} else if (data.status === "draw") {
alert("引き分け!");
}
renderBoard(data.board);
statusElement.textContent = `現在のプレイヤー: ${data.current_player}`;
}
resetButton.addEventListener("click", async () => {
const response = await fetch("/reset", { method: "POST" });
const data = await response.json();
renderBoard(data.board);
statusElement.textContent = "現在のプレイヤー: X";
});
fetch("/reset", { method: "POST" })
.then(response => response.json())
.then(data => renderBoard(data.board));
});
🚀 実行方法
- ターミナルでサーバーを起動:
python app.py
- ブラウザで http://127.0.0.1:5000/ にアクセスしてゲームをプレイ!
📝 まとめ
このプロジェクトでは、Flaskを使ったサーバーサイドロジックとJavaScriptによるフロントエンド連携を学べます。また、シンプルなAIロジックを追加して、一人用のまるばつゲームを実現しました。さらに強いAIロジックを実装することで、ゲーム性を向上させることも可能です!
ぜひ試してみて、自分だけの改良を加えてみてください😊