iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0

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/

前情提要

  • 🚧 選職業:開始職業階段

sprint 3 遊戲基本流程完成

  • 建築卡片資料
    • 工廠建築卡片資料
    • 城市建築卡片資料
  • 建築卡片功能實作
  • 🚧 選職業:開始職業階段
  • 執行 礦工 階段行動
  • 執行 議員 階段行動
  • 執行 建築師 階段行動
  • 執行 製造商 階段行動
  • 執行 貿易商 階段行動
  • 行動結束,更換目前玩家
  • 回合開始
  • 遊戲結束

今天要做

  • 選完職業,更新遊戲狀態
  • 判斷有沒有什麼動作可以由系統接續自動執行
  • 如果有,回傳 202
  • 如果沒有,回傳 204

選完職業,更新遊戲狀態

  1. 建立 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
  1. 建立 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
  1. 建立 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
  1. 更新 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
  1. 更新 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
  1. 測試看看

重置一場遊戲

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"]

小結

  • 🚧 選完職業,更新遊戲狀態
    • 更新 controller
    • 更新測試,產生 swagger
  • 判斷有沒有什麼動作可以由系統接續自動執行
  • 如果有,回傳 202
  • 如果沒有,回傳 204

TODO

  • 建築卡片資料
    • 工廠建築卡片資料
    • 城市建築卡片資料
  • 建築卡片功能實作
  • 選職業:開始職業階段
    • 🚧 選完職業,更新遊戲狀態
      • 更新 controller
      • 更新測試,產生 swagger
    • 判斷有沒有什麼動作可以由系統接續自動執行
    • 如果有,回傳 202
    • 如果沒有,回傳 204
  • 執行 礦工 階段行動
  • 執行 議員 階段行動
  • 執行 建築 階段行動
  • 執行 生產 階段行動
  • 執行 交易 階段行動
  • 行動結束,更換目前玩家
  • 回合開始
  • 遊戲結束

明天要做什麼

  • 選完職業,更新遊戲狀態
    • 更新 controller
    • 更新測試,產生 swagger

以上不代表明天會做,如有雷同純屬巧合


工商服務

SPT (Side Project Taiwan) 的宗旨是藉由Side Project開發來成就自我,透過持續學習和合作,共同推動技術和專業的發展。我們相信每一個參與者,無論是什麼專業,都能在這個社群中找到屬於自己的成長空間。

歡迎所有對Side Project開發有興趣的人加入我們,可以是有點子來找夥伴,也可以是來尋找有興趣的Side Project加入,邀請大家一同打造一個充滿活力且有意義的技術社群!

Discord頻道連結: https://sideproj.tw/dc


上一篇
Day 18 - 選職業(1/n)
下一篇
Day 20 - 選職業(3/n)
系列文
透過實作網頁遊戲練習網站工程師的基本素養,以 San Juan(聖胡安) 為例。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言