Skip to content

Instantly share code, notes, and snippets.

@rk
Created June 10, 2009 20:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rk/127466 to your computer and use it in GitHub Desktop.
Save rk/127466 to your computer and use it in GitHub Desktop.
My AI for the ruby-warrior project by RyanB, solves first 3 intermediate puzzles.
# I had trouble with the player saving data from the previous turns, namely
# the escape route I was generating to escape from 2 or more enemies. So,
# I put the behavior into a Brain that is a finite state machine. It's not
# that great, but you can see what I've got.
#
# This guy ought to solve ruby-warrior puzzles:
# - intermediate-001
# - intermediate-002
# - intermediate-003
class Brain
DIRECTIONS = [:left, :right, :forward, :backward]
def initialize
@last_health = 20
@health = 20
@next_dir = nil
@escape_route = []
# this will wind up as a decent FSM eventually
@state = :normal
end
def think(warrior)
@warrior = warrior
@health = warrior.health
@harmed = @health < @last_health
find_all_adjacent_spaces
dispatch
@last_health = warrior.health
end
def dispatch(new_state=nil)
@state = new_state unless new_state.nil?
send "state_#{@state.to_s}"
end
private
def state_normal
adjacent_enemies = adjacent_enemy_count
if adjacent_enemies > 1
dispatch :panic
elsif adjacent_enemies == 1
dispatch :hostile
else
if @health < 20
@warrior.rest!
elsif adjacent_captive_count > 0
@warrior.rescue! find_adjacent_captive
else
@warrior.walk! @warrior.direction_of_stairs
end
end
end
def state_hostile
hostiles = adjacent_enemy_count
if hostiles == 0
dispatch :normal
elsif hostiles == 1
@warrior.attack! find_adjacent_enemy
else
dispatch :panic
end
end
def state_panic
# goals:
# 1. generate escape route array and pop a direction from it,
# maybe a total of two.
# 2. when in a safer situation and without more than 1 enemy
# go hostile
# 3. go normal
threats = adjacent_enemy_count
if threats < 2 && @escape_route.count == 0
if threats == 0
dispatch :normal
else
dispatch :hostile
end
else # panicking is a REALLY good idea!
# plan an escape route if there is an empty space
if threats >= 2 && find_adjacent_empty_space
@escape_route << find_adjacent_empty_space
exclude = opposing_dir(find_adjacent_empty_space)
next_dir = DIRECTIONS.choice
until next_dir != exclude && next_dir != @escape_route.last
next_dir = DIRECTIONS.choice
end
@escape_route << next_dir
end
# unless in danger a second time, the route will be only 2 steps long
if @escape_route.count > 0
dir = @escape_route.shift
@warrior.walk! @spaces[dir] == :empty ? dir : opposing_dir(dir)
else
# otherwise start binding folks until there's only 1 left!
@warrior.bind! find_adjacent_enemy
end
end
end
def find_all_adjacent_spaces
@spaces = {}
DIRECTIONS.each do |dir|
@spaces[dir] = if @warrior.feel(dir).empty?
:empty
elsif @warrior.feel(dir).wall?
:wall
elsif @warrior.feel(dir).enemy?
:enemy
elsif @warrior.feel(dir).captive?
:captive
else
false
end
end
end
def adjacent_enemy_count
count = 0
@spaces.each do |dir,status|
count += 1 if status == :enemy
end
count
end
def adjacent_captive_count
count = 0
@spaces.each do |dir,status|
count += 1 if status == :captive
end
count
end
def find_adjacent_enemy
@spaces.each do |dir,status|
return dir if status == :enemy
end
nil
end
def find_adjacent_captive
@spaces.each do |dir,status|
return dir if status == :captive
end
nil
end
def find_adjacent_empty_space
@spaces.each do |dir,status|
return dir if status == :empty
end
nil
end
def opposing_dir(where)
{ # SORT OF cheating, but better than a case statement IMO.
:left => :right,
:right => :left,
:backward => :forward,
:forward => :backward
}[where]
end
end
class Player
def initialize
@@brain ||= Brain.new
end
def play_turn(warrior)
@@brain.think(warrior)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment