5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

5
Last updated at Posted at 2023-07-23

はじめに

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

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

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

ボードのHTML

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

image.png

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

CSS

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

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

LiveViewでの表示

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

      <%= 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関数全体は、次の通りになります。

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で更新します。

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モジュールは、AIの学習にも使用していて、おける場所に置いた場合は、反則負けとする必要があったので、その関係でこうなってます。

  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{}を返します。

Gomoku.new(5, 4)
%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()を使うとテキストで盤面を表示できるようにしてみました。

Gomoku.new(5, 4)
|> Gomoku.put!({1, 1})
|> Gomoku.put!({2, 2})
|> Gomoku.render()
|> IO.puts()
   abc
1: -----
2: -○---
3: --●--
4: -----
5: -----

まとめ

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

【Elixir/Phoenix】対戦4目並べを作る(強化学習編)では、強化学習によりAIの対戦相手を作成する部分を説明します。

関連記事

5
3
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?