GitHub Copilot Chatチュートリアル：N目並べゲームの作成

# はじめに
突然ですが、皆さんは「五目並べ」をご存知でしょうか？幼少期に遊んだことのある方も一定数いらっしゃるかと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/756c03cb-dee0-cdd3-307d-58e189731b44.png" width=50%>

※いい感じの**五目並べのイラスト**を貼り付ける予定だったのですが、そもそも五目並べのイラストがなかったため、**五目おにぎりのイラスト**で代替します。

<br>

本記事では、そんな~~五目おにぎり~~五目並べを拡張した「N目並べ」ゲームアプリの作成を通じて、GitHub Copilot Chatのチュートリアルを行います。

Copilot Chatを使用して、以下の手順でアプリの作成と別言語でのリライトを試す道中で、Copilot Chatの主要な機能をご紹介していきます。
1. Markdown形式のアプリ設計書から、ソースコードを生成する
2. 「1」で生成したソースコードから、Markdown形式の設計書を生成する
3. 「2」で生成した設計書をベースに「1」で作成したアプリを別言語でリライトする


また、本記事の前提事項、注意事項は以下のとおりです。

::: note warn
本記事において、アプリのソースコードは「動作確認時、期待通りに動けばOK」とします。
細かい実装の良し悪しについては考慮しません。
:::

::: note warn
生成系AIの特性上、本記事と同じプロンプトをCopilot Chatに与えても、同じソースコードが生成されるとは限りません。
実際、本記事でも「何度か同じプロンプトを与えて、上手く実装できたソースコードを採用する」方法をとっています。
:::

::: note warn
アプリの題材として五目並べを採用した理由は特にありません。
:::


# 想定読者
本記事は、以下の項目に該当する方を想定読者とします。
- ただの五目並べには飽きてしまい、新感覚の五目並べを遊んでみたい方
- GitHub Copilot Chatの簡単な使用例を見てみたい方

# GitHub Copilot Chatの超概要
著者の認識は、① IDEから直接使用可能で　② プログラミングに関する知識に特化した　③ ChatGPT です。
標準機能でソースコードの内容説明、修正・改善提案、単体テストコード生成などの機能を有します。
執筆時点では、VS Code、Visual Studio、JetBrainsのIDEでサポートされています。

