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