Current Sprint: 3. 遊戲基本流程完成
repo: https://github.com/side-project-at-SPT/ithome-ironman-2024-san-juan
swagger docs: https://side-project-at-spt.github.io/ithome-ironman-2024-san-juan/
Games::PlayerCommand // 玩家行動Games::ChooseRoleCommand < PlayerCommand // 選擇職業行動礦工 階段行動議員 階段行動建築 階段行動生產 階段行動交易 階段行動assign 選擇職業 請求時choose_role_command
choose_role_command
post action(s)
choose_role_command
# app/commands/games/choose_role_command.rb
module Games
  class ChooseRoleCommand < PlayerCommand
    attr_reader :role
    def initialize(params = {})
      super(params)
      @role = validate_role!(params[:role])
    end
    def call
      if errors.any?
        game.errors.add(:base, errors.full_messages.join(", "))
        return game
      end
      role_is_taken?(role, game.game_data["roles"]) do
        errors.add(:role, "#{role.demodulize} is being taken") and return self
      end
      game.game_data["roles"].delete(role)
      game.game_data["players"][game.game_data["current_player_index"]]["role"] = role
      game.save
      # 判斷下一個動作要做什麼
      case role
      when Roles::Prospector.to_s
        # 判斷玩家是否有金礦或金工坊
        # 如果有,則讓玩家選擇要先執行哪個動作
        # 如果沒有,則直接執行礦工的動作
        player_buildings = game.game_data["players"][game.game_data["current_player_index"]]["buildings"]
        # if player_buildings.any? { |building| building["id"] == Cards::GoldMine.id || building["id"] == Cards::GoldSmithy.id }
        if player_buildings.any? { |building| building["id"] == "07" || building["id"] == "38" }
          # TODO: implement this
          puts "TODO: implement this (choose action)"
        else
          # 從牌庫抽取一張卡片
          @post_action = [
            Games::DrawCommand,
            {
              player_id: player.id,
              number: 1,
              description: "選擇礦工玩家抽一張卡片"
            }
          ]
        end
      else
        pp "Unimplemented role: #{role}"
      end
      self
    end
    class InvalidRoleError < RuntimeError
      def initialize(invalid_role = nil)
        @invalid_role = invalid_role
      end
      def message
        "Invalid Role: #{@invalid_role}"
      end
    end
    private
    def role_is_taken?(role, roles, &block)
      unless role.in? roles
        if block_given?
          block.call
        else
          raise InvalidRoleError.new(role)
        end
      end
    end
    def validate_role!(params_role = nil)
      if (role = find_role(params_role))
        role
      else
        errors.add(:role, "#{params_role} is invalid")
        nil
      end
    end
    def find_role(value)
      return nil unless value
      case value.to_s.downcase
      when "1", "建築師", "builder"
        Roles::Builder.to_s
      when "2", "製造商", "producer"
        Roles::Producer.to_s
      when "3", "貿易商", "trader"
        Roles::Trader.to_s
      when "4", "礦工", "prospector"
        Roles::Prospector.to_s
      when "5", "議員", "councillor"
        Roles::Councillor.to_s
      else
        nil
      end
    end
  end
end
在 game 新增 build_assign_role_command 實體方法,產生 assign action 需要的 command
# app/models/game.rb
class Game < ApplicationRecord
# ...
  def build_assign_role_command(**params)
    command_builder(Games::ChooseRoleCommand, params.merge(description: "選擇職業"))
  end
  
  private
  
  # 產生任意 command
  # @param command [Class] command class
  # @param params [Hash] command parameters
  # @return [Games::PlayerCommand] command instance
  def command_builder(command, params)
    command.new(params.merge(game: self))
  end
end
# app/controllers/api/v1/games_controller.rb
class Api::V1::GamesController < ApplicationController
# ...
  def assign
    # ...
    command = @game.build_assign_role_command(role: params[:role], player: @current_player)
    if command.errors.any?
      return render status: :bad_request, json: { error: command.errors.full_messages }
    end
    # ...
  end
# ...
end
choose_role_command,檢查是否執行成功# app/controllers/api/v1/games_controller.rb
    result = command.call
    if result.errors.any?
      return render status: :unprocessable_entity, json: { error: result.errors.full_messages }
    end
post action(s)
執行接下來不需要玩家選擇,而可以自動執行的動作
# app/controllers/api/v1/games_controller.rb
    # resolve the rest of the action can be done automatically
    result.resolve_post_action(game: @game)
# app/controllers/api/v1/games_controller.rb
    # notify the next player to take action
    # TODO: implement this
    @game.notify_next_turn
新增 show action
新增 show.json.jbuilder
# app/controllers/api/v1/games_controller.rb
  def show; end
  def assign
    # ...
    @message = "你選擇了: #{params[:role]}"
    render :show
  end
# app/views/api/v1/games/show.json.jbuilder
json.ignore_nil!
json.partial! "api/v1/games/game", game: @game
json.message @message
rails rswag # 更新 swagger
建立遊戲
curl -X 'POST' \
  'http://localhost:3000/api/v1/games' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "seed": "1234567890abcdef"
}'
回應
{
  "id": 7,
  "status": "playing",
  "game_config": {
    "seed": "1234567890abcdef"
  },
  "game_data": {
  // 略
選擇職業(礦工)
curl -X 'POST' \
  'http://localhost:3000/api/v1/games/7/roles/%E7%A4%A6%E5%B7%A5' \
  -H 'accept: application/json' \
  -d ''
回應
{
  "id": 7,
  "status": "playing",
  "game_config": {
    "seed": "1234567890abcdef"
  },
  "game_data": {
    "current_price": [
      1,
      2,
      2,
      2,
      3
    ],
    "supply_pile": ["00",...],
    "current_player_index": 0,
    "roles": [
      "Games::Roles::Builder",
      "Games::Roles::Producer",
      "Games::Roles::Trader",
      "Games::Roles::Councillor"
    ],
    "players": [
      {
        "id": 1,
        "hand": [
          "00",
          "00",
          "00",
          "00",
          "00"
        ],
        "buildings": [
          {
            "id": "01"
          }
        ]
      },
      {
        "id": 2,
        "hand": [
          "00",
          "00",
          "00",
          "00"
        ],
        "buildings": [
          {
            "id": "01"
          }
        ]
      },
      {
        "id": 4,
        "hand": [
          "01",
          "00",
          "00",
          "00"
        ],
        "buildings": [
          {
            "id": "01"
          }
        ]
      },
      {
        "id": 3,
        "hand": [
          "00",
          "00",
          "00",
          "00"
        ],
        "buildings": [
          {
            "id": "01"
          }
        ]
      }
    ]
  },
  "message": "你選擇了: 礦工"
}
收工
step model 用來儲存遊戲(每一步)紀錄phase,用來描述目前是哪個職業階段礦工 階段行動議員 階段行動建築 階段行動生產 階段行動交易 階段行動step model 用來儲存遊戲(每一步)紀錄phase,用來描述目前是哪個職業階段以上不代表明天會做,如有雷同純屬巧合
SPT (Side Project Taiwan) 的宗旨是藉由Side Project開發來成就自我,透過持續學習和合作,共同推動技術和專業的發展。我們相信每一個參與者,無論是什麼專業,都能在這個社群中找到屬於自己的成長空間。
歡迎所有對Side Project開發有興趣的人加入我們,可以是有點子來找夥伴,也可以是來尋找有興趣的Side Project加入,邀請大家一同打造一個充滿活力且有意義的技術社群!
Discord頻道連結: https://sideproj.tw/dc