::: note info
詳細は [公式ページ](https://docs.github.com/ja/copilot/github-copilot-chat/about-github-copilot-chat) をご覧ください。
:::

# やること
## 1.「N目並べ」ゲームアプリを作成 (生成) する

:::note info
本項でやることの目的：
- Copilot Chatのソースコード生成機能 (特に、Markdown形式のドキュメントからのソースコード生成) をご紹介すること
- Copilot Chatが「ユーザーの入力によって変更される、ある程度複雑な勝利判定ロジック」を正しく実装できるか検証すること
:::

### アプリの概要
本記事で作成する「N目並べ」は、五目並べを拡張した架空のゲームです。
通常の五目並べからの変更点は以下のとおりです。
- プレイヤーは、盤面のサイズと勝利条件(並べると勝ちになる石の数)を自由に指定することができる
- 2名ではなく、3名のプレイヤーで対戦を行う

### アプリの作成方法
まず、**Markdown形式のドキュメントで、アプリの簡単な設計書を作成**します。
次に、**作成した設計書をプロンプトとしてCopilot Chatに与えて、アプリのソースコードを生成**します。
アプリの実装修正や追加実装は、追加のドキュメントを作成し、上記と同様にCopilot Chatにプロンプトとして与える形式で行います。

## 2. 作成したアプリのソースコードから、設計書を生成する

:::note info
本項でやることの目的：
- Copilot Chatの「ソースコードからのドキュメント生成機能」をご紹介すること
- Copilot Chatが、ソースコードの実装内容をどのくらい理解し、分かりやすい日本語のドキュメントにできるかを検証すること
:::

「1」では、Markdown形式の設計書からソースコードを生成しましたが、ここでは逆に、**ソースコードからMarkdown形式の設計書を生成できるか**を試します。
「1」で作成した設計書と同じ見出しを与えて、各見出しの内容がどの程度再現できるかを確認します。

## 3. 生成した設計書から、ゲームアプリを別言語で再作成(生成)する

:::note info
本項でやることの目的：
- ~~ブラウザを開くだけで遊べるN目並べゲームを作りたかったので試しました~~
- Copilot Chatの「ドキュメント⇔ソースコード生成機能」を活用することで、どのようなことが実現できるか探索すること
:::

「2」で生成した設計書をベースに、もともと**Pythonで作成したデスクトップアプリを、HTML・CSS・JavaScriptによる簡単なWebアプリにリライトできるか**を試します。

# いざ、実践
## 1.「N目並べ」ゲームアプリを作成(生成)する
それでは実際に、アプリを作成していきます。
最終的に目指すのは前述の「N目並べ」ですが、まずは盤面のサイズを20×20に固定した「六目並べ」を作成し、その後で追加実装を行っていきます。

### 「六目並べ」の作成
#### Markdown形式の設計書作成
まずは、アプリの設計書をMarkdown形式で作成します。実際に作成した設計書は、以下のとおりです。

<details>
<summary>作成した設計書★</summary>

```
# 1. アプリケーションの概要
- 本アプリケーションは、五目並べをカスタマイズした「六目並べ」です。
- 通常の五目並べは、最初に縦横斜めのいずれかに石を5つ並べたプレイヤーが勝利しますが、本アプリケーションは、最初に石を6つ並べたプレイヤーが勝利します。
- また、通常の五目並べは2名のプレイヤーで対戦しますが、本アプリケーションは、3名のプレイヤーで対戦します。
- 本アプリケーションは、Webブラウザ上で動作するアプリケーションではなく、WindowsのGUIで動作するデスクトップアプリケーションとして開発します。
- 開発言語はPythonです。

# 2. 機能要件
- 対戦機能
    - 3名のプレイヤーで対戦します。
- 勝敗判定機能
    - 最初に、縦横斜めのいずれかに石を6つ並べたプレイヤーが勝利します。
    - 勝敗の判定結果は、画面上にダイアログで表示されます。
- リセット機能
    - 対戦の勝者が決まった時点で、自動的に盤面を初期状態にリセットします。
    - また、対戦中でもリセットボタンをクリックすることで、いつでもゲームをリセットできるようにします。

# 3. 非機能要件
- データの永続化
    - データの永続化は行いません。
- データのバックアップ
    - データのバックアップは行いません。

# 4. ユーザーインターフェース設計
- メイン画面
    - 画面上部には、各プレイヤーの名前と石の色が表示されます。
    - 画面中央には、盤面が表示されます。
    - 画面下部には、リセットボタンが表示されます。
- リセットボタン
    - リセットボタンをクリックすると、ゲームがリセットされます。
- 盤面
    - 盤面は、縦横20マスの正方形で構成されています。
    - 盤面のマスには、石を置くことができます。

- 上記の要件を全て満たす、アプリケーションの完全なソースコードを提示してください。
```

</details>

#### アプリのソースコード生成
作成した設計書を、Copilot Chatにプロンプトとして与えて、アプリのソースコードを生成してもらいます。
実際に生成されたソースコードは以下のとおりです。生成時点で、構文エラーは1つもありませんでした。

<details>
<summary>生成されたソースコード★</summary>

```python
import tkinter as tk
import tkinter.messagebox as msg

# 盤面のサイズ
BOARD_SIZE = 20

# 勝利条件（石を並べる数）
WIN_CONDITION = 6

# プレイヤーの情報
PLAYERS = [
    {"name": "プレイヤー1", "stone": "black"},
    {"name": "プレイヤー2", "stone": "white"},
    {"name": "プレイヤー3", "stone": "red"},
]

class GameBoard(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self.grid(sticky="nsew")
        self.create_widgets()

    def create_widgets(self):
        # 盤面の作成
        self.cells = []
        for i in range(BOARD_SIZE):
            row = []
            for j in range(BOARD_SIZE):
                cell = tk.Button(self, text=" ", width=2, command=lambda x=i, y=j: self.put_stone(x, y))
                cell.grid(row=i, column=j)
                row.append(cell)
            self.cells.append(row)

        # リセットボタンの作成
        self.reset_button = tk.Button(self, text="リセット", command=self.reset)
        self.reset_button.grid(row=BOARD_SIZE, column=0, columnspan=BOARD_SIZE)

        # ゲームの初期化
        self.reset()

    def reset(self):
        # ゲームの状態の初期化
        self.board = [["" for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
        self.current_player = 0
        self.game_over = False

        # 盤面の初期化
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                self.cells[i][j]["text"] = " "
                self.cells[i][j]["bg"] = "white"

        # プレイヤー情報の表示
        self.master.title(f"{PLAYERS[self.current_player]['name']} ({PLAYERS[self.current_player]['stone']}) の番")

    def put_stone(self, x, y):
        if self.game_over or self.board[x][y] != "":
            return

        # 石を置く
        self.board[x][y] = PLAYERS[self.current_player]["stone"]
        self.cells[x][y]["text"] = PLAYERS[self.current_player]["stone"]
        self.cells[x][y]["bg"] = PLAYERS[self.current_player]["stone"]

        # 勝者がいるかチェック
        if self.check_winner(x, y):
            msg.showinfo("ゲーム終了", f"{PLAYERS[self.current_player]['name']} の勝利！")
            self.game_over = True
            return

        # 次のプレイヤーへ
        self.current_player = (self.current_player + 1) % len(PLAYERS)
        self.master.title(f"{PLAYERS[self.current_player]['name']} ({PLAYERS[self.current_player]['stone']}) の番")

    def check_winner(self, x, y):
        # 勝者判定のロジック
        # 縦、横、斜めの各方向について、同じ色の石がWIN_CONDITION個並んでいるかをチェック
        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
        for dx, dy in directions:
            count = 0
            for i in range(-WIN_CONDITION+1, WIN_CONDITION):
                nx, ny = x + dx * i, y + dy * i
                if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[nx][ny] == PLAYERS[self.current_player]["stone"]:
                    count += 1
                    if count == WIN_CONDITION:
                        return True
                else:
                    count = 0
        return False

if __name__ == "__main__":
    root = tk.Tk()
    app = GameBoard(master=root)
    app.mainloop()
```

</details>


#### 動作確認
Copilot Chatが生成したアプリが、期待通りに動作するかを確認します。
結果、プレイヤー人数 (3人)、盤面のサイズ (20×20)、勝利条件 (縦・横・斜めのいずれかに石を6つ並べる) については、期待通りに実装されていることが分かりました。
ただし、設計書に記載した「対戦の勝者が決まった時点で、自動的に盤面を初期状態にリセットする」機能は実装されていませんでした。


次は、上記の自動リセット機能の実装と、追加の要件 (詳細は後述) を実装して「N目並べ」を完成させます。

#### 画面キャプチャ
・盤面のサイズは正しく20×20となっており、

　![Rokumoku1_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/938423da-2073-7889-f24a-bc83db5eaf97.png)

<br>

・最初に石を6つ並べたプレイヤーが勝利となります。
　(縦・横・斜めのいずれでも正しく勝利判定されます)
 
　![Rokumoku1_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/83aa5416-4090-dd07-8326-bb337546445c.png)


### 「N目並べ」に向けた追加実装
#### 追加要件に関するドキュメントの作成
アプリの追加要件として、以下の新機能を実装します。
- プレイヤー名の入力機能
- 盤面サイズの変更機能
- 勝利条件の変更機能

また、UIも一部、改善してみます。
新機能・UI改善案と、先ほど実装が漏れていた勝敗決定時の自動盤面リセット機能の実装を、追加要件としてドキュメントにまとめます。

<details>
<summary>作成したドキュメント★</summary>

```
# アプリケーションの追加要件

## 1. 既存の機能の改善
- リセット機能の改善
    - 対戦中、勝者が決まったときには、自動でゲームがリセットされるようにします。

## 2. 新規機能の追加
- プレイヤー名の入力機能
    - 対戦開始前に、プレイヤー名を入力できるようにします。
- 盤面サイズと、勝利条件の変更機能
    - 対戦開始前に、盤面サイズと、勝利条件を変更できるようにします。
    - 盤面サイズは、対戦前にプレイヤーが入力した数値に従うものとします。例えばユーザーが`20`と入力した場合は、盤面のサイズは`20x20`になります。
    - 勝利条件は、対戦前にプレイヤーが入力した数値に従うものとします。例えばユーザーが`6`と入力した場合は、勝利条件は「6つの石が並ぶこと」になります。

## 3. ユーザーインターフェースの改善
- メイン画面
    - プレイヤーが使用する石の色は、赤色、オレンジ色、緑色とします。
    - 石の形を丸形に変更し、マス目のうち、石の部分だけに色が塗られるように変更します。
    - プレイヤーが設定した、盤面サイズと勝利条件を画面右端に表示します。
```

</details>

#### 追加実装を含めたアプリのソースコード生成
先ほどと同様に、Copilot Chatにプロンプトとしてドキュメントを与えて、アプリのソースコードを生成してもらいます。
実際のソースコードは以下のとおりです。今回も、生成時点で構文エラーは1つもありませんでした。

<details>
<summary>生成されたソースコード (追加実装含)★</summary>

```python
import tkinter as tk
import tkinter.messagebox as msg
import tkinter.simpledialog as sd

# プレイヤーの情報
PLAYERS = [
    {"name": "", "stone": "red"},
    {"name": "", "stone": "orange"},
    {"name": "", "stone": "green"},
]

# 盤面のサイズと勝利条件
BOARD_SIZE = 0
WIN_CONDITION = 0

class GameBoard(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master, **kwargs)
        self.grid(sticky="nsew")
        self.create_widgets()

    def create_widgets(self):
        # プレイヤー名、盤面サイズ、勝利条件の入力
        for i, player in enumerate(PLAYERS):
            player["name"] = sd.askstring("プレイヤー名の入力", f"プレイヤー{i+1}の名前を入力してください")
        global BOARD_SIZE
        BOARD_SIZE = sd.askinteger("盤面サイズの入力", "盤面のサイズを入力してください")
        global WIN_CONDITION
        WIN_CONDITION = sd.askinteger("勝利条件の入力", "勝利条件（石を並べる数）を入力してください")

        # 盤面の作成
        self.cells = []
        for i in range(BOARD_SIZE):
            row = []
            for j in range(BOARD_SIZE):
                cell = tk.Button(self, text=" ", width=2, command=lambda x=i, y=j: self.put_stone(x, y))
                cell.grid(row=i, column=j)
                row.append(cell)
            self.cells.append(row)

        # リセットボタンの作成
        self.reset_button = tk.Button(self, text="リセット", command=self.reset)
        self.reset_button.grid(row=BOARD_SIZE, column=0, columnspan=BOARD_SIZE)

        # ゲームの初期化
        self.reset()

    def reset(self):
        # ゲームの状態の初期化
        self.board = [["" for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
        self.current_player = 0
        self.game_over = False

        # 盤面の初期化
        for i in range(BOARD_SIZE):
            for j in range(BOARD_SIZE):
                self.cells[i][j]["text"] = " "
                self.cells[i][j]["bg"] = "white"

        # プレイヤー情報の表示
        self.master.title(f"{PLAYERS[self.current_player]['name']} ({PLAYERS[self.current_player]['stone']}) の番")

    def put_stone(self, x, y):
        if self.game_over or self.board[x][y] != "":
            return

        # 石を置く
        self.board[x][y] = PLAYERS[self.current_player]["stone"]
        self.cells[x][y]["text"] = "●"
        self.cells[x][y]["bg"] = PLAYERS[self.current_player]["stone"]

        # 勝者がいるかチェック
        if self.check_winner(x, y):
            msg.showinfo("ゲーム終了", f"{PLAYERS[self.current_player]['name']} の勝利！")
            self.game_over = True
            self.reset()
            return

        # 次のプレイヤーへ
        self.current_player = (self.current_player + 1) % len(PLAYERS)
        self.master.title(f"{PLAYERS[self.current_player]['name']} ({PLAYERS[self.current_player]['stone']}) の番")

    def check_winner(self, x, y):
        # 勝者判定のロジック
        # 縦、横、斜めの各方向について、同じ色の石がWIN_CONDITION個並んでいるかをチェック
        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
        for dx, dy in directions:
            count = 0
            for i in range(-WIN_CONDITION+1, WIN_CONDITION):
                nx, ny = x + dx * i, y + dy * i
                if 0 <= nx < BOARD_SIZE and 0 <= ny < BOARD_SIZE and self.board[nx][ny] == PLAYERS[self.current_player]["stone"]:
                    count += 1
                    if count == WIN_CONDITION:
                        return True
                else:
                    count = 0
        return False

if __name__ == "__main__":
    root = tk.Tk()
    app = GameBoard(master=root)
    app.mainloop()
```
</details>

#### 再度、動作確認
追加の要件（自動リセット機能、プレイヤー名の入力機能、盤面サイズの指定機能、勝利条件の指定機能）が正しく実装されているかを確認します。
結果、追加要件が全て満たされており、**意図した「N目並べ」ゲームが完成しました！**

#### 画面キャプチャ

・対戦開始前にプレイヤー名の入力が可能になっており、

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/155b0e27-b83f-31cd-6c43-1d4c223d3111.png)　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/359d9614-960b-46c8-3589-b3358f0dbd81.png)　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/03c9e480-21be-f146-bf23-fc0e25bf5a38.png)

