I'm trying to use the minmax algorithm for a game of tic-tac-toe.
This is the code I'm working with for getting the best AI move in the game.
def get_best_move(board, next_player, depth = 0, best_score = {})
return 0 if tie(board)
return -1 if game_is_over(board)
available_spaces = []
best_move = nil
board.each do |s|
if s != "X" && s != "O"
available_spaces << s
end
end
available_spaces.each do |as|
board[as.to_i] = #com
if game_is_over(board)
best_score[as.to_i] = -1 * get_best_move(board, #com, depth + 1, {})
board[as.to_i] = as
return best_score
else
board[as.to_i] = #hum
if game_is_over(board)
best_score[as.to_i] = -1 * get_best_move(board, #com, depth + 1, {})
board[as.to_i] = as
return best_score
else
board[as.to_i] = as
end
end
end
best_move = best_score.max_by { |key, value| value }[0]
highest_minimax_score = best_score.max_by { |key, value| value }[1]
if depth == 0
return best_move
elsif depth > 0
return highest_minimax_score
end
end
I get this error when I run the game in the terminal, and I would like to know how to fix it.
rb:332:in get_best_move': undefined method `[]' for nil:NilClass (NoMethodError)
rb:332 is referring to these 2 lines in the example below
best_move = best_score.max_by { |key, value| value }[0]
highest_minimax_score = best_score.max_by { |key, value| value }[1]
The error message says pretty clearly what the problem is: you are attempting to call [] on nil, but nil doesn't respond to []. The only place where you call [] is on the result of max_by. max_by returns nil when called on an empty Enumerable. Ergo, best_score must be empty. And indeed: you always pass an empty Hash as the argument to best_score, and you never modify best_score, so it is and always will be empty.
Related
Hello I am wondering if anyone may be able to give some assistance with two functions I am working on for a Ruby project. I have an array of Objects, and I need to get a certain attribute with the highest and lowest occurrence in each function respectively.
So far I have this, which works, but seems a bit too verbose:
def most_visited_port(time)
most_visited_port_name = ""
most_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
most_visited = ending_port_array.sort.max_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.max_by { |v| ending_port_array.count(v) } ? false : ending_port_array.max_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == most_visited
most_visited_port_name = ship.ending_port_name
end
end
pp most_visited_port_name
end
def least_visited_port(time)
least_visited_port_name = ""
least_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
least_visited = ending_port_array.sort.min_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.min_by { |v| ending_port_array.count(v) } ? false : ending_port_array.min_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == least_visited
least_visited_port_name = ship.ending_port_name
end
end
pp least_visited_port_name
end
Here is a sample of the array of Objects format:
[#<FleetShip:0x0000000108444450
#average_speed=46.02272727272727,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=0,
#ship_name="Alpha",
#time_arrived=2016-06-12 08:05:36 -0500,
#time_left=2016-06-12 06:20:00 -0500>,
#<FleetShip:0x0000000108444400
#average_speed=32.01932579334578,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=1,
#ship_name="Sea Ghost",
#time_arrived=2016-06-12 11:07:47 -0500,
#time_left=2016-06-12 08:36:00 -0500>]
But could anyone give some assistance on a possibly simpler or more concise way to pull it off?
For fun, you could use each_with_object to build a hash of ending_port values and their frequency, and then retrieve the most frequent.
I'm going to use a much simpler example.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1), A.new(3), A.new(3)]
most_freq_b = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}.max_by(&:last).first
# => 3
This does not account for situations where more than one value for b occurs equal numbers of times. We can tweak it though, to accomplish this.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1)]
freq = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}
highest_freq = freq.values.max
most_freq_b = freq.select { |_, v| v == highest_freq }.keys
# => [3, 1]
Alternatively, we can provide a default value of 0 for the hash, simplifying part of the code.
freq = c.each_with_object(Hash.new(0)) { |x, h|
h[x.b] += 1
}
I've been working on this for a few days, at least. Testing seems to show the correct value is being returned. My problem is being able to grab the best_move value and have it print out. I set up the suggested_move method and try to use return suggested_move(best_move) but it triggers the method for every level back up the tree. Also it returns the wrong value, which I'm guessing is because it's stopping before depth is back to 0.
In my minimax I had a similar setup the difference being the depth was incremented (not decremented) on successive calls. Point being I was able to say if depth == 0 p best_move. So I'm scratching my head because using that same conditional I get a nil class error in this code.
#board = ["X","O","O","X","X","","","O",""]
def available_spaces
#board.map.with_index {|a, i| a == "" ? i+1 : nil}.compact
end
def suggested_move(move)
p "Suggested move: #{move}"
end
def full?
#board.all?{|token| token == "X" || token == "O"}
end
def suggested_move(move)
p "Move: #{move}"
end
def bestmove(board, depth=0, best_score = {})
return 0 if full?
return 1 if won?
best = -Float::INFINITY
available_spaces.each do |space|
#board[space-1] = current_player
best_score[space] = -bestmove(#board, depth-1, {})
#board[space-1] = ""
end
p best_score
if best_score.max_by {|key,value| value }[1] > best
best = best_score.max_by {|key,value| value }[1]
best_move = best_score.max_by {|key,value| value }[0]
end
return best_move
end
bestmove(#board)
I have the following implementation of a linked list in Ruby:
class Node
attr_accessor :data, :next
def initialize(data = nil)
#data = data
#next = nil
end
end
class LinkedList
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def iterate
return nil if #head.nil?
entry = #head
until entry.nil?
yield entry
entry = entry.next
end
end
def equal?(other_list)
#How do I check if all the data for all the elements in one list are the same in the other one?
end
end
I have tried using the .iterate like this:
def equals?(other_list)
other_list.iterate do |ol|
self.iterate do |sl|
if ol.data != sl.data
return false
end
end
end
return true
end
But this is doing a nested approach. I fail to see how to do it.
You can't do it easily with the methods you have defined currently, as there is no way to access a single next element. Also, it would be extremely useful if you implemented each instead of iterate, which then gives you the whole power of the Enumerable mixin.
class LinkedList
include Enumerable # THIS allows you to use `zip` :)
class Node # THIS because you didn't give us your Node
attr_accessor :next, :value
def initialize(value)
#value = value
#next = nil
end
end
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def each
return enum_for(__method__) unless block_given? # THIS allows block or blockless calls
return if #head.nil?
entry = #head
until entry.nil?
yield entry.value # THIS yields node values instead of nodes
entry = entry.next
end
end
def ==(other_list)
# and finally THIS - get pairs from self and other, and make sure all are equal
zip(other_list).all? { |a, b| a == b }
end
end
a = LinkedList.new([1, 2, 3])
b = LinkedList.new([1, 2, 3])
c = LinkedList.new([1, 2])
puts a == b # => true
puts a == c # => false
EDIT: I missed this on the first run through: equal? is supposed to be referential identity, i.e. two variables are equal? if they contain the reference to the same object. You should not redefine that method, even though it is possible. Rather, == is the general common-language meaning of "equal" as in "having the same value", so I changed it to that.
I think there is something wrong with your initialize method in LinkedList, regardless could this be what you need
...
def equal?(other_list)
other_index = 0
cur_index = 0
hash = Hash.new
other_list.iterate do |ol|
hash[ol.data.data] = other_index
other_index += 1
end
self.iterate do |node|
return false if hash[node.data.data] != cur_index
return false if !hash.has_key?(node.data.data)
cur_index += 1
end
return true
end
...
Assuming this is how you use your code
a = Node.new(1)
b = Node.new(2)
c = Node.new(3)
listA = [a,b,c]
aa = Node.new(1)
bb = Node.new(2)
cc = Node.new(3)
listB = [aa,bb,cc]
linkA = LinkedList.new(listA)
linkB = LinkedList.new(listB)
puts linkA.equal?(linkB)
Don't understand why #nums.pop won't work in the value method. It seems to tell me that it can't do that for nil, but if I just say #nums, it shows that there is indeed something in the array. So then why can't I pop it out?
class RPNCalculator
def initialize
#value = value
nums ||= []
#nums = nums
end
def push(num)
#nums << num
end
def plus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop + #nums.pop
#nums.push(#value)
end
end
def minus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2] - #nums[-1]
#nums.pop(2)
#nums.push(#value)
end
end
def divide
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2].to_f / #nums[-1].to_f
#nums.pop(2)
#nums.push(#value)
end
end
def times
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop.to_f * #nums.pop.to_f
#nums.push(#value)
end
end
def value
#nums #Don't understand why #nums.pop won't work here
end
def tokens(str)
str.split(" ").map { |char| (char.match(/\d/) ? char.to_i : char.to_sym)}
end
def evaluate(str)
tokens(str).each do |x|
if x == ":-"
minus
elsif x == ":+"
plus
elsif x == ":/"
divide
elsif x ==":*"
times
else
push(x)
end
end
value
end
end
Error relates to the following part of a spec:
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
Error says either:
Failure/Error: calculator.value.should == 5
expected: 5
got: [5] <using ==>
OR if .pop is used
Failure/Error: #calculator = RPNCalculator.new
NoMethodError:
undefined method 'pop' for nil:NilClass
Your initialize method assigning #value = value calls the function at def value which returns #nums which has not yet been created in initialize since #nums is created afterwards with nums ||= []; #nums = nums therefore it's nil. This is why .pop won't work.
You've created #nums as an array with nums ||= [] and you're using it with push and pop so why are you checking for the value with value.should == 5 (Integer) when calling value returns an (Array). You would need to write it like value.first.should == 5 or value[0].should == 5 ... otherwise you should change value to return just the element you want
def value
#nums.pop # or #nums[0], or #nums.first or #nums.last however you plan on using it
end
The problem is #value = value in your initialize method. Fix that then you can add the .pop in value.
EDIT
Also your evaluation is calling methods before you've populated #nums with the values. Then the methods "raise" errors. You can't call minus after only one value has been pushed to #nums.
Here's how I would do the flow for splitting the string
# Multiplication and Division need to happen before addition and subtraction
mylist = "1+3*7".split(/([+|-])/)
=> ["1", "+", "3*7"]
# Compute multiplication and division
mylist = mylist.map {|x| !!(x =~ /[*|\/]/) ? eval(x) : x }
=> ["1", "+", 21]
# Do the rest of the addition
eval mylist.join
=> 22
I realize this isn't exactly how you're going about solving this... but I think splitting by order of mathematical sequence will be the right way to go. So first evaluate everything between (), then only multiplication and division, then all addition and subtraction.
EDIT I just looked into what a RPN Calculator is. So don't mind my splitting recommendation as it doesn't apply.
Can anyone please help a relatively new person of ruby to see why I am getting this no method error? It would be much appreciated!
def comp_block
only_user_valued = #winning_propositions.map { |each_hash| each_hash.select { |key, value| value == #user_sign } }
count_of_each = only_user_valued.map { |count_the_items_in_hash| count_the_items_in_hash.count }
index_array = count_of_each.each_with_index.select { |num, index| num == 2 }.map { |index_spot| index_spot[1] }
if index_array.empty? == true
random_move
else
#nil_valued_values_array = []
#nil_valued_array_true_false = []
index_array.each do |element|
#nil_valued_values_array += [#winning_propositions[element].select { |key, value| value == nil }]
#nil_valued_array_true_false += [#nil_valued_values_array.empty?]
end
nil_value = #nil_valued_values_array.delete({})
move = nil_value[0].keys[0]
if #nil_valued_array_true_false == [false] || #nil_valued_array_true_false == [true, false] || #nil_valued_array_true_false == [false, true]
#possible_places[move] = #comp_sign
#changes the winning prop values in parallel
list_of_matching_arrays=#winning_propositions.select { |key, value| key.to_s.match(move.to_s) }
list_of_matching_arrays.each do |change_hash_value|
change_hash_value[move] = #comp_sign
end
puts #comp_name + " made the move: #{move}"
display_game_board
puts "Here I am defending/BLOCKED!!!!!!"
else #nil_valued_array_true_false == [true] || #nil_valued_array_true_false == [true, true]
random_move
end
end
end
Well, obviously either nil_value or nil_value[0].keys is nil. Looking at the two lines of code:
nil_value = #nil_valued_values_array.delete({})
move = nil_value[0].keys[0]
The most obvious reason is that #nil_valued_values_array.delete({}) does not find an empty hash to delete, thus returns nil, or if it does find one, it returns an empty hash.
Well, this isn't exactly a fix (since you've got a bunch of code, not sure which things are happening externally), but you should add some debugging statements.
Look here:
move = nil_value[0].keys[0]
If either nil_value or keys are nil, you're going to get that error obviously. I suggest you print their values and see which one is null:
puts "<<<<< NIL_VALUE: #{nil_value}"
puts "<<<<< NIL_VALUE[0]: #{nil_value[0]}"
puts "<<<<< NIL_VALUE[0].KEYS: #{nil_value[0].keys}"
so on and so forth.