Permalink
Browse files

adds some specs and refactors several classes

  • Loading branch information...
1 parent e7b26ab commit 8135f4d5ef5809523eca41a4a506bd849ed8882b @jspillers committed May 16, 2012
View
2 .rspec
@@ -0,0 +1,2 @@
+--format nested
+--color
View
@@ -0,0 +1 @@
+
View
@@ -1,8 +1,5 @@
# base game controller
class AI
- # GameMap, as an array of arrays.
- attr_accessor :game_map
-
# Number of current turn. If it's 0, we're in setup turn. If it's :game_over, you don't need to give any orders; instead, you can find out the number of players and their scores in this game.
attr_accessor :turn_number
@@ -24,14 +21,14 @@ class << self
attr_accessor :logger, :ai
end
+ self.logger = Logger.new('ants.log')
+
# Initialize a new AI object. Arguments are streams this AI will read from and write to.
def initialize(stdin=$stdin, stdout=$stdout)
@stdin, @stdout = stdin, stdout
- @game_map = nil
+ GameMap.map_array = []
@turn_number = 0
@did_setup = false
- @my_ants = []
- @enemy_ants = []
@food_squares = []
end
@@ -61,7 +58,7 @@ def setup # :yields: self
@stdout.puts 'go'
@stdout.flush
- @game_map = Array.new(@rows) {|row|
+ GameMap.map_array = Array.new(@rows) {|row|
Array.new(@cols) {|col|
Square.new({
water: false, food: false, hill: false,
@@ -90,6 +87,7 @@ def run(&b) # :yields: self
# Internal; reads zero-turn input (game settings).
def read_intro
rd = @stdin.gets.strip
+
warn "unexpected: #{rd}" unless rd == 'turn 0'
until((rd = @stdin.gets.strip) == 'ready')
@@ -124,10 +122,12 @@ def read_turn
@turn_number = :game_over
rd = @stdin.gets.strip
+
_, players = *rd.match(/\Aplayers (\d+)\Z/)
@players = players.to_i
rd = @stdin.gets.strip
+
_, score = *rd.match(/\Ascore (\d+(?: \d+)+)\Z/)
@score = score.split(' ').map{|s| s.to_i}
@@ -138,15 +138,14 @@ def read_turn
end
# reset the game_map data
- @game_map.each do |row|
- row.each do |square|
- square.ant = nil
- square.hill = false
- end
+ GameMap.squares.each do |square|
+ square.ant = nil
+ square.hill = false
end
foods = []
- ant_locations = {}
+ my_ants = {}
+ enemy_ants = {}
until((rd = @stdin.gets.strip) == 'go')
_, type, row, col, owner = *rd.match(/(w|f|h|a|d) (\d+) (\d+)(?: (\d+)|)/)
@@ -155,22 +154,22 @@ def read_turn
case type
when 'w'
- @game_map[row][col].water = true
+ GameMap.square_at(row,col).water = true
when 'f'
foods << [row, col]
when 'h'
- @game_map[row][col].hill = owner
+ GameMap.square_at(row,col).hill = owner
when 'a'
if owner == 0
- ant_locations.merge!("my_ant:#{row}-#{col}" => true)
+ my_ants.merge!(Ant.ant_id(owner, row, col) => true)
else
- ant_locations.merge!("enemy_ant:#{row}-#{col}" => true)
+ enemy_ants.merge!(Ant.ant_id(owner, row, col) => true)
end
when 'd'
if owner == 0
- ant_locations.merge!("my_ant:#{row}-#{col}" => false)
+ my_ants.merge!(Ant.ant_id(owner, row, col) => false)
else
- ant_locations.merge!("enemy_ant:#{row}-#{col}" => false)
+ enemy_ants.merge!(Ant.ant_id(owner, row, col) => false)
end
when 'r'
# pass
@@ -179,50 +178,14 @@ def read_turn
end
end
- update_ants('my_ant', @my_ants, ant_locations)
- update_ants('enemy_ant', @enemy_ants, ant_locations)
-
- # any locations not previously used for updating must be newly spawned ants... add
- # them to the array of ant objects
- ant_locations.each do |k,v|
- location = k.split(":")[1].split('-')
- if k.match /^my_ant:/
- @my_ants << Ant.new(alive: true, owner: 0, square: @game_map[location[0].to_i][location[1].to_i])
- elsif k.match /^enemy_ant:/
- @enemy_ants << Ant.new(alive: true, owner: 1, square: @game_map[location[0].to_i][location[1].to_i])
- end
- end
+ Ant.update_my_ants(my_ants)
+ Ant.update_enemy_ants(enemy_ants)
Food.update_foods(foods)
ret
end
- # loop through all ant objects and update their location and aliveness
- # remove from the ants array if not found in the new ant_locations hash
- def update_ants(key_prefix, collection, ant_locations)
- collection.each do |ant|
- if ant_locations.has_key?(ant.ant_id)
- # update the ants location
- ant.square = ant.expected_square
-
- # update alive or dead
- ant.alive = ant_locations[ant.ant_id]
-
- @game_map[ant.row][ant.col].ant = ant
-
- # update complete... remove from the hash of new locations
- ant_locations.delete(ant.ant_id)
- else
- # ant is no longer on the map... remove from array
- collection.delete(ant)
-
- # a dead ant is not able to get the food... unassign
- ant.path.last.food.ant_en_route = nil if ant.has_path? && ant.path.last.food?
- end
- end
- end
-
# call-seq:
# order(ant, direction)
# order(row, col, direction)
View
@@ -6,7 +6,73 @@ class Ant
# Square this ant sits on.
attr_accessor :square
- attr_accessor :alive, :position_history, :order_history, :path
+ attr_accessor :alive, :position_history, :order_history, :path, :state
+
+ class << self
+ attr_accessor :my_ants, :enemy_ants
+ end
+
+ def self.ant_id(owner, row, col)
+ "#{owner}:#{row}-#{col}"
+ end
+
+ def self.update_my_ants(ant_locations)
+ @my_ants ||= []
+ update_ants(@my_ants, ant_locations)
+ end
+
+ def self.update_enemy_ants(ant_locations)
+ @enemy_ants ||= []
+ update_ants(@enemy_ants, ant_locations)
+ end
+
+ # loop through all ant objects and update their location and aliveness
+ # remove from the ants array if not found in the new ant_locations hash
+ def self.update_ants(collection, ant_locations)
+ collection.each do |ant|
+ if ant_locations.has_key?(ant.ant_id)
+ # update the ants location
+ ant.square = ant.expected_square
+
+ # update alive or dead
+ ant.alive = ant_locations[ant.ant_id]
+
+ # update the game_map square with reference to this ant
+ GameMap.square_at(ant.row, ant.col).ant = ant
+
+ # update complete... remove from the hash of new locations
+ ant_locations.delete(ant.ant_id)
+ else
+ # ant is no longer on the map... remove from array
+ collection.delete(ant)
+
+ # a dead ant is not able to get the food... unassign
+ ant.path.last.food.ant_en_route = nil if ant.has_path? && ant.path.last.food?
+ end
+ end
+
+ # any locations not previously used for updating must be newly spawned ants... add
+ # them to the array of ant objects
+ ant_locations.each do |k,v|
+ owner_and_location = k.split(':')
+ owner, location = owner_and_location[0], owner_and_location[1].split('-')
+
+ if owner && location
+ ant = Ant.new(
+ alive: true,
+ owner: owner,
+ square: GameMap.square_at(location)
+ )
+ ant.position_history = [[ant.row, ant.col]]
+ collection << ant
+ end
+ end
+
+ end
+
+ class << self
+ attr_accessor :move_orders
+ end
def initialize(opts={})
@alive = opts[:alive]
@@ -18,14 +84,6 @@ def initialize(opts={})
@path = []
end
- def self.ant_id(prefix, row, col)
- "#{prefix}:#{row}-#{col}"
- end
-
- class << self
- attr_accessor :move_orders
- end
-
def ant_id
row, col = if order_history.empty?
@position_history.last
@@ -34,7 +92,6 @@ def ant_id
[square.row, square.col]
end
- owner = mine? ? 'my_ant' : 'enemy_ant'
self.class.ant_id(owner, row, col)
end
@@ -79,14 +136,14 @@ def expected_square
if order_history.empty?
square
else
- previous_pos = position_history.last
- square = AI.ai.game_map[previous_pos[0]][previous_pos[1]]
- square.neighbor(order_history.last)
+ square = GameMap.square_at(position_history.last)
+ square.neighbor(order_history.last) if square
end
end
def set_path_to(goal_square)
calculated_path = square.calculate_path_to(goal_square)
+ AI.logger.debug 'calculated path: ' + calculated_path[1].map(&:to_a).inspect
if !calculated_path.nil? && !calculated_path[1].nil?
calculated_path[1].shift if !calculated_path[1].empty? && calculated_path[1].first.to_a == square.to_a
@path = calculated_path[1]
View
@@ -16,7 +16,7 @@ def to_a
def consumed!
ant_en_route.path = [] if ant_en_route.is_a?(Ant)
ant_en_route = nil
- AI.ai.game_map[row][col].food = nil
+ GameMap.square_at(row, col).food = nil
end
# class methods
@@ -27,18 +27,18 @@ def self.update_foods(food_locs)
@foods = [] unless @foods.is_a?(Array)
curr_foods = @foods.map(&:to_a)
-
# create new foods for food locations that are reported
# but have not had food objects created for them
(food_locs - (curr_foods & food_locs)).each do |new_food_loc|
- food = Food.new(square: AI.ai.game_map[new_food_loc[0]][new_food_loc[1]])
+ square = GameMap.square_at(new_food_loc)
+ food = Food.new(square: square)
@foods << food
- AI.ai.game_map[new_food_loc[0]][new_food_loc[1]].food = food
+ square.food = food
end
# remove foods that exist already but are not reported (ie: eaten)
(curr_foods - food_locs).each do |consumed_food|
- food = AI.ai.game_map[consumed_food[0]][consumed_food[1]].food
+ food = GameMap.square_at(consumed_food).food
food.consumed!
@foods.delete(food)
end
@@ -55,7 +55,13 @@ def self.unassigned_foods?
end
def self.unassigned_foods
- @foods.select {|f| f.ant_en_route.nil? }
+ foods = @foods.select {|f| !f.ant_en_route.is_a?(Ant) }
+ AI.logger.debug 'unassigned food locations: ' + foods.map(&:to_a).inspect
+ AI.logger.debug 'all food locations: ' + @foods.map(&:to_a).inspect
+ AI.logger.debug 'food and assigned ant: ' + @foods.map{|f| [f.to_a, f.ant_en_route] }.inspect
+ AI.logger.debug '----------------' * 5
+
+ foods
end
def self.nearest_unassigned_from(ant_square)
View
@@ -0,0 +1,46 @@
+class GameController
+
+ def self.dirs
+ [:N,:E,:S,:W]
+ end
+
+ def self.do_turn(ai)
+ square = GameMap.square_at([35,10])
+
+ #AI.logger.debug 'square: ' + square.to_a.inspect
+
+ Ant.move_orders = []
+
+ #AI.logger.debug 'number of ants: ' + ai.my_ants.size.to_s
+ #AI.logger.debug 'number of enemy ants: ' + ai.enemy_ants.size.to_s
+ #AI.logger.debug 'food locations: ' + Food.foods.map(&:to_a).inspect
+ #AI.logger.debug 'food assignments: ' + Food.foods.map {|f| f.ant_en_route.ant_id if f.ant_en_route }.inspect
+
+ Ant.my_ants.first.state = 'gather'
+
+ Ant.my_ants.each_with_index do |ant,i|
+ #AI.logger.debug 'ant.has_path?: ' + ant.has_path?.to_s
+
+ if !ant.has_path?
+
+ #AI.logger.debug 'Food.unassigned_available? ' + Food.unassigned_available?.to_s
+ if ant.state == 'gather' && Food.unassigned_foods?
+ food_square = Food.nearest_unassigned_from(ant.square)
+
+ #AI.logger.debug 'food square to_a: ' + food_square.to_a.inspect
+
+ food_square.ant_en_route = ant
+ ant.path = ant.set_path_to(food_square.square)
+
+ #AI.logger.debug 'path: ' + ant.path.inspect
+ else
+ # wander if no food found
+ ant.path = [ant.neighbor(dirs[rand(0..3)])]
+ end
+ end
+
+ ant.move_to_next_path_node
+ end
+
+ end
+end
View
@@ -0,0 +1,22 @@
+class GameMap
+
+ class << self
+ attr_accessor :map_array
+ end
+
+ def self.square_at(*args)
+ row, col = args[0].is_a?(Array) ? args[0] : [args[0], args[1]]
+ @map_array[row.to_i][col.to_i]
+ end
+
+ def self.set_square_at(*args)
+ row, col = args[0].is_a?(Array) ? args[0] : [args[0], args[1]]
+ square = args.select {|a| a.is_a?(Square) }.first
+ @map_array[row.to_i][col.to_i] = square
+ end
+
+ # return a flat array of all squares
+ def self.squares
+ @map_array.flatten
+ end
+end
Oops, something went wrong.

0 comments on commit 8135f4d

Please sign in to comment.