<br>

・盤面のサイズと、勝利条件も指定することができるようになっています。

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/1ed84645-22f3-f461-58cd-9c716ed92b9a.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/ca8f6061-9a2e-2d63-fbcf-6a5cbf3e8bb5.png)

<br>

・また、指定したサイズ (10×10) の盤面が正しく作成されており、

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/fcaaaf9b-64b7-60cf-2d6d-5115a3f10b91.png)

<br>

・指定した勝利条件 (3つの石を並べたら勝利) も正しく機能しています。
　
 　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/d8bd4b6c-0baa-8da0-8135-78e6c545d1e4.png)

<br>

・さらに、ゲーム終了後には自動で盤面がリセットされます。

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/0f98f6b8-578c-0df1-1508-25c396c0fa47.png)

::: note info
UI改善のうち「プレイヤーが設定した、盤面サイズと勝利条件を画面右端に表示する」部分は実装されていないのですが、この点についてはCopilot Chatが以下のように補足しているため、実装の不足ではないと判断します。
※tkinterではない別フレームワークを提案してもらい、アプリを書き換えてもらうことで、さらなるUIの改善も実装可能であることは確認していますが、本記事では割愛します。

Copilot Chatの回答
　・このコードは、要求された機能を全て実装しています。
　・ただし、盤面サイズと勝利条件を画面右端に表示する機能は、
　・tkinterの制約上、実装できません。
:::


