Lets talk Ruby and Ruby warrior. I love this gem. Its fun to challenge myself to beat the game in epic mode while writing some ruby code challenging myself to complete not just the game but to do it with some of my style.
Github: Ruby Warrior

So to start I wrote code that got me all the way through then passed the Epic level I.E. code ran start to finish from level 1 – 9; Since then its been lets optimize the code and see if we cannot beat the score for epic.

Not a large score but a 488 with a grade of B is not too bad.
My player class wants to leverage Ruby and so I put code into separate classes depending on need.
Current player.rb class. Note: loading the additional files as well as pry which I constantly use to debug.
require 'pry'
load 'warrior_recon.rb'
load 'warrior_turn.rb'
load 'threat_check.rb'
class Player
attr_reader :warrior, :recon, :turn_end_health, :stashed_recon
def play_turn(warrior)
@warrior = warrior
warrior_turn = WarriorTurn.new(warrior, @turn_end_health, @stashed_recon)
warrior_turn.next_turn
@turn_end_health = warrior.health
@stashed_recon = warrior_turn.current_recon
puts "This is the player health at end of turn #{turn_end_health}"
end
end
Still in the process of refactoring so some of the classes still need cleaning up.
Recon class (warrior_recon.rb). Reconnaissance done for the warrior. This is a passive skill I.E. it does not count as a turn; leveraging this as much as possible to streamline the turn seems like a good idea.
# frozen_string_literal: true
# Class will do reconnaissance for warrior
# Stash will add current recon to stashed recon
class WarriorRecon
attr_reader :warrior
def initialize(warrior)
@warrior = warrior
end
def stash_additions(recon_result, new_recon)
recon_result = {} if recon_result.nil?
recon_result.merge(new_recon)
end
def warrior_direction(position)
space = warrior.look(:forward).first
front_location = space.location.first
return :west if front_location > position
:east
end
def reconnaissance
eval_look(warrior.look(:forward)).merge(eval_look(warrior.look(:backward)))
end
def warrior_position
front_space = warrior.look(:forward).first.location
back_space = warrior.look(:backward).first.location
front_space.zip(back_space).map { |front, back| (front + back) / 2 }
end
def eval_look(look)
look.each_with_object({}) do |item, recon_result|
next if item.location.first < -1
location = item.location.first
recon_result[location] =
if item.captive?
{ name: 'captive' }
elsif item.wall?
{ name: location == -1 ? 'east_wall' : 'west_wall' }
elsif item.unit
{ name: item.unit.name }
elsif item.stairs?
{ name: 'stairs' }
else
{ name: 'space' }
end
end
end
end
Threat Check class (threat_check.rb) works with the recon class.
# frozen_string_literal: true
require 'pry'
# Class looks for threats about to happen to warrior
class ThreatCheck
attr_reader :warrior, :recon, :turn_end_health
ENEMIES = {
'Sludge' => { health: 12, power: 3 },
'Archer' => { health: 7, power: 3 },
'Thick Sludge' => { health: 24, power: 3 },
'Wizard' => { health: 3, power: 11 }
}.freeze
LONG_DISTANCE_ENEMIES = %w[Archer Wizard 'Thick Sludge'].freeze
STAIRS = %w[stairs].freeze
WALLS = %w[east_wall west_wall].freeze
CAPTIVE = 'captive'.freeze
def initialize(recon_team, recon)
@recon = recon
@warrior_position = recon_team.warrior_position.first
@warrior_direction = recon_team.warrior_direction(@warrior_position)
end
def health_needs_replenish?(health)
return false unless enemy_on_next_space?
enemy = recon[@warrior_position + 2]
return false unless enemy
enemy_stats = ENEMIES[enemy[:name]]
(enemy_stats[:health] / 5) >= (health / enemy_stats[:power])
end
def threat_in_front?
enemy_in_locations?(@warrior_position + 1, @warrior_position + 1)
end
def threat_from_behind?
enemy_in_locations?(@warrior_position - 2, @warrior_position)
end
def long_range_threat?
enemy_in_locations?(@warrior_position - 2, @warrior_position + 2)
end
def enemy_on_next_space?
enemy_in_locations?(@warrior_position + 2, @warrior_position + 2)
end
def enemy_in_locations?(local1, local2)
recon.any? do |pos, space|
pos.between?(local1, local2) && ENEMIES.key?(space[:name])
end
end
def captive_in_front?
enemy_space = recon[@warrior_position + 1]
enemy_space[:name] == CAPTIVE
end
def long_distance_enemy?
enemy_space = recon[@warrior_position + 2]
enemy_space && LONG_DISTANCE_ENEMIES.include?(enemy_space[:name])
end
def long_distance_attack?
return false if recon.empty?
recon.any? do |pos, space|
LONG_DISTANCE_ENEMIES.include?(space[:name]) && [@warrior_position + 1, @warrior_position + 2].include?(pos)
end
end
end
Warrior turn class. (warrior_turn.rb)
class WarriorTurn
attr_reader :warrior, :recon_team, :turn_end_health, :recon, :threatened,
:check, :current_recon
def initialize(warrior, turn_end_health, stashed_recon)
@warrior = warrior
@recon_team = WarriorRecon.new(warrior)
@current_recon = recon_team.stash_additions(stashed_recon, recon_team.reconnaissance)
@check = ThreatCheck.new(@recon_team, @current_recon)
@turn_end_health = turn_end_health
end
def walking
puts 'Warrior walks forward'
warrior.walk!
end
def walking_backward
puts 'Walking backward, likely a need to refresh health'
warrior.walk!(:backward)
end
def shoot
warrior.shoot!
end
def attack
warrior.attack!
end
def release
warrior.rescue!
end
def rest
puts 'Player is resting until max health reached'
warrior.rest!
end
def pivot
warrior.pivot!
end
def handle_ranged_threat
return shoot if check.long_distance_enemy?
pivot
end
def next_turn
return attack if check.threat_in_front?
return pivot if check.threat_from_behind?
return rest if check.health_needs_replenish?(warrior.health)
# return shoot if check.long_distance_attack?
return release if check.captive_in_front?
walking
end
end
Currently my code will only make it to level 6 then break.
Level 6 setup:
|C @ S aa|
The fix is going to be rescue the captive then account for the two(2) archers in a row that have to be dealt with. Health needs replenish works well to get the S rating(highest score possible) but is having issues with the multiple archers after the thick sludge attack.
def health_needs_replenish?(health)
return false unless enemy_on_next_space?
enemy = recon[@warrior_position + 2]
return false unless enemy
enemy_stats = ENEMIES[enemy[:name]]
(enemy_stats[:health] / 5) >= (health / enemy_stats[:power])
end
Next update coming soon.
Leave a comment