ChatGPT によるリバーシの作成その３

ChatGPT によるリバーシゲームの作成手順を変えてみます。

# 概要

前回（[ChatGPT によるリバーシの作成その２](https://qiita.com/rex0220/items/f182278937ef13e93cbc)）のように、チャットを続けて機能を追加すると、エラー・修正の繰り返しになったので、最初にある程度仕様を与えて開発してみます。

- 仕様に応じてコードが作成されるが、仕様追加・変更すると以前のコードと継続性が無くなり、新たな問題が発生する
- ある程度仕様を修正した後は、チャットで機能追加・変更を行う
- 盤面の位置ズレについては、表示された盤面の画像を読み込ませても、うまく修正されない
※実際の画面をChatGPTに読み込ませても位置ズレは認識できないようです
- 位置を調整した CSS を直接指定すると、CSS に余計な変更を加えて結局ズレる
保守する場合は、ある程度固まったコードをベースにして、コードの一部を ChatGPT に出してもらうくらいしかなさそう

![2024-09-13_10h48_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/8eb953b4-aa2b-4506-5b5c-00a13e319638.png)

現状の問題点

- パス機能が未実装
- 「もどす」後に、別の手順を行うとNG 

# 盤面の変更過程

いろいろ位置が変わったり、盤面の位置ズレ、画面サイズの変更によるズレなど試行錯誤の結果です。
結局、ChatGPT がコードが書けてもその結果のズレをフィードバックするのが困難で、細かな調整は手作業でやるしかなさそう。

![2024-09-13_09h27_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/d4773815-6101-6ab2-b9ce-06a87d8c778a.png)


# チャットの内容

>下記仕様で、リバーシゲームを作成してください
・盤面は、div と css で正方形にする
・盤面の上部に a-h の位置を枠の中央に表示
・盤面の左に 1-8 の位置を枠の中央に表示
・盤面と位置表示がずれないように
・白と黒の駒は、丸くわかりやすいように表示
・初期画面で、白2個と黒2個の駒を中央に表示
・黒先行で、どちらの順番かわかるように表示
・盤面をクリックすると、駒がおけるか判定し、おける場合は、駒を配置し、はさんだ駒を反転表示
・ゲームの履歴を右側に固定長で表示
・リセット機能を実装
・もどす・すすむの機能を実装
・リセット・もどす・すすむのボタンは横幅を統一

チャット中の問題

- 盤面と位置表示がズレて表示される
    - いろいろ修正指示を行ったが、ダメで直接 CSS の変更コードを指示
- 履歴の表示が冗長
    - フォーマット指定

チャットの共有がエラーになるので、共有は後程。

![2024-09-13_11h25_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/7d014f3b-f4ae-c01d-1157-04676feacf11.png)

# コード

```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="style.css">
</head>
<body>
    <h1>リバーシゲーム</h1>
    <div class="game-container">
        <div class="board-info">
            <div id="turn">黒の番です</div>
            <button id="reset">リセット</button>
            <button id="undo">もどす</button>
            <button id="redo">すすむ</button>
            <div id="history">履歴</div>
        </div>
        <div class="board-wrapper">
            <div class="column-labels">
                <div>a</div><div>b</div><div>c</div><div>d</div><div>e</div><div>f</div><div>g</div><div>h</div>
            </div>
            <div class="board-container">
                <div class="row-labels">
                    <div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div>
                </div>
                <div id="board"></div>
            </div>
        </div>
    </div>

    <script src="script.js"></script>
</body>
</html>
```

```style.css
body {
    font-family: Arial, sans-serif;
    text-align: center;
}

h1 {
    margin-bottom: 20px;
}

.game-container {
    display: flex;
    justify-content: center;
    gap: 20px;
}

.board-info {
    display: flex;
    flex-direction: column;
    align-items: center;
}

#turn {
    font-size: 18px;
    margin-bottom: 10px;
}

button {
    width: 100px;
    margin: 5px 0;
}

#history {
    width: 100px;
    height: 200px;
    overflow-y: scroll;
    border: 1px solid #000;
    margin-top: 10px;
}

.board-wrapper {
    display: grid;
    grid-template-columns: 50px repeat(8, 50px);
    grid-template-rows: 50px repeat(8, 50px);
    justify-content: center;
    margin-top: 20px;  /* 追加: 縦のスペースを調整 */
}

.column-labels, .row-labels {
    display: grid;
    gap: 0;
}

.column-labels {
    grid-column: 2 / span 8;
    display: grid;
    grid-template-columns: repeat(8, 50px);
    text-align: center;
}

.row-labels {
    grid-row: 2 / span 8;
    display: grid;
    grid-template-rows: repeat(8, 50px);
    text-align: center;
    justify-items: center;
}

#board {
    grid-column: 2 / span 8;
    grid-row: 2 / span 8;
    display: grid;
    grid-template-columns: repeat(8, 50px);
    grid-template-rows: repeat(8, 50px);
    gap: 1px;
    position: absolute;
    top: 140px;
    left: calc(50% - 115px); /* 画面の中央に配置 */
}

.cell {
    width: 50px;
    height: 50px;
    background-color: green;
    position: relative;
}

.piece {
    border-radius: 50%;
    width: 80%;
    height: 80%;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.black {
    background-color: black;
}

.white {
    background-color: white;
}
```

```script.js
const board = document.getElementById('board');
const turnDisplay = document.getElementById('turn');
const resetButton = document.getElementById('reset');
const undoButton = document.getElementById('undo');
const redoButton = document.getElementById('redo');
const historyDisplay = document.getElementById('history');

let boardState = Array(8).fill().map(() => Array(8).fill(null));
let history = [];
let currentHistoryIndex = -1;
let currentTurn = 'black';

const columns = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; // 列名の定義
const directions = [
    [-1, 0], [1, 0], [0, -1], [0, 1], // 上、下、左、右
    [-1, -1], [-1, 1], [1, -1], [1, 1] // 斜め方向
];

// 初期の駒配置
function initializeBoard() {
    board.innerHTML = '';
    boardState = Array(8).fill().map(() => Array(8).fill(null));
    boardState[3][3] = 'white';
    boardState[3][4] = 'black';
    boardState[4][3] = 'black';
    boardState[4][4] = 'white';
    history = [];
    currentHistoryIndex = -1;
    renderBoard();
    updateHistory();
}

// ボードの描画
function renderBoard() {
    board.innerHTML = '';
    for (let row = 0; row < 8; row++) {
        for (let col = 0; col < 8; col++) {
            const cell = document.createElement('div');
            cell.classList.add('cell');
            cell.dataset.row = row;
            cell.dataset.col = col;
            cell.addEventListener('click', handleCellClick);
            const piece = boardState[row][col];
            if (piece) {
                const pieceElement = document.createElement('div');
                pieceElement.classList.add('piece', piece);
                cell.appendChild(pieceElement);
            }
            board.appendChild(cell);
        }
    }
}

// 駒のクリック処理
function handleCellClick(e) {
    const row = parseInt(e.target.dataset.row);
    const col = parseInt(e.target.dataset.col);
    if (boardState[row][col]) return;

    if (isValidMove(row, col, currentTurn)) {
        makeMove(row, col, currentTurn);
        history = history.slice(0, currentHistoryIndex + 1); // 履歴を更新
        const move = `${columns[col]}${row + 1}`; // 置いた場所を「列+行」で記録
        history.push({ board: JSON.parse(JSON.stringify(boardState)), move, player: currentTurn });
        currentHistoryIndex++;
        switchTurn();
        renderBoard();
        updateHistory();
    }
}

// 有効な手かどうかを判定する
function isValidMove(row, col, color) {
    // 8方向のチェックを行い、少なくとも1方向で挟める駒があればtrueを返す
    return directions.some(direction => canFlip(row, col, direction, color));
}

// 駒を挟めるかどうかを確認
function canFlip(row, col, direction, color) {
    let [dx, dy] = direction;
    let x = row + dx;
    let y = col + dy;
    let hasOpponentPiece = false;

    while (x >= 0 && x < 8 && y >= 0 && y < 8 && boardState[x][y] && boardState[x][y] !== color) {
        hasOpponentPiece = true;
        x += dx;
        y += dy;
    }

    // 最後に自分の色の駒があれば、その間にある駒を挟めると判断
    return hasOpponentPiece && x >= 0 && x < 8 && y >= 0 && y < 8 && boardState[x][y] === color;
}

// 駒を配置し、挟んだ駒を反転させる
function makeMove(row, col, color) {
    boardState[row][col] = color;
    flipPieces(row, col, color);
}

// 駒を反転させる処理
function flipPieces(row, col, color) {
    directions.forEach(direction => {
        if (canFlip(row, col, direction, color)) {
            let [dx, dy] = direction;
            let x = row + dx;
            let y = col + dy;

            while (x >= 0 && x < 8 && y >= 0 && y < 8 && boardState[x][y] && boardState[x][y] !== color) {
                boardState[x][y] = color; // 駒を反転
                x += dx;
                y += dy;
            }
        }
    });
}

// ターンの切り替え
function switchTurn() {
    currentTurn = currentTurn === 'black' ? 'white' : 'black';
    turnDisplay.textContent = currentTurn === 'black' ? '黒の番です' : '白の番です';
}

// リセット
resetButton.addEventListener('click', initializeBoard);

// 履歴を戻す
undoButton.addEventListener('click', () => {
    if (currentHistoryIndex > 0) {
        currentHistoryIndex--;
        boardState = JSON.parse(JSON.stringify(history[currentHistoryIndex].board));
        renderBoard();
        updateHistory();
    }
});

// 履歴を進める
redoButton.addEventListener('click', () => {
    if (currentHistoryIndex < history.length - 1) {
        currentHistoryIndex++;
        boardState = JSON.parse(JSON.stringify(history[currentHistoryIndex].board));
        renderBoard();
        updateHistory();
    }
});

// 履歴表示の更新
function updateHistory() {
    historyDisplay.innerHTML = history.map((entry, index) => {
        const playerName = entry.player === 'black' ? '黒' : '白'; // プレイヤー名を日本語に
        return `<div>${index + 1}. ${playerName}: ${entry.move}</div>`;
    }).join('');
}

// ゲーム開始時の初期化
initializeBoard();
```