## 2. 作成したアプリのソースコードから、設計書を生成する
「1」では、Markdown形式の設計書からアプリのソースコードを生成し、意図した「N目並べ」ゲームがCopilot Chatによって生成できることを確認しました。
ここでは、逆に**ソースコードからMarkdown形式の設計書を生成できるか**を試してみます。
「1」で作成した設計書と同じ見出しを与えて、各見出しの内容がどの程度再現できるか、換言するとCopilot Chatがソースコードの内容をどの程度理解し、ドキュメント化できるかを確認します。

### 設計書の生成のためのプロンプト
```
- Rokumoku_v2.pyのソースコード実装から、アプリケーションの設計書を作成してください。
- 設計書の見出しは、以下の4項目としてください。
    1. アプリケーションの概要
    2. 機能要件
    3. 非機能要件
    4. ユーザーインターフェース設計
```

:::note info
Copilot Chatは、IDE (本記事ではVS Codeを使用) で現在開いているソースコードをreferenceとして回答に使用することができます。
ここでは、作成した「N目並べ」アプリのソースコードを開いた状態でCopilot Chatを起動し、上記のプロンプトを与えることで、ソースコードを参照した上での回答を得ています。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/e28c8c64-7943-63dc-b77a-d47ff3b974b6.png)

### 生成された設計書
全体として、人間の手で作成したものとほとんど遜色ない内容の設計書が生成されました。
ごく簡単なアプリにおける一例ではありますが、Copilot Chatはソースコードの内容をもとに設計書のドキュメントを作成することもできるようです。
```
1. アプリケーションの概要
    - このアプリケーションは、3人で遊べる六目並べゲームです。
    - プレイヤーはそれぞれ異なる色の石を持ち、交互に盤面に石を置いていきます。
    - 先に指定した数の石を縦、横、または斜めに並べたプレイヤーが勝者となります。

2. 機能要件
    - プレイヤー名の入力: プレイヤーは自分の名前を入力できます。
    - 盤面サイズの設定: プレイヤーは盤面のサイズを設定できます。
    - 勝利条件の設定: プレイヤーは勝利条件（石を並べる数）を設定できます。
    - 石の配置: プレイヤーは盤面上の任意の位置に石を置くことができます。
    - 勝者の判定: 縦、横、斜めのいずれかの方向で指定した数の石が並んだ場合、そのプレイヤーが勝者となります。
    - ゲームのリセット: ユーザーはゲームをいつでもリセットできます。

3. 非機能要件
    - このアプリケーションはPythonのtkinterライブラリを使用して作成されています。
    - GUIは直感的で、ユーザーが容易に操作できるように設計されています。

4. ユーザーインターフェース設計
    - プレイヤー名、盤面サイズ、勝利条件の入力はダイアログボックスを使用して行います。
    - 盤面はボタンのグリッドとして表示され、プレイヤーは石を置きたいボタンをクリックして石を置きます。
    - 現在のプレイヤーの名前と石の色はウィンドウのタイトルバーに表示されます。
    - リセットボタンをクリックすると、ゲームが初期状態に戻ります。
    - 勝者が決まった場合、ダイアログボックスが表示されて勝者を通知します。
```

