It is clear that a solution to a problem like this can't just be imagined out of thin air. I am just not that smart. Just writing if statements was satisfying and productive to start with but once level 7 hit and I had to change direction there was no alternative but to throw everything away and start again.
This time, I decided to create a fake warrior implementation, an write some rspec tests to ensure that the actions I chose were appropriate. Then "release" (copy) that code to "production" (paste) to see if it worked there as well.
With this approach, I am making headway but can only solve level 2 because I haven't reimplemented the resting process yet.
Warrior Fake
class Warrior
attr_accessor :position
attr_accessor :board
def self.at_position_1
warrior = Warrior.new
warrior.position = 1
warrior
end
def self.next_to_sludge
warrior = Warrior.at_position_1
warrior.board = [:sludge]
warrior
end
def initialize
@position = 1 #zero based
@attacked = false
@board = []
end
def walk!
@position += 1
end
def attack!
@attacked = true
end
def has_attacked?
@attacked
end
def feel
@board
end
end
Player Implementation
require './warrior_fake'
class Context
end
class Action
def self.choose(warrior, context)
if !warrior.feel.empty?
Attack.new
else
Walk.new
end
end
end
class Walk
def run(warrior)
warrior.walk!
warrior
end
end
class Attack
def run(warrior)
warrior.attack!
warrior
end
end
class Player
def initialize
@context = Context.new
end
def play_turn(warrior)
action = Action.choose(warrior, @context)
action.run(warrior)
warrior
end
end
Specs
require './spec_helper'
require './warrior_fake'
require './warrior'
describe Player do
let(:player) { Player.new }
context "walking" do
it "moves forward" do
warrior = player.play_turn(Warrior.at_position_1)
warrior.position.should == 2
end
end
context "sludge" do
it "notices sludge" do
warrior = player.play_turn(Warrior.next_to_sludge)
warrior.should have_attacked
end
end
end
describe Action do
let(:context) { Context.new }
let(:warrior) { Warrior.at_position_1 }
context "construction" do
it "walks forward" do
action = Action.choose(warrior, context)
action.should be_a Walk
end
end
end
describe Walk do
it "forward" do
walk = Walk.new
warrior = walk.run(Warrior.at_position_1)
warrior.position.should == 2
end
end
describe Attack do
it "forward" do
attack = Attack.new
warrior = attack.run(Warrior.next_to_sludge)
warrior.should have_attacked
end
end
1 comment:
This little choose factory method, in amongst the other nice refactored code :
def self.choose(warrior, context)
if !warrior.feel.empty?
Attack.new
else
Walk.new
end
end
Is the same as the first example, just hidden in a factory method. Things do not get easier because the fluff code is refactored. The essence of the problem is that we do not know how to abstract the warrior logic as a generic algorithm.
This might not be necessary, depending on your goal. We could certainly kill each level with a sequence of proscribed steps. but it seems more interesting to find the pattern that allows the warrior to be of one mind and kill all on comers.
Post a Comment