はじめに
Kiroで仕様駆動開発をしてみたら、完成度が高かったので、Elixirのプログラムも作ってみました。
リバーシを作ってみます。
要求仕様作成
次の入力をして要求仕様を作成してもらいました。
リバーシをElixirでつくります。対戦型にします。
最初は二人の人が対戦するものにします。WSLの環境でwslgが使える前提で動作するものにします。
作成されたrequirmentsは、リバーシのルール説明そのものでした。
特に修正する事はありませんでした。
設計
設計を進めるように伝えると、調査をはじめて、次のような提案がありました。
調査結果から、WSLg環境でElixirのGUIアプリケーションを作成する方法として、以下の2つのアプローチが考えられます:
Phoenix LiveView(Webベース): ブラウザベースのインターフェースで、WSLgとの互換性が高く、実装が容易
Scenic(ネイティブGUI): ネイティブGUIフレームワークだが、WSL環境でのセットアップが複雑
Phoenix LiveViewの方がWSLg環境での動作が確実で、実装も簡単なため、こちらを採用します。
それでは設計書を作成します。
このあと作成された設計書は、問題なさそうなものが作成されました。
ElirixとPhoenixのバージョン指定の範囲が広かったので、Elixirのバージョンは、1.9 Phoenix 1.8.3を使用してとコメントして、変更してもらいました。
設計書の一部を紹介します
2. Game.Board モジュール
ゲームのコアロジックを実装するモジュールです。
データ構造
defmodule Game.Board do
@type player :: :black | :white
@type cell :: player() | nil
@type position :: {row :: 0..7, col :: 0..7}
@type t :: %__MODULE__{
cells: [[cell()]], # 8x8の盤面(リストのリスト)
current_player: player(), # 現在のプレイヤー
black_count: non_neg_integer(), # 黒の駒数
white_count: non_neg_integer(), # 白の駒数
pass_count: 0..2 # 連続パス回数
}
end
主要な関数
# 新しいゲームを初期化
new() :: t()
# 指定位置に駒を配置(検証含む)
place_piece(board :: t(), position :: position()) ::
{:ok, t()} | {:error, :invalid_move | :occupied}
# 指定位置が有効な手かチェック
valid_move?(board :: t(), position :: position()) :: boolean()
# 指定方向で反転される駒のリストを取得
pieces_to_flip(board :: t(), position :: position(), direction :: {integer(), integer()}) ::
[position()]
# すべての方向で反転される駒を取得
all_pieces_to_flip(board :: t(), position :: position()) :: [position()]
# 駒を反転
flip_pieces(board :: t(), positions :: [position()]) :: t()
# 現在のプレイヤーに有効な手があるかチェック
has_valid_moves?(board :: t()) :: boolean()
# ターンをパス
pass_turn(board :: t()) :: t()
# ゲームが終了しているかチェック
game_over?(board :: t()) :: boolean()
# 勝者を判定
winner(board :: t()) :: player() | :tie
# 盤面の特定位置のセルを取得
get_cell(board :: t(), position :: position()) :: cell()
# 駒数を更新
update_counts(board :: t()) :: t()
3. テンプレート (game_live.html.heex)
HEExテンプレートでUIを定義します。
構造
<div class="game-container">
<!-- ゲーム情報 -->
<div class="game-info">
<h1>Othello</h1>
<div class="current-player">
現在のプレイヤー: <%= if @board.current_player == :black, do: "黒", else: "白" %>
</div>
<div class="score">
黒: <%= @board.black_count %> | 白: <%= @board.white_count %>
</div>
<%= if @message do %>
<div class="message"><%= @message %></div>
<% end %>
</div>
<!-- 盤面 -->
<div class="board">
<%= for row <- 0..7 do %>
<div class="board-row">
<%= for col <- 0..7 do %>
<button
class={"cell #{cell_class(@board, {row, col})}"}
phx-click="place_piece"
phx-value-row={row}
phx-value-col={col}
disabled={@game_over}>
<%= render_piece(@board, {row, col}) %>
</button>
<% end %>
</div>
<% end %>
</div>
<!-- ゲーム終了時の表示 -->
<%= if @game_over do %>
<div class="game-over">
<h2>ゲーム終了</h2>
<p><%= game_result(@board) %></p>
<button phx-click="new_game">新しいゲーム</button>
</div>
<% end %>
</div>
データモデル
盤面の表現
盤面は8x8のリストのリストとして表現されます:
# 初期状態の例
[
[nil, nil, nil, nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil, nil, nil, nil],
[nil, nil, nil, :white, :black, nil, nil, nil],
[nil, nil, nil, :black, :white, nil, nil, nil],
[nil, nil, nil, nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil, nil, nil, nil],
[nil, nil, nil, nil, nil, nil, nil, nil]
]
かなり詳細で、内容も正しそう。特に突っ込めるような点はありませんでした。
タスク
作成されました。
すべてのタスクを必須にする を選択して進めてみます。
主要なタスクだけ作るのもありですが、全部進めても手間はあまりかわらない気がするので、こっちにしてみます。
タスクの1から順にクリックして作成していきます。
Phoenixの環境作成
Phoenixのインストールが開始されました
mix archive.install hex phx_new 1.8.3 --force
この後、mix phx.newを実行してプロジェクトが作成されました。
task 2以降も順に実行していきます。
2. Game.Boardモジュールの実装 に進みます。
3. ターン管理とゲーム終了ロジックの実装
4. チェックポイント - コアロジックの確認
5. GameLive LiveViewモジュールの実装
6. UIテンプレートの実装
7. CSSスタイリングの実装
8. ルーティングと統合
9. 最終チェックポイント
いずれのタスクも特に何も補佐する必要はなく完了していきました。
念のため、タスク完了時にコミットして保存して、各タスクでの対応を記録しながら見守ります。
実行
mix phx.server
実行して、ブラウザーで開いてみました。
黒が先行で、クリックすると駒を置くことができ、間の白が黒になりました。
最後まで進める事ができました。
まとめ
- kiroを使い、リバーシを作ることができた
- 仕様や設計、タスクは生成されたものがそのまま利用できた
- テストプログラムによる確認も実施されていた
- 作成したプログラムは想定通りの動作だった
- Phoenixのlive Viewを生かしたプログラムとなっていた
- きれいな構造で作られてるので、コードを読んでみたいと思います
- Elixir Desktopで動くようにもしてみようとおもいます