:::note info
今回は、設計書の作成が目的だったため、プロンプトを明示的に与えてドキュメントを生成しましたが、Copilot Chatには標準機能として、ソースコードの理解を助ける機能が存在します。
参考として、以下で簡単にご紹介します。
:::

・標準機能は、ソースコードを選択して「右クリック → Copilot」から利用することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/30a03d47-b667-c5ed-d19b-712c1f9b9eb1.png)

・ソースコードの説明：クラス、メソッド単位の処理内容についてCopilot Chatが説明してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/a54cfab2-2891-2539-5e92-5d59479d827e.png)

・ソースコードへのドキュメント追加：クラス、メソッド単位でソースコード中にコメントを追加します。ただし英語です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/dc8a2ff4-7905-390c-62b4-2f4c9d1a6a69.png)


## 3. 生成した設計書から、ゲームアプリを別言語で再作成(生成)する
「1」ではMarkdown形式のドキュメントからアプリのソースコードを作成し、「2」では逆にソースコードからMarkdown形式のドキュメントを生成しました。
最後に、ここでは「2」で生成した設計書をベースとして、**アプリをHTML・CSS・JavaScriptによる簡単なWebアプリにリライトできるか**を試します。

:::note info
背景の補足
「1」で作成したPythonのソースコードから、直接的にHTML・CSS・JavaScriptによるWebアプリにリライトする方法は上手くいかなかったため、ここでは「2」のプロセスで一度、ソースコードをドキュメント化した上でリライトを行っています。
~~ちなみに直接リライトすると、盤面が縦一列になったり、ずっと最初のプレイヤーのターンだったり、そもそも石が置けなかったりします。~~
:::


