Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7ec9a81
commit 83921e7
Showing
16 changed files
with
690 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.hlt | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
|
||
# Welcome to your first Halite-III bot! | ||
# | ||
# This bot's purpose is simple (don't expect it to win complex games!): | ||
# 1. Initialize game | ||
# 2. If a ship is full or the current ship position has less than 10% halite, | ||
# move in a random direction. Otherwise, collect halite. | ||
# 3. Try to spawn a ship at the shipyard. | ||
|
||
# Load the files we need | ||
$:.unshift(File.dirname(__FILE__) + "/hlt") | ||
require 'game' | ||
require 'logger' | ||
|
||
# Our bot is named... | ||
bot_name = "MyRubyBot" | ||
|
||
# Set up a logger | ||
LOGGER = Logger.new("#{bot_name}_#{Time.now.to_f}.log").tap do |l| | ||
l.formatter = proc do |severity, datetime, progname, msg| | ||
"#{datetime.strftime("%m-%d %H:%M:%S.%3N")} - #{severity} - #{msg}\n" | ||
end | ||
end | ||
|
||
######## Game Begin ######## | ||
|
||
# This game object contains the initial game state. | ||
game = Game.new(bot_name) | ||
# At this point "game" variable is populated with initial map data. | ||
# This is a good place to do computationally expensive start-up pre-processing. | ||
|
||
# As soon as you call "ready" function below, the 2 second per turn timer will | ||
# start. | ||
game.ready | ||
|
||
# Now that your bot is initialized, save a message to yourself in the log file | ||
# with some important information. | ||
# Here, you log here your id, which you can always fetch from the game object | ||
# by using my_id. | ||
LOGGER.info("Successfully created bot! My Player ID is #{game.my_id}.") | ||
|
||
######## Game Loop ######## | ||
|
||
while true | ||
# This loop handles each turn of the game. The game object changes every turn, | ||
# and you refresh that state by running update_frame(). | ||
game.update_frame | ||
# Extract player metadata and the updated map metadata here for convenience. | ||
me = game.me | ||
map = game.map | ||
|
||
# A command queue holds all the commands you will run this turn. | ||
# You build this list up and submit it at the end of the turn. | ||
command_queue = [] | ||
|
||
for ship in me.ships | ||
# For each of your ships, move randomly if the ship is on a low halite | ||
# location or the ship is full. | ||
# Else, stay still & collect halite. | ||
if map[ship.position].halite_amount < Constants::MAX_HALITE / 10 || ship.is_full? | ||
command_queue << ship.move( Direction.all_cardinals.sample ) | ||
else | ||
command_queue << ship.stay_still | ||
end | ||
end | ||
|
||
# If the game is in the first 200 turns and you have enough halite, spawn a | ||
# ship. Don't spawn a ship if you currently have a ship at port, though - | ||
# the ships will collide. | ||
if game.turn_number <= 200 && me.halite_amount >= Constants::SHIP_COST && | ||
!map[me.shipyard].is_occupied? | ||
command_queue << me.shipyard.spawn | ||
end | ||
|
||
# Send your moves back to the game environment, ending this turn. | ||
game.end_turn(command_queue) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
|
||
# A cell on the game map. | ||
class Cell | ||
attr_reader :position | ||
attr_accessor :structure, :halite_amount, :ship | ||
|
||
def initialize(position, halite_amount) | ||
@position = position | ||
@halite_amount = halite_amount | ||
@ship = nil | ||
@structure = nil | ||
end | ||
|
||
# :return: Whether this cell has no ships or structures | ||
def is_empty? | ||
return @ship == nil && @structure == nil | ||
end | ||
|
||
# :return: Whether this cell has any ships | ||
def is_occupied? | ||
return @ship != nil | ||
end | ||
|
||
# :return: Whether this cell has any structures | ||
def has_structure? | ||
return @structure != nil | ||
end | ||
|
||
# :return: What is the structure type in this cell | ||
def structure_type | ||
return !@structure ? nil : @structure.class | ||
end | ||
|
||
# Mark this cell as unsafe (occupied) for navigation. | ||
# Use in conjunction with GameMap.naive_navigate. | ||
def mark_unsafe(ship) | ||
@ship = ship | ||
end | ||
|
||
def ==(other) | ||
return @position == other.position | ||
end | ||
|
||
def to_s | ||
return "Cell(#{@position}, halite=#{@halite_amount})" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# All viable commands that can be sent to the engine | ||
|
||
module Commands | ||
NORTH = 'n' | ||
SOUTH = 's' | ||
EAST = 'e' | ||
WEST = 'w' | ||
STAY_STILL = 'o' | ||
GENERATE = 'g' | ||
CONSTRUCT = 'c' | ||
MOVE = 'm' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# The constants representing the game variation being played. | ||
# They come from game engine and changing them has no effect. | ||
# They are strictly informational. | ||
|
||
module Constants | ||
# Load constants from JSON given by the game engine. | ||
def self.load_constants(constants) | ||
# The cost to build a single ship. | ||
const_set("SHIP_COST", constants['NEW_ENTITY_ENERGY_COST']) | ||
|
||
# The cost to build a dropoff. | ||
const_set("DROPOFF_COST", constants['DROPOFF_COST']) | ||
|
||
# The maximum amount of halite a ship can carry. | ||
const_set("MAX_HALITE", constants['MAX_ENERGY']) | ||
|
||
# The maximum number of turns a game can last. This reflects the fact | ||
# that smaller maps play for fewer turns. | ||
const_set("MAX_TURNS", constants['MAX_TURNS']) | ||
|
||
# 1/EXTRACT_RATIO halite (truncated) is collected from a square per turn. | ||
const_set("EXTRACT_RATIO", constants['EXTRACT_RATIO']) | ||
|
||
# 1/MOVE_COST_RATIO halite (truncated) is needed to move off a cell. | ||
const_set("MOVE_COST_RATIO", constants['MOVE_COST_RATIO']) | ||
|
||
# Whether inspiration is enabled. | ||
const_set("INSPIRATION_ENABLED", constants['INSPIRATION_ENABLED']) | ||
|
||
# A ship is inspired if at least INSPIRATION_SHIP_COUNT opponent | ||
# ships are within this Manhattan distance. | ||
const_set("INSPIRATION_RADIUS", constants['INSPIRATION_RADIUS']) | ||
|
||
# A ship is inspired if at least this many opponent ships are within | ||
# INSPIRATION_RADIUS distance. | ||
const_set("INSPIRATION_SHIP_COUNT", constants['INSPIRATION_SHIP_COUNT']) | ||
|
||
# An inspired ship mines 1/X halite from a cell per turn instead. | ||
const_set("INSPIRED_EXTRACT_RATIO", constants['INSPIRED_EXTRACT_RATIO']) | ||
|
||
# An inspired ship that removes Y halite from a cell collects X*Y additional halite. | ||
const_set("INSPIRED_BONUS_MULTIPLIER", constants['INSPIRED_BONUS_MULTIPLIER']) | ||
|
||
# An inspired ship instead spends 1/X% halite to move. | ||
const_set("INSPIRED_MOVE_COST_RATIO", constants['INSPIRED_MOVE_COST_RATIO']) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
require 'commands' | ||
|
||
# Holds positional arrays in relation to cardinal directions | ||
module Direction | ||
NORTH = [0, -1] | ||
SOUTH = [0, 1] | ||
EAST = [1, 0] | ||
WEST = [-1, 0] | ||
|
||
STILL = [0, 0] | ||
|
||
# Returns all contained items in each cardinal | ||
# :return: An array of cardinals | ||
def self.all_cardinals | ||
return [NORTH, SOUTH, EAST, WEST] | ||
end | ||
|
||
# Converts from this direction tuple notation to the engine's string notation | ||
# :param direction: the direction in this notation | ||
# :return: The character equivalent for the game engine | ||
def self.convert(direction) | ||
case direction | ||
when NORTH | ||
return Commands::NORTH | ||
when SOUTH | ||
return Commands::SOUTH | ||
when EAST | ||
return Commands::EAST | ||
when WEST | ||
return Commands::WEST | ||
when STILL | ||
return Commands::STAY_STILL | ||
else | ||
raise IndexError | ||
end | ||
end | ||
|
||
# Returns the opposite cardinal direction given a direction | ||
# :param direction: The input direction | ||
# :return: The opposite direction | ||
def self.invert(direction) | ||
case direction | ||
when NORTH | ||
return SOUTH | ||
when SOUTH | ||
return NORTH | ||
when EAST | ||
return WEST | ||
when WEST | ||
return EAST | ||
when STILL | ||
return STILL | ||
else | ||
raise IndexError | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
require 'entity' | ||
|
||
|
||
# Dropoff class for housing dropoffs | ||
class Dropoff < Entity | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
require 'position' | ||
|
||
# Base Entity Class from whence Ships, Dropoffs and Shipyards inherit | ||
class Entity | ||
attr_reader :owner, :id, :position | ||
|
||
def initialize(owner, id, position) | ||
@owner = owner | ||
@id = id | ||
@position = position | ||
end | ||
|
||
# Method which creates an entity for a specific player given input from the engine. | ||
# :param game: The game object for fetching information | ||
# :param player_id: The player id for the player who owns this entity | ||
# :return: An instance of Entity along with its id | ||
def self.generate(game, player_id) | ||
id, x, y = game.read_ints_from_input | ||
self.new(player_id, id, Position.new(x, y)) | ||
end | ||
|
||
def to_s | ||
"#{self.class}(id=#{@id}, #{@position})" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
require 'logger' | ||
require 'json' | ||
require 'constants' | ||
require 'map' | ||
require 'player' | ||
|
||
# The game object holds all metadata pertinent to the game and all its contents | ||
class Game | ||
attr_reader :map, :me, :turn_number, :my_id | ||
|
||
# Initiates a game object collecting all start-state instances for the contained items for pre-game. | ||
# Also sets up basic logging. | ||
def initialize(name) | ||
@name = name | ||
# implicit IO flush | ||
$stdout.sync = true | ||
|
||
# Grab constants JSON | ||
Constants.load_constants(JSON.parse(read_from_input)) | ||
|
||
@turn_number = 0 | ||
@num_players, @my_id = read_ints_from_input | ||
|
||
@players = {} | ||
@num_players.times do | ||
player_id, player = Player.generate(read_ints_from_input) | ||
@players[player_id] = player | ||
end | ||
|
||
@me = @players[@my_id] | ||
@map = Map.generate(self) | ||
end | ||
|
||
# Indicate that your bot is ready to play. | ||
def ready | ||
end_turn([@name]) | ||
end | ||
|
||
# Updates the game object's state. | ||
# :returns: nothing. | ||
def update_frame | ||
@turn_number = read_ints_from_input.first | ||
LOGGER.info("=============== TURN #{@turn_number} ================") | ||
|
||
@num_players.times do |_| | ||
player, num_ships, num_dropoffs, halite = read_ints_from_input | ||
@players[player].update(self, num_ships, num_dropoffs, halite) | ||
end | ||
|
||
@map.update(self) | ||
|
||
|
||
# Mark cells with ships as unsafe for navigation | ||
for player in @players.values | ||
for ship in player.ships | ||
@map[ship.position].mark_unsafe(ship) | ||
end | ||
|
||
@map[player.shipyard.position].structure = player.shipyard | ||
for dropoff in player.dropoffs | ||
@map[dropoff.position].structure = dropoff | ||
end | ||
end | ||
end | ||
|
||
def end_turn(commands) | ||
write_to_output(commands.join(" ")) | ||
end | ||
|
||
def read_from_input | ||
$stdin.gets.strip | ||
end | ||
|
||
def read_ints_from_input | ||
read_from_input.split(' ').map { |v| Integer(v) } | ||
end | ||
|
||
private | ||
|
||
def write_to_output(data) | ||
data = "#{data.strip}" | ||
LOGGER.info("Sending: #{data.inspect}") | ||
$stdout.puts(data) | ||
end | ||
end |
Oops, something went wrong.