8/17/2013

Ruby Warrior - Trying Again

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:

Ed Sumerfield said...

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.