### 「2」で生成した設計書の一部修正
生成した設計書にはPythonを前提とした記載があるため、これを修正します。
また、記載に一部分かりにくい部分があるため、これも修正します。
(例えば、プレイヤー名の入力などを行うタイミングについての記載がない部分などを修正)

実際の修正後の設計書は、以下のとおりです。

<details>
<summary>修正後の設計書★</summary>

```
1. アプリケーションの概要
- このアプリケーションは、3人で遊べるN目並べゲームです。プレイヤーはそれぞれ異なる色の石を持ち、交互に盤面に石を置いていきます。先に指定した数の石を縦、横、または斜めに並べたプレイヤーが勝者となります。

2. 機能要件
    - プレイヤー名の入力: プレイヤーは対戦開始前に、自分の名前を入力できます。
    - 盤面サイズの設定: プレイヤーは対戦開始前に、盤面のサイズを設定できます。
    - 勝利条件の設定: プレイヤーは対戦開始前に、勝利条件（石を並べる数）を設定できます。
    - 石の配置: プレイヤーは盤面上の任意の位置に石を置くことができます。
    - 勝者の判定: 縦、横、斜めのいずれかの方向で指定した数の石が並んだ場合、そのプレイヤーが勝者となります。
    - ゲームのリセット: ユーザーはゲームをいつでもリセットできます。

3. 非機能要件
    - このアプリケーションはHTML、CSS、JavaScriptを使用して作成します。
    - GUIは直感的で、ユーザーが容易に操作できるように設計します。

4. ユーザーインターフェース設計
    - プレイヤー名、盤面サイズ、勝利条件の入力はダイアログボックスを使用して行います。
    - 盤面はボタンのグリッドとして表示され、プレイヤーは石を置きたいボタンをクリックして石を置きます。
    - 現在のプレイヤーの名前と石の色はウィンドウのタイトルバーに表示されます。
    - リセットボタンをクリックすると、ゲームが初期状態に戻ります。
    - 勝者が決まった場合、ダイアログボックスが表示されて勝者を通知します。
```

</details>

### アプリのソースコード生成
これまでと同様、作成した設計書をCopilot Chatにプロンプトとして与えて、アプリのソースコードを生成してもらいます。

実際に生成されたソースコードは以下のとおりです。

<details>
<summary>生成されたソースコード★</summary>

