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
用來處理玩家發動的行動# app/commands/games/player_command.rb
module Games
class PlayerCommand
include ActiveModel::Validations
attr_reader :game, :player
# 1. 遊戲是否存在
# 2. 玩家是否存在
# 3. 是否輪到玩家
# 4. 遊戲是否正在進行
validate :game_exists?, :player_exists?, :player_turn?, :status_playing?
def initialize(params = {})
@game = Game.find(params[:game_id])
@player = User.new(params[:player_id])
validate!
end
private
def game_exists?
errors.add(:game, "not found") unless game.present?
end
def player_exists?
errors.add(:player, "not found") unless player.present?
end
def player_turn?
errors.add(:player, "not your turn") unless game&.current_player_id == player&.id
end
def status_playing?
errors.add(:status, "is not playing") unless game&.status_playing?
end
end
class User
attr_reader :id
def initialize(id)
@id = id
end
end
end
Games::ChooseRoleCommand
用來處理 玩家::選擇職業 行動# 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
role.in? game.game_data["roles"]
game.game_data["roles"].delete(role)
game.game_data["players"][game.game_data["current_player_index"]]["role"] = role
game.save
end
class InvalidRoleError < RuntimeError
def initialize(invalid_role = nil)
@invalid_role = invalid_role
end
def message
"Invalid Role: #{@invalid_role}"
end
end
private
def validate_role!(params_role = nil)
raise InvalidRoleError.new(params_role) unless (role = find_role(params_role))
role
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
Games
儲存用到的共同常數、方法# app/commands/games.rb
module Games
module Roles
class Builder; end
class Producer; end
class Trader; end
class Prospector; end
class Councillor; end
All = [ Builder, Producer, Trader, Prospector, Councillor ].map(&:to_s).freeze
end
end
config/application.rb
,預先載入 app/commands
的元件module IthomeIronman2024SanJuan
class Application < Rails::Application
# ...
config.autoload_lib(ignore: %w[assets tasks])
config.autoload_paths << Rails.root.join("app/commands")
# ...
end
end
Game
,加上 restart
實體方法,方便測試用# app/models/game.rb
class Game < ApplicationRecord
# ...
def restart
status_playing!
save
self.class.start_new_game(seed: seed, game: self)
end
def current_player_id
return nil unless game_data["players"]
players[game_data["current_player_index"]]["id"]
end
class << self
def generate_seed = SecureRandom.hex(16)
def start_new_game(seed: nil, game: nil)
game ||= new(status: :playing)
# ...
# 6. prepare role cards
roles = Games::Roles::All
# save the game data
game.game_data[:players] = players
game.game_data[:supply_pile] = deck
game.game_data[:roles] = roles
game.save
game
end
# ...
end
end
重置一場遊戲
rails r 'Game.find(6).restart'
目前遊戲狀態
rails r 'Game.find(6)'
# #<Game:0x00000001249d7068
# id: 6,
# status: "playing",
# created_at: "2024-09-20 23:12:50.448342000 +0800",
# updated_at: "2024-09-21 22:33:59.765578000 +0800",
# seed: "4eea1f3d277836d3aad4766baa8483e1",
# version: nil,
# game_data:
# {"players"=>[{"id"=>2, "hand"=>["00", "01", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]}, {"id"=>4, "hand"=>["01", "00", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]}, {"id"=>3, "hand"=>["00", "00", "01", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]}, {"id"=>1, "hand"=>["01", "00", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]}], "current_player_index"=>0, "trading_house_order"=>[3, 1, 0, 2, 4], "supply_pile"=>["00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "01", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "01", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", "00"], "roles"=>["Games::Roles::Builder", "Games::Roles::Producer", "Games::Roles::Trader", "Games::Roles::Prospector", "Games::Roles::Councillor"]},
# result: {}>
玩家選擇礦工
rails r 'Games::ChooseRoleCommand.new(game_id:6,player_id:2,role:"礦工")'
# then check result
rails r 'pp Game.find(6).game_data["players"]'
# [{"id"=>2, "hand"=>["00", "01", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}], "role"=>"Games::Roles::Prospector"},
# {"id"=>4, "hand"=>["01", "00", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]},
# {"id"=>3, "hand"=>["00", "00", "01", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]},
# {"id"=>1, "hand"=>["01", "00", "00", "00"], "buildings"=>[{"id"=>"01", "good_id"=>nil, "card_ids"=>nil}]}]
rails r 'pp Game.find(6).game_data["roles"]'
# ["Games::Roles::Builder", "Games::Roles::Producer", "Games::Roles::Trader", "Games::Roles::Councillor"]
礦工
階段行動議員
階段行動建築
階段行動生產
階段行動交易
階段行動以上不代表明天會做,如有雷同純屬巧合
SPT (Side Project Taiwan) 的宗旨是藉由Side Project開發來成就自我,透過持續學習和合作,共同推動技術和專業的發展。我們相信每一個參與者,無論是什麼專業,都能在這個社群中找到屬於自己的成長空間。
歡迎所有對Side Project開發有興趣的人加入我們,可以是有點子來找夥伴,也可以是來尋找有興趣的Side Project加入,邀請大家一同打造一個充滿活力且有意義的技術社群!
Discord頻道連結: https://sideproj.tw/dc