Created
June 10, 2009 20:07
-
-
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.
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
# 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