```html
<!DOCTYPE html>
<html>
<head>
    <style>
        #board button {
            width: 50px;
            height: 50px;
        }
    </style>
</head>
<body>
    <h1 id="title">Player 1's Turn</h1>
    <div id="board"></div>
    <button onclick="resetGame()">Reset</button>

    <script>
        var boardSize = prompt("Enter board size");
        var winCondition = prompt("Enter win condition");
        var players = [prompt("Enter player 1 name"), prompt("Enter player 2 name"), prompt("Enter player 3 name")];
        var board = [];
        var currentPlayer = 0;

        function createBoard() {
            var boardElement = document.getElementById("board");
            for (var i = 0; i < boardSize; i++) {
                var row = [];
                for (var j = 0; j < boardSize; j++) {
                    var button = document.createElement("button");
                    button.onclick = (function(i, j) {
                        return function() {
                            makeMove(i, j);
                        };
                    })(i, j);
                    boardElement.appendChild(button);
                    row.push(button);
                }
                boardElement.appendChild(document.createElement("br"));
                board.push(row);
            }
        }

        function makeMove(i, j) {
            if (board[i][j].textContent == "") {
                board[i][j].textContent = currentPlayer + 1;
                if (checkWin(i, j)) {
                    alert(players[currentPlayer] + " wins!");
                    resetGame();
                } else {
                    currentPlayer = (currentPlayer + 1) % players.length;
                    document.getElementById("title").textContent = players[currentPlayer] + "'s Turn";
                }
            }
        }

        function checkWin(i, j) {
            // Check row, column, and diagonals for win condition
            // This is a simplified check and may not work for all cases
            var row = board[i];
            var column = board.map(function(row) { return row[j]; });
            var diagonal1 = board.filter(function(row, index) { return index == j; });
            var diagonal2 = board.filter(function(row, index) { return index == boardSize - j - 1; });
            return checkLine(row) || checkLine(column) || checkLine(diagonal1) || checkLine(diagonal2);
        }

        function checkLine(line) {
            var count = 0;
            for (var i = 0; i < line.length; i++) {
                if (line[i].textContent == currentPlayer + 1) {
                    count++;
                    if (count == winCondition) {
                        return true;
                    }
                } else {
                    count = 0;
                }
            }
            return false;
        }

        function resetGame() {
            currentPlayer = 0;
            document.getElementById("title").textContent = players[currentPlayer] + "'s Turn";
            board.forEach(function(row) {
                row.forEach(function(button) {
                    button.textContent = "";
                });
            });
        }

        createBoard();
    </script>
</body>
</html>
```

</details>

### 動作確認
生成されたHTML、CSS、JavaScriptによるアプリが、期待通りに動作するかを確認します。
結果、勝利条件 (斜めに石を並べた場合に勝利判定されない) やUIの一部に不備がありました。

これを修正して「Webアプリ版N目並べ」を完成させていきます。

### 画面キャプチャ
・盤面サイズの指定、勝利条件の指定と、

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/9c28483e-c2ff-9d06-55b8-bdbe91c6bd74.png" width=45%>　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/84630259-4b14-c812-e83c-53d788e9ad8a.png" width=45%>

<br>

・プレイヤー名の入力は正しく実装されています。

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/eb49c5cc-9c04-96a2-837c-05240d7f2467.png" width=45%>

 <br>
 
・ただし、対戦開始時の盤面で入力したプレイヤー名 (sapporoと入力しました) が反映されていなかったり、
 
 　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/a0362124-fe30-8b1c-ff2c-1b9a61ff9606.png" width=50%>

<br>

・斜めに石を並べた時の勝利条件が正しく実装されていないようです。またUIも石を置いているように見えず、クリックするとマス目が下方向に移動してしまうなどイマイチです。

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/82843138-6867-bc3e-0760-08f011445ed8.png" width=50%>


### 実装の修正に関するドキュメントの作成
アプリの修正要件をドキュメントにまとめます。
```
# アプリケーションの修正要件

## 1. 勝利条件の修正
- 勝利条件を、以下の要件を満たすように修正してください。
    - 勝者の判定: 縦、横だけでなく、斜めにも指定した数の石が並んだ場合、そのプレイヤーを勝者とします。

## 2. UIの改善
- UIを、以下の要件を満たすように修正してください。
    - 対戦開始時、画面上部に実際に入力したプレイヤー名が表示されるようにします。
    - プレイヤーが使用する石の色は、赤色、オレンジ色、緑色とします。
    - 石の形を丸形に変更し、マス目のうち、石の部分だけに色が塗られるように変更します。
    - 現在、プレイヤーが石を置いた際に、石が下方向に動いてしまいます。これを動かないように修正します。
```

### 修正アプリのソースコード生成
これまでと同様に、作成したドキュメントをCopilot Chatに与えて、実装を修正してもらいます。
実際のソースコードは以下のとおりです。

<details>
<summary>修正後のソースコード★</summary>

