【Elixir/Phoenix】AI対戦4目並べを作る（GUI編）

# はじめに

Elixir/Phoenixを使って、AIと対戦できる4目並べを作成してみました。
その１では、人対人で4目並べのゲームができるものを作成します。

# ゲームのボードの表示方法

ゲームのボードは、キャンバスを使って自力で書く方法もありますが、以前オセロやマインスイーパーを作った時に、CSSのGridを使うと描画部分をいい感じにHTMLに任せられて楽だったので、この方法で作成してみます。
HTMLとCSSはつぎのような感じです


## ボードのHTML

grid-template-columns, grid-teplate-rowsで正方形を5×5個並べています
各マスはマスの状態に応じたclassを割り当てています。
石を置いたり、ターンが変わるごとにこのclassの値を書き換えています。
この例は、(1,1)に白石があり、黒の手番の時です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/69251c87-fef5-3730-b997-a9137611784c.png)

※各マスのdivは実際は長いので、(1,4)の10個分までになってますが、実際は25個あります。


## CSS

|class|役割|
|-----|----|
|cell      |すべてのマスに適用。色などを決める|
|turn-black|マウスがおかれたら黒石を表示|
|turn-white|マウスがおかれたら白石を表示|
|black|白石を表示|
|white|黒石を表示|


```css:assets/css/gomoku.css
.cell {
    border: 1px solid black;
    background: rgb(91, 184, 37);
}
.turn-white:hover {
    background: rgb(104, 231, 30);
    background-position: center;
    background-image: url("../images/white.svg");
}

.turn-black:hover {
    background: rgb(104, 231, 30);
    background-position: center;
    background-image: url("../images/black.svg");
}

.cell.white {
    background-position: center;
    background-image: url("../images/white.svg");
}

.cell.black {
    background-image: url("../images/black.svg");
}
```

## 画面表示

マウスをあてたセルがハイライトされ、自分の色の石が表示されます。
マウスを移動させると、石も連動して移動して表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/b64a6793-1898-4bb3-5926-23ad45af932a.png)

※マウスポインターがキャプチャできてませんが、左上の黒石にポインターがあります。

# LiveViewでの表示

手番が変わる度に、HTMLを書き換えます。書き換える部分は、各マスに対応するdivのclassの値です。
classの値は、前述の石の状態により決まるclassです。このほかに、
セルをクリックした時に、セルの場所をイベントハンドラーに伝えるために、phx-value-x、phx-value-yの値を埋め込んでいます。

```Elixir
      <%= for {y, x, class_string} <- create_elements(@board) do %>
        <div class={class_string} phx-click="clicked" phx-value-x={x} phx-value-y={y}></div>
      <% end %>
```

render関数全体は、次の通りになります。

```Elixir:lib/phx_gomoku_web/live/gomoku_live.ex
  def render(assigns) do
    ~H"""
    <div style={"display: grid;
                grid-template-columns: repeat(#{@board.size},#{@width/@board.size}px);
                grid-template-rows: repeat(#{@board.size},#{@width/@board.size}px);
                width: #{@width}px;
                height: #{@width}px;"}>
      <%= for {y, x, class_string} <- create_elements(@board) do %>
        <div class={class_string} phx-click="clicked" phx-value-x={x} phx-value-y={y}></div>
      <% end %>
    </div>
    <%= if @board.done do %>
      <.button phx-click="new_game">New Game</.button>
    <% end %>
    """
  end
```

#　ゲームの進行方法




# 石を置く処理

マス目をクリックすると、"clicked"のhandle_eventが呼び出されます。
:boardには、ゲームの状態が保存されているので、この値を、
Gomokuモジュールのputで更新します。

```Elixir:lib/phx_gomoku_web/live/gomoku_live.ex
  def handle_event("clicked", %{"x" => x, "y" => y}, socket) do
    x = String.to_integer(x)
    y = String.to_integer(y)
    {:noreply, update(socket, :board, &Gomoku.put!(&1, {y, x}))}
  end
```

# ゲームのロジック

LiveViewを使って、画面表示部分はできあがりました。Gomokuモジュールにゲームのロジックを記述します。

put!()では、
boardの{y,x}キーの値を自分の手番の番号に更新
update_done()でゲームオーバかどうかの確認をして、手番の更新をしています。
すでに石が置かれている場所に石を置くと、ゲームオーバとなるロジックになっています。これは、このGomokuモジュールは、ＡＩの学習にも使用していて、おける場所に置いた場合は、反則負けとする必要があったので、その関係でこうなってます。

```Elixir
  def put!(%Gomoku{board: board, turn: turn} = gomoku, {y, x}) do
    case board[{y, x}] do
      0 ->
        gomoku
        |> struct(board: %{board | {y, x} => turn})
        |> update_done({y, x})
        |> turn_player()

      _ ->
        struct(gomoku, miss: true, done: true)
    end
  end
```

# ゲームロジックの動作確認


newを実行すると、なにも石の置かれていない初期状態のボードの%Gomoku{}を返します。

```elixir
Gomoku.new(5, 4)
```

<!-- livebook:{"output":true} -->

```
%Gomoku{
  board: %{
    {0, 0} => 0,
    {0, 1} => 0,
    {0, 2} => 0,
    {0, 3} => 0,
    {0, 4} => 0,
    {1, 0} => 0,
    {1, 1} => 0,
    {1, 2} => 0,
    {1, 3} => 0,
    {1, 4} => 0,
    {2, 0} => 0,
    {2, 1} => 0,
    {2, 2} => 0,
    {2, 3} => 0,
    {2, 4} => 0,
    {3, 0} => 0,
    {3, 1} => 0,
    {3, 2} => 0,
    {3, 3} => 0,
    {3, 4} => 0,
    {4, 0} => 0,
    {4, 1} => 0,
    {4, 2} => 0,
    {4, 3} => 0,
    {4, 4} => 0
  },
  size: 5,
  turn: 1,
  done: false,
  draw: false,
  miss: false,
  k: 4
}
```

put!()を使って石を置く操作ができます。(1,1)に白（先手）と(2,2)に黒を置いた状態を作ってみます。
でバック用に、render()を使うとテキストで盤面を表示できるようにしてみました。

```elixir
Gomoku.new(5, 4)
|> Gomoku.put!({1, 1})
|> Gomoku.put!({2, 2})
|> Gomoku.render()
|> IO.puts()
```

<!-- livebook:{"output":true} -->

```
   abc
1: -----
2: -○---
3: --●--
4: -----
5: -----

```

# まとめ

- 盤面の表示は、HTML/CSS、五目並べのゲームロジックはGomokuモジュールに分割して作成した。
- 組み合わせることで、人対人の対戦ができる状態になった。

[【Elixir/Phoenix】対戦4目並べを作る（強化学習編）](https://qiita.com/GeekMasahiro/private/68396a71aa9408a1c485)では、強化学習によりAIの対戦相手を作成する部分を説明します。

# 関連記事

https://qiita.com/GeekMasahiro/items/0cc62af5bcc67cbb2b3e

https://qiita.com/GeekMasahiro/items/68396a71aa9408a1c485

https://qiita.com/GeekMasahiro/items/09c96ccb8da13e445d70

https://qiita.com/GeekMasahiro/items/603f1f845018988ec7a7