LoginSignup
0
0

GPT-4を搭載したBingチャットでオセロゲーム書かせたらサクッと出来て感動した話

Posted at

きっかけ

いまのBingChatはGPT-4を搭載しているという記事( https://qiita.com/takao-takass/items/16a7052a4a0e857b7c90 )を読んで、早速使ってみた話。
色々と記録を残しそびれたので、まずは結果だけを載せます。

コード

othello.html
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
    <div id="player" style="font-size: 24px; margin-bottom: 10px;"></div>
    <div id="board" class="board"></div>

    <script type="text/javascript" src="src/othello.js"></script>
    <noscript>JavaScriptが利用できません.</noscript>
</body>
</html>
css/style.css
.board {
    display: grid;
    grid-template-columns: repeat(8, 50px);
    grid-template-rows: repeat(8, 50px);
    gap: 1px;
}
.cell {
    width: 50px;
    height: 50px;
    background-color: darkgreen;
    border: 1px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 2em;
}
.stone {
    width: 80%;
    height: 80%;
    border-radius: 50%;
}
.black {
    background-color: black;
}
.white {
    background-color: white;
}
othello.js
class OthelloGame {
  constructor() {
    this.board = Array(8).fill().map(() => Array(8).fill(0));
    this.board[3][3] = this.board[4][4] = 1;
    this.board[3][4] = this.board[4][3] = -1;
    this.currentPlayer = 1;
    this.directions = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
  }

  traverseBoard(x, y, color, callback) {
    for(let [dx, dy] of this.directions){
      let nx = x + dx;
      let ny = y + dy;
      let count = 0;
      while(nx >= 0 && nx < 8 && ny >= 0 && ny < 8 && this.board[nx][ny] == -color){
        nx += dx;
        ny += dy;
        count++;
      }
      if(count > 0 && nx >= 0 && nx < 8 && ny >= 0 && ny < 8 && this.board[nx][ny] == color){
        callback(x, y, dx, dy, count);
      }
    }
  }

  canPlaceStone(x, y, color) {
    if(this.board[x][y] != 0) return false;
    let canPlace = false;
    this.traverseBoard(x, y, color, () => canPlace = true);
    return canPlace;
  }

  doPlaceStone(x, y, color) {
    this.traverseBoard(x, y, color, (x, y, dx, dy, count) => {
      let nx = x;
      let ny = y;
      for(let j = 0; j <= count; j++){
        this.board[nx][ny] = color;
        nx += dx;
        ny += dy;
      }
    });
  }

  hasLegalMove(color) {
    return this.board.some((row,x) => row.some((cell,y) => this.canPlaceStone(x,y,color)));
  }

  calculateScore() {
    let black = Array.prototype.concat(...this.board).filter(x => x === 1).length;
    let white = Array.prototype.concat(...this.board).filter(x => x === -1).length;
    return {black: black, white: white};
  }

}

class OthelloGameUI {
  constructor(game) {
    this.game = game;
    this.boardElement = document.getElementById('board');
    this.playerElement = document.getElementById('player');

    for(let i = 0; i < 8; i++){
      for(let j = 0; j < 8; j++){
        let cellElement = document.createElement('div');
        cellElement.className = 'cell';
        cellElement.addEventListener('click', () => {
          if(this.game.canPlaceStone(i, j, this.game.currentPlayer)) {
            this.game.doPlaceStone(i, j, this.game.currentPlayer);
            this.game.currentPlayer *= -1;
            if(!this.game.hasLegalMove(this.game.currentPlayer)) {
              this.game.currentPlayer *= -1;
              if(!this.game.hasLegalMove(this.game.currentPlayer)) {
                let score = this.game.calculateScore();
                let winner = score.black > score.white ? '' : '';
                alert('ゲーム終了\n' + '黒: ' + score.black + ', 白: ' + score.white + '\n勝者: ' + winner);
              }
            }
            if(this.game.currentPlayer == -1) { // AI(白)の手番
              if(!this.game.hasLegalMove(this.game.currentPlayer)) {
                this.game.currentPlayer *= -1;
              }
              this.drawBoard();
            } else {
              if(!this.game.hasLegalMove(this.game.currentPlayer)) {
                this.game.currentPlayer *= -1;
              }
              this.drawBoard();
            }
            this.drawBoard();
          }
        });
        this.boardElement.appendChild(cellElement);
      }
    }

    this.drawBoard();
  }

  drawBoard() {
    for(let i = 0; i < 8; i++){
      for(let j = 0; j < 8; j++){
        let cellElement = this.boardElement.children[i * 8 + j];
        cellElement.innerHTML = '';
        if(this.game.board[i][j] != 0){
          let stoneElement = document.createElement('div');
          stoneElement.classList.add('stone');
          if(this.game.board[i][j] == 1){
            stoneElement.classList.add('black');
          } else if(this.game.board[i][j] == -1){
            stoneElement.classList.add('white');
          }
          cellElement.appendChild(stoneElement);
        }
      }
    }

    this.playerElement.textContent = '現在のプレイヤー: ' + (this.game.currentPlayer == 1 ? '' : '');
  }
}

let game = new OthelloGame();
let ui = new OthelloGameUI(game);
ui.drawBoard();

どう出したか

正直に言えば、一発でここまで出たわけではありません。
最初は「簡単ではございますが……」みたいに遠慮がちに、全く動かないものを出してきたこともありました。
そして、これはBing Chatの仕様なのでしょうが、1000文字までしか入力できませんし、5回までしか返事がもらえません。それゆえの工夫も必要でした。

ファイルを分けさせた。

「htmlでオセロゲームを作りたい。htmlとcssとjavascriptで分けて、コードを書いて」
と最初に指示をして、書かせました。これだけで大分いい感じのものが出てきました。動きませんでしたが。

理想通り動くまで要求を繰り返す。

分離させたおかげで、jsのコードも全文載せられました(最初は)。
そこで、実際に動かしてみたときの不具合を、コードと共に突き付けて、
「このままだとこれこれこういうエラーがある。
 どこをどのように書き換えればいいか、正しいコードで返して」
とコードを必ず要求しました。コードを要求しないと

このエラーの原因は次のようなことが考えられます
1. ~~~
2. ~~~

と、説明だけして、結局素人には何も分からないことになりました。どこを、どのように、コードを書き換えるのか、を必ず要求しました。

コードが長くなると、1回の1000文字制限を超えるようになりますが、途中で切り、その続きを次のチャットに入れて、要求を添えれば、きちんと反応してくれました。これはありがたかった。

最後に、リファクタリング

継ぎはぎで動くようにしただけのオセロゲームですから、どうせ、綺麗なコードになっているわけがないのは火を見るより明らかです。

Bing Chatにjsのコードをぶん投げて、最後に
「これをリファクタリングして」
の一言でここまでになりました。

最初はクラスなんて作ってなかったのに、OthelloGameクラスとOthelloGameUIクラスに分けて「こちらの方が保守もうんたらかんたら」と言っていたのにはマジでビックリしました。
同じものがあるからfunctionでまとめようねとかそういうレベルじゃないんだっていう驚き。もうこれだけで筆者を完全に越えました。

まだまだ、改善の余地はありそうですが、ひとまずこの辺で。

0
0
3

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
0