Skip to content

Commit

Permalink
Create a starter kit for Ruby
Browse files Browse the repository at this point in the history
  • Loading branch information
jenheilemann committed Oct 17, 2018
1 parent 7ec9a81 commit 83921e7
Show file tree
Hide file tree
Showing 16 changed files with 690 additions and 0 deletions.
2 changes: 2 additions & 0 deletions starter_kits/Ruby/.gitignore
@@ -0,0 +1,2 @@
*.hlt
*.log
77 changes: 77 additions & 0 deletions starter_kits/Ruby/MyBot.rb
@@ -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
47 changes: 47 additions & 0 deletions starter_kits/Ruby/hlt/cell.rb
@@ -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
12 changes: 12 additions & 0 deletions starter_kits/Ruby/hlt/commands.rb
@@ -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
47 changes: 47 additions & 0 deletions starter_kits/Ruby/hlt/constants.rb
@@ -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
57 changes: 57 additions & 0 deletions starter_kits/Ruby/hlt/direction.rb
@@ -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
6 changes: 6 additions & 0 deletions starter_kits/Ruby/hlt/dropoff.rb
@@ -0,0 +1,6 @@
require 'entity'


# Dropoff class for housing dropoffs
class Dropoff < Entity
end
25 changes: 25 additions & 0 deletions starter_kits/Ruby/hlt/entity.rb
@@ -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
85 changes: 85 additions & 0 deletions starter_kits/Ruby/hlt/game.rb
@@ -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

0 comments on commit 83921e7

Please sign in to comment.