Class: Sc2::Player
- Inherits:
-
Object
- Object
- Sc2::Player
- Extended by:
- Forwardable
- Includes:
- GameState
- Defined in:
- lib/sc2ai/player.rb,
lib/sc2ai/player/geo.rb,
lib/sc2ai/player/debug.rb,
lib/sc2ai/player/units.rb,
lib/sc2ai/player/actions.rb,
lib/sc2ai/player/game_state.rb,
lib/sc2ai/player/previous_state.rb
Overview
Allows defining Ai, Bot, BotProcess (external), Human or Observer for a Match
Defined Under Namespace
Modules: Actions, Debug, GameState, Units Classes: Bot, BotProcess, Computer, Enemy, Geo, Human, Observer, PreviousState
Constant Summary collapse
- IDENTIFIED_RACES =
Known races for detecting race on Api::Race::RANDOM or nil
[Api::Race::PROTOSS, Api::Race::TERRAN, Api::Race::ZERG].freeze
Instance Attribute Summary collapse
- #ai_build ⇒ Integer
-
#api ⇒ Sc2::Connection
Manages connection to client and performs Requests.
-
#callbacks_defined ⇒ Array<Symbol>
Callbacks implemented on player class.
-
#difficulty ⇒ Integer
if @type is Api::PlayerType::COMPUTER, set one of Api::Difficulty scale 1 to 10.
-
#enable_feature_layer ⇒ Boolean
Enables the feature layer at 1x1 pixels.
-
#IDENTIFIED_RACES ⇒ Array<Integer>
Known races for detecting race on Api::Race::RANDOM or nil.
- #interface_options ⇒ Hash
-
#name ⇒ String
In-game name.
-
#opponent_id ⇒ String
Ladder matches will set an opponent id.
-
#race ⇒ Integer
Api::Race enum.
-
#realtime ⇒ Boolean
Realtime mode does not require stepping.
-
#step_count ⇒ Integer
Number of frames to step in step-mode, default 1.
-
#type ⇒ Integer
Api::PlayerType::PARTICIPANT, Api::PlayerType::COMPUTER, Api::PlayerType::OBSERVER.
Attributes included from GameState
#chats_received, #data, #game_info, #game_loop, #observation, #result, #spent_minerals, #spent_supply, #spent_vespene, #status
Connection collapse
-
#connect(host:, port:) ⇒ Sc2::Connection
Creates a new connection to Sc2 client.
-
#disconnect ⇒ void
Terminates connection to Sc2 client.
-
#quit ⇒ void
Note: Do not document, because ladder players should never quit, but rather #leave_game instead Sends quit command to SC2.
-
#requires_client? ⇒ Boolean
Returns whether or not the player requires a sc2 instance.
Api collapse
- #create_game(map:, players:, realtime: false) ⇒ Object
- #join_game(server_host:, port_config:) ⇒ Object
-
#leave_game ⇒ Object
Multiplayer only.
Instance Method Summary collapse
-
#callback_defined?(callback) ⇒ Boolean
Checks if callback method is defined on our bot Used to skip processing on unused callbacks.
-
#initialize(race:, name:, type: nil, difficulty: nil, ai_build: nil) ⇒ Player
constructor
A new instance of Player.
-
#prepare_start ⇒ Object
Initialize data on step 0 before stepping and before on_start is called.
-
#race_unknown? ⇒ Boolean
Checks whether the Player#race is known.
-
#refresh_game_info ⇒ void
Refreshes bot#game_info ignoring all caches.
-
#refresh_state ⇒ void
Refreshes game state for current loop.
-
#set_enemy ⇒ Object
Sets enemy once #game_info becomes available on start.
-
#set_race_for_random ⇒ Object
If you’re random, best to set #race to match after launched.
-
#started ⇒ Object
Initialize step 0 after data has been gathered.
-
#step_forward ⇒ Api::Observation
Moves emulation ahead and calls back #on_step.
Methods included from GameState
#available_abilities, #common, #on_status_change
Methods included from Connection::StatusListener
Constructor Details
#initialize(race:, name:, type: nil, difficulty: nil, ai_build: nil) ⇒ Player
Returns a new instance of Player.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/sc2ai/player.rb', line 80 def initialize(race:, name:, type: nil, difficulty: nil, ai_build: nil) # Be forgiving to symbols race = Api::Race.resolve(race) if race.is_a?(Symbol) type = Api::PlayerType.resolve(type) if type.is_a?(Symbol) difficulty = Api::Difficulty.resolve(difficulty) if difficulty.is_a?(Symbol) ai_build = Api::AIBuild.resolve(ai_build) if ai_build.is_a?(Symbol) # Yet strict on required fields raise ArgumentError, "unknown race: '#{race}'" if race.nil? || Api::Race.lookup(race).nil? raise ArgumentError, "unknown type: '#{type}'" if type.nil? || Api::PlayerType.lookup(type).nil? @race = race @name = name @type = type @difficulty = difficulty @ai_build = ai_build @realtime = false @step_count = 2 @enable_feature_layer = false @interface_options = {} end |
Instance Attribute Details
#ai_build ⇒ Integer
70 71 72 |
# File 'lib/sc2ai/player.rb', line 70 def ai_build @ai_build end |
#api ⇒ Sc2::Connection
Manages connection to client and performs Requests
33 34 35 |
# File 'lib/sc2ai/player.rb', line 33 def api @api end |
#callbacks_defined ⇒ Array<Symbol>
Returns callbacks implemented on player class.
550 551 552 |
# File 'lib/sc2ai/player.rb', line 550 def callbacks_defined @callbacks_defined end |
#difficulty ⇒ Integer
if @type is Api::PlayerType::COMPUTER, set one of Api::Difficulty scale 1 to 10
66 67 68 |
# File 'lib/sc2ai/player.rb', line 66 def difficulty @difficulty end |
#enable_feature_layer ⇒ Boolean
Enables the feature layer at 1x1 pixels. Adds additional actions (UI and Spatial) at the cost of overall performance. Must be configured before #join_game
48 49 50 |
# File 'lib/sc2ai/player.rb', line 48 def enable_feature_layer @enable_feature_layer end |
#IDENTIFIED_RACES ⇒ Array<Integer>
Known races for detecting race on Api::Race::RANDOM or nil
27 |
# File 'lib/sc2ai/player.rb', line 27 IDENTIFIED_RACES = [Api::Race::PROTOSS, Api::Race::TERRAN, Api::Race::ZERG].freeze |
#interface_options ⇒ Hash
52 53 54 |
# File 'lib/sc2ai/player.rb', line 52 def @interface_options end |
#name ⇒ String
Returns in-game name.
58 59 60 |
# File 'lib/sc2ai/player.rb', line 58 def name @name end |
#opponent_id ⇒ String
Returns ladder matches will set an opponent id.
73 74 75 |
# File 'lib/sc2ai/player.rb', line 73 def opponent_id @opponent_id end |
#race ⇒ Integer
Returns Api::Race enum.
55 56 57 |
# File 'lib/sc2ai/player.rb', line 55 def race @race end |
#realtime ⇒ Boolean
Realtime mode does not require stepping. When you observe the current step is returned.
38 39 40 |
# File 'lib/sc2ai/player.rb', line 38 def realtime @realtime end |
#step_count ⇒ Integer
Returns number of frames to step in step-mode, default 1.
42 43 44 |
# File 'lib/sc2ai/player.rb', line 42 def step_count @step_count end |
#type ⇒ Integer
Returns Api::PlayerType::PARTICIPANT, Api::PlayerType::COMPUTER, Api::PlayerType::OBSERVER.
61 62 63 |
# File 'lib/sc2ai/player.rb', line 61 def type @type end |
Instance Method Details
#callback_defined?(callback) ⇒ Boolean
Checks if callback method is defined on our bot Used to skip processing on unused callbacks
556 557 558 559 560 561 562 |
# File 'lib/sc2ai/player.rb', line 556 def callback_defined?(callback) if @callbacks_defined.nil? # Cache the intersection check, assuming nobody defines a callback method while actually running @callbacks_defined = CALLBACK_METHODS.intersection(self.class.instance_methods(false)) end @callbacks_defined.include?(callback) end |
#connect(host:, port:) ⇒ Sc2::Connection
Creates a new connection to Sc2 client
117 118 119 120 121 122 123 124 |
# File 'lib/sc2ai/player.rb', line 117 def connect(host:, port:) @api&.close @api = Sc2::Connection.new(host:, port:) # @api.add_listener(self, klass: Connection::ConnectionListener) @api.add_listener(self, klass: Connection::StatusListener) @api.connect @api end |
#create_game(map:, players:, realtime: false) ⇒ Object
150 151 152 153 |
# File 'lib/sc2ai/player.rb', line 150 def create_game(map:, players:, realtime: false) Sc2.logger.debug { "Creating game..." } @api.create_game(map:, players:, realtime:) end |
#disconnect ⇒ void
This method returns an undefined value.
Terminates connection to Sc2 client
128 129 130 |
# File 'lib/sc2ai/player.rb', line 128 def disconnect @api&.close end |
#join_game(server_host:, port_config:) ⇒ Object
157 158 159 160 161 162 163 164 165 |
# File 'lib/sc2ai/player.rb', line 157 def join_game(server_host:, port_config:) Sc2.logger.debug { "Player \"#{@name}\" joining game..." } response = @api.join_game(name: @name, race: @race, server_host:, port_config:, enable_feature_layer: @enable_feature_layer, interface_options: @interface_options) if response.error != :ENUM_RESPONSE_JOIN_GAME_ERROR_UNSET && response.error != :MISSING_PARTICIPATION raise Sc2::Error, "Player \"#{@name}\" join_game failed: #{response.error}" end add_listener(self, klass: Connection::StatusListener) response end |
#leave_game ⇒ Object
Multiplayer only. Disconnects from a multiplayer game, equivalent to surrender. Keeps client alive.
168 169 170 |
# File 'lib/sc2ai/player.rb', line 168 def leave_game @api.leave_game end |
#prepare_start ⇒ Object
Initialize data on step 0 before stepping and before on_start is called
565 566 567 568 569 570 |
# File 'lib/sc2ai/player.rb', line 565 def prepare_start @data = Sc2::Data.new(@api.data) clear_action_queue clear_action_errors clear_debug_command_queue end |
#quit ⇒ void
This method returns an undefined value.
Note: Do not document, because ladder players should never quit, but rather #leave_game instead Sends quit command to SC2
137 138 139 |
# File 'lib/sc2ai/player.rb', line 137 def quit @api&.quit end |
#race_unknown? ⇒ Boolean
Checks whether the Player#race is known. This is false on start for Random until scouted.
528 529 530 |
# File 'lib/sc2ai/player.rb', line 528 def race_unknown? !IDENTIFIED_RACES.include?(race) end |
#refresh_game_info ⇒ void
This method returns an undefined value.
Refreshes bot#game_info ignoring all caches
673 674 675 |
# File 'lib/sc2ai/player.rb', line 673 public def refresh_game_info self.game_info = @api.game_info end |
#refresh_state ⇒ void
This method returns an undefined value.
Refreshes game state for current loop. Will update GameState#observation and GameState#game_info TODO: After cleaning up all the comments, review whether this is too heavy or not. #perf #clean
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 |
# File 'lib/sc2ai/player.rb', line 597 def refresh_state # Process.clock_gettime(Process::CLOCK_MONOTONIC) step_to_loop = @realtime ? game_loop + @step_count : nil response_observation = @api.observation(game_loop: step_to_loop) return if response_observation.nil? # Check if match has a result and callback on_player_result(response_observation.player_result) unless response_observation.player_result.empty? # Halt further processing if match is over return unless @result.nil? # Save previous frame before continuing @previous.reset(self) # We can request game info async, while we process observation refresh_game_info # Reset self.observation = response_observation.observation self.game_loop = observation.game_loop self.chats_received = response_observation.chat self.spent_minerals = 0 self.spent_vespene = 0 self.spent_supply = 0 geo.reset # First game-loop: set enemy and our race if random if enemy.nil? # Finish game_info load immediately, because we need it's info game_info set_enemy set_race_for_random if race == Api::Race::RANDOM end parse_observation_units(response_observation.observation) # Having loaded all the necessities for the current state... # If we're on the first frame of the game, say previous state and current are the same # This is better than having a bunch of random zero and nil values @previous.reset(self) if @previous.all_units.nil? # Actions performed and errors (only if implemented) on_actions_performed(response_observation.actions) unless response_observation.actions.empty? if callback_defined?(:on_action_errors) unless response_observation.action_errors.empty? @action_errors.concat(response_observation.action_errors.to_a) end on_action_errors(@action_errors) unless @action_errors&.empty? end on_alerts(observation.alerts) unless observation.alerts.empty? # Diff previous observation upgrades to see if anything new completed new_upgrades = observation.raw_data.player.upgrade_ids - @previous.observation.raw_data.player.upgrade_ids on_upgrades_completed(new_upgrades) unless new_upgrades.empty? # Dead units = observation.raw_data&.event&.dead_units @event_units_destroyed = UnitGroup.new &.each do |dog_tag| dead_unit = previous.all_units[dog_tag] unless dead_unit.nil? @event_units_destroyed.add(dead_unit) on_unit_destroyed(dead_unit) end end # If enemy is not known, try detect every couple of frames based on units if enemy.race_unknown? && enemy.units.size > 0 detected_race = enemy.detect_race_from_units on_random_race_detected(detected_race) if detected_race end end |
#requires_client? ⇒ Boolean
Returns whether or not the player requires a sc2 instance
108 109 110 |
# File 'lib/sc2ai/player.rb', line 108 def requires_client? true end |
#set_enemy ⇒ Object
Sets enemy once #game_info becomes available on start
686 687 688 689 690 691 692 693 694 695 696 697 |
# File 'lib/sc2ai/player.rb', line 686 def set_enemy enemy_player_info = game_info.player_info.find { |pi| pi.player_id != observation.player_common.player_id } self.enemy = Sc2::Player::Enemy.from_proto(player_info: enemy_player_info) if enemy.nil? self.enemy = Sc2::Player::Enemy.new(name: "Unknown", race: Api::Race::RANDOM) end if enemy.race_unknown? detected_race = enemy.detect_race_from_units on_random_race_detected(detected_race) if detected_race end end |
#set_race_for_random ⇒ Object
If you’re random, best to set #race to match after launched
680 681 682 683 |
# File 'lib/sc2ai/player.rb', line 680 def set_race_for_random player_info = game_info.player_info.find { |pi| pi.player_id == observation.player_common.player_id } self.race = player_info.race_actual end |
#started ⇒ Object
Initialize step 0 after data has been gathered
573 574 575 576 577 578 |
# File 'lib/sc2ai/player.rb', line 573 def started # Calculate expansions geo.expansions # Set our start position base on camera geo.start_position end |
#step_forward ⇒ Api::Observation
Moves emulation ahead and calls back #on_step
582 583 584 585 586 587 588 589 590 591 |
# File 'lib/sc2ai/player.rb', line 582 def step_forward # Sc2.logger.debug "#{self.class} step_forward" unless @realtime @api.step(@step_count) end refresh_state on_step if @result.nil? end |