Skip to content

Instantly share code, notes, and snippets.

@coffeeaddict
Created October 25, 2010 10:29
Show Gist options
  • Save coffeeaddict/644740 to your computer and use it in GitHub Desktop.
Save coffeeaddict/644740 to your computer and use it in GitHub Desktop.
Bulls and Cows - an attempt to get a sane solution for http://rosettacode.org/wiki/Bulls_and_cows/Player
class Game
attr_reader :secret
def initialize
@secret = Secret.new
end
def console_play
bulls = -1
while bulls != 4
bulls = 0
cows = 0
print "Your guess please: "
guess = gets.chomp.split("").collect { |d| d.to_i }
(bulls,cows) = secret.score(guess)
puts "You have #{cows} cows and #{bulls} bulls"
end
end
end
require 'game'
require 'player'
# let the automated player play a game
game = Game.new
player = Player.new(game)
player.play
require 'game.rb'
class Player
attr_reader :game
def initialize(game=nil)
@game = game
@game ||= Game.new
end
def play
# find the numbers in play
numbers = []
1.upto(9) do |i|
(bulls,cows) = game.secret.score(Array.new(4) { i })
if ( bulls > 0 || cows > 0 )
numbers << i
end
end
# try a forward rotating approach
#
# keep track of the scoring, if we can't find a match in mere rotating we
# must be able to calculate the best results
#
bulls = -1
count = 0
high = {}
while bulls != 4
(bulls, cows) = game.secret.score(numbers)
high[numbers] = bulls if bulls >= 2
numbers = rotate(numbers, count += 1) if bulls != 4
break if count > 20
end
if bulls != 4
numbers = must_be(high)
end
puts "#{numbers.join} -- #{game.secret.secret} : #{numbers == game.secret.s\
ecret}"
end
# figure out what it must be, given the high scoring combinations
#
def must_be(high)
# make a list of positions and numbers
pos = { 0 => [], 1 => [], 2 => [], 3 => [] }
high.each_key { |key|
key.each_index { |i| pos[i] << key[i] }
}
# find double occurences on the given position, they must be right
must_be = Array.new(4) { 0 }
pos.each_key { |i|
prev = 0
pos[i].sort { |a,b| a <=> b }.each do |n|
must_be[i] = n if n == prev
prev = n
end
}
if must_be.include? 0
# There are still empty positions we have figured out only 2.
# The other 2 numbers are known - try them at each available position
#
# First copy the must be positions
#
could_be = [ must_be.dup ]
could_be[0].each_index { |i|
# substract any already known numbers from the pool
pos[i] -= must_be
while could_be[0][i] == 0
# assume the first number is right unless it already occurs
candidate = pos[i].shift
could_be[0][i] = candidate unless could_be[0].include? candidate
end
}
# The newly found numbers might be reversed, fix that
could_be[1] = could_be[0].dup
must_be.each_index { |i|
if must_be[i] == 0
could_be[1][
i == 0 ? 3 : i == 1 ? 2 : i == 2 ? 1 : 0
] = could_be[0][i]
end
}
# Check if the first guess fits
(bulls, cows) = game.secret.score(could_be[0])
must_be = bulls == 4 ? could_be[0] : could_be[1]
end
must_be
end
# Rotate the numbers forward
#
def rotate(array,count)
pos = count % 4
return array if pos == 0
dup = array.dup
dup[pos] = dup[pos-1]
dup[pos-1] = array[pos]
return dup
end
end
# A Bulls and Cows secret
#
class Secret
attr_reader :secret
def initialize
@secret = []
while @secret.length < 4
rnd = 1 + rand(9)
@secret << rnd unless @secret.include? rnd
end
end
def score(guess)
bulls = 0
cows = 0
guess.each_index { |i|
if @secret[i] == guess[i]
bulls += 1
elsif @secret.include? guess[i]
cows += 1
end
}
return [bulls,cows]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment