0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python と Flask と JavaScript で作る一人用○×ゲーム(VS AI)

Posted at

はじめに

こんにちは!今回は Python の Flask と JavaScript を使って、一人用の「○×ゲーム」を簡単に作成する方法を紹介します。AIとの対戦が可能で、シンプルなデザインながら、楽しめるウェブアプリケーションを作ります。


🎮 完成イメージ

  • ゲームの流れ:
    1. プレイヤー(X)がマスをクリックして記号を配置。
    2. AI(O)が自動で次のマスに記号を配置。
    3. 勝敗または引き分けが判定されると、ゲームがリセット。
  • シンプルな操作:
    • クリックして遊ぶだけ!
    • デザインも基本的なCSSで作成。

スクリーンショット 2024-12-26 15.29.08.png


🛠 必要なツール

  • 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の動作

  1. 空いているマスを取得:

    empty_cells = [i for i, cell in enumerate(board) if cell == " "]
    
    • board から空いているマスのインデックスを取得します。
  2. ランダムでマスを選択:

    return random.choice(empty_cells) if empty_cells else -1
    
    • 空いているマスからランダムで1つ選びます。
    • 空きがない場合は -1 を返します(ゲーム終了時の処理)。

改良案

現在のロジックは「空きマスにランダムで配置」ですが、以下のように改良することでより強いAIを実現できます:

  1. 勝利可能なマスがあれば優先して配置。
  2. プレイヤーの勝利を防ぐためのブロックを配置。
  3. 中央や角を優先的に選択する戦略。

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));
});

🚀 実行方法

  1. ターミナルでサーバーを起動:
    python app.py
    
  2. ブラウザで http://127.0.0.1:5000/ にアクセスしてゲームをプレイ!

📝 まとめ

このプロジェクトでは、Flaskを使ったサーバーサイドロジックとJavaScriptによるフロントエンド連携を学べます。また、シンプルなAIロジックを追加して、一人用のまるばつゲームを実現しました。さらに強いAIロジックを実装することで、ゲーム性を向上させることも可能です!

ぜひ試してみて、自分だけの改良を加えてみてください😊

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?