```html
<!DOCTYPE html>
<html>
<head>
    <style>
        #board button {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            vertical-align: middle;
        }
        .player1 {
            background-color: red;
        }
        .player2 {
            background-color: orange;
        }
        .player3 {
            background-color: green;
        }
        #settings {
            position: absolute;
            right: 0;
        }
    </style>
</head>
<body>
    <h1 id="title">Player 1's Turn</h1>
    <div id="board"></div>
    <button onclick="resetGame()">Reset</button>
    <div id="settings"></div>

    <script>
        var boardSize = prompt("Enter board size");
        var winCondition = prompt("Enter win condition");
        var players = [prompt("Enter player 1 name"), prompt("Enter player 2 name"), prompt("Enter player 3 name")];
        var board = [];
        var currentPlayer = 0;

        function createBoard() {
            var boardElement = document.getElementById("board");
            for (var i = 0; i < boardSize; i++) {
                var row = [];
                for (var j = 0; j < boardSize; j++) {
                    var button = document.createElement("button");
                    button.onclick = (function(i, j) {
                        return function() {
                            makeMove(i, j);
                        };
                    })(i, j);
                    boardElement.appendChild(button);
                    row.push(button);
                }
                boardElement.appendChild(document.createElement("br"));
                board.push(row);
            }
            document.getElementById("settings").textContent = "Board size: " + boardSize + ", Win condition: " + winCondition;
            document.getElementById("title").textContent = players[currentPlayer] + "'s Turn";
        }

        function makeMove(i, j) {
            if (board[i][j].textContent == "") {
                board[i][j].textContent = currentPlayer + 1;
                board[i][j].className = "player" + (currentPlayer + 1);
                if (checkWin(i, j)) {
                    alert(players[currentPlayer] + " wins!");
                    resetGame();
                } else {
                    currentPlayer = (currentPlayer + 1) % players.length;
                    document.getElementById("title").textContent = players[currentPlayer] + "'s Turn";
                }
            }
        }

        function checkWin(i, j) {
            var row = board[i];
            var column = board.map(function(row) { return row[j]; });
            var diagonal1 = [];
            var diagonal2 = [];

            var k = j - i;
            for (var x = Math.max(0, k); x < Math.min(boardSize, boardSize + k); x++) {
                diagonal1.push(board[x][x - k]);
            }

            k = j + i;
            for (var x = Math.max(0, k - boardSize + 1); x < Math.min(boardSize, k + 1); x++) {
                diagonal2.push(board[x][k - x]);
            }

            return checkLine(row) || checkLine(column) || checkLine(diagonal1) || checkLine(diagonal2);
        }

        function checkLine(line) {
            var count = 0;
            for (var i = 0; i < line.length; i++) {
                if (line[i].textContent == currentPlayer + 1) {
                    count++;
                    if (count == winCondition) {
                        return true;
                    }
                } else {
                    count = 0;
                }
            }
            return false;
        }

        function resetGame() {
            currentPlayer = 0;
            document.getElementById("title").textContent = players[currentPlayer] + "'s Turn";
            board.forEach(function(row) {
                row.forEach(function(button) {
                    button.textContent = "";
                    button.className = "";
                });
            });
        }

        createBoard();
    </script>
</body>
</html>

```
</details>

### 再度、動作確認
アプリの修正要件が正しく実装されているかを確認します。
結果、追加要件が全て満たされており、意図した「Web版N目並べ」ゲームが完成しました！

### 画面キャプチャ
・対戦開始時、実際に入力したプレイヤー名が表示されるようになり、

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/48c4fe66-a2ef-84ca-9ad6-8d4daa50e723.png" width=70%>

<br>

・勝利条件も正しく修正されました。またUIも修正要件どおりに改善されています。

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588921/d3c51cbc-4d0f-e43f-628c-8401a3d386db.png" width=70%>


# おわりに
本記事では、Copilot Chatを使用して、以下の手順でアプリの作成と別言語でのリライトを試してみました。
1. Markdown形式のアプリ設計書から、ソースコードを生成する
2. 「1」で生成したソースコードから、Markdown形式の設計書を生成する
3. 「2」で生成した設計書をベースに「1」で作成したアプリを別言語でリライトする最近は

結果として、ごく簡単なアプリであれば、設計書からのソースコード生成、ソースコードからの設計書生成、ソースコードの別言語によるリライトが可能であることが分かりました。

当然ながら結果は参考程度ですが、本記事がGitHub Copilot Chat、あるいは、いわゆるAIコーディングツールで何ができるか？の理解の一助になれば幸いです。