Minimax algorithm Ruby Tic Tac Toe - ruby

I am writing unbeatable tic tac toe game using minimax algorithm. For some reason my scores hash lose its value as it comes out for the loop. If it is happening then I must be doing something wrong that I am unable to catch. I am new to coding. Need help!!!
mark is the mark for current player
mark1 and mark2 are the two marks for player1 and player2 respectively
spots is the empty spots on the board
require_relative 'board'
class ComputerPlayer
attr_reader :board
def initialize
#board = Board.new
#optimal_moves = [0, 2, 4, 6, 8]
end
def get_position(name, spots, mark, mark1, mark2)
position = nil
other_mark = nil
mark == mark1 ? other_mark = mark2 : other_mark = mark1
if spots.length == 9
position = #optimal_moves.sample.to_i
else
position = best_move(spots, mark, other_mark)
end
print "Enter your move #{name}: #{position}\n"
position
end
def best_move(spots, mark, other_mark, depth = 0, scores = {})
return 1 if board.winner == mark
return 0 if board.draw?
return -1 if board.winner == other_mark
spots.each do |move|
board.place_mark(move, mark)
scores[move] = best_move(spots[1..-1], mark, other_mark, depth += 1, {})
board.reset_position(move)
end
# it does not keep the value of scores. scores here is {}
return scores.max_by { |key, value| value }[0] if depth == 0
return scores.max_by { |key, value| value }[1] if depth > 0
return scores.min_by { |key, value| value }[1] if depth < 0
end
end

It looks like you are passing an empty hash back into best_move every time. What you're probably wanting to do is pass scores in on each recurrence to build up an object with moves and scores.
scores[move] = best_move(spots[1..-1], mark, other_mark, depth += 1, scores)

Related

Need help to create a breadth-first search function

I’m currently doing Knight’s Travails project.
In this project you need to find the shortest way from A to B for the chess knight.
I don’t know why my program crashes when it comes to breadth-first search function. I cannot catch it with debugger because VScode freezes at reading variable “root” inside knight_moves.
Could you help me find the ussue?
I’ve created the board. It has links to every cell of the board according position of the cell.
I’ve created links between cells with add_edges function. Links are possible ways to move.
So far I’ve got the code below
class Node
attr_reader :pos
attr_accessor :children, :search_info
def initialize (row, column)
#pos = [row, column]
#children = nil
#search_info = Hash.new
end
end
class Board
attr_reader :show
def initialize
create_board
end
def create_board
board = []
8.times do |x|
board<<[x]
end
board.each_with_index do |item, index|
8.times do |x|
board[index] << x unless x == index
end
end
board.each do |x|
x.sort!
end
#board = board
end
def show
#board
end
def fill_with_nodes
#board.each_with_index do |item, index|
item.map! {|column| Node.new(index,column)}
end
end
def add_edges
#board.each_with_index do |row, index|
row.each do |node|
node.children = []
node.children = node.children << #board[node.pos[0]-2][node.pos[1]-1] if (0..7).include?(node.pos[0]-2) && (0..7).include?(node.pos[1]-1)
node.children = node.children << #board[node.pos[0]-2][node.pos[1]+1] if (0..7).include?(node.pos[0]-2) && (0..7).include?(node.pos[1]+1)
node.children = node.children << #board[node.pos[0]+2][node.pos[1]-1] if (0..7).include?(node.pos[0]+2) && (0..7).include?(node.pos[1]-1)
node.children = node.children << #board[node.pos[0]+2][node.pos[1]+1] if (0..7).include?(node.pos[0]+2) && (0..7).include?(node.pos[1]+1)
node.children = node.children << #board[node.pos[0]-1][node.pos[1]-2] if (0..7).include?(node.pos[0]-1) && (0..7).include?(node.pos[1]-2)
node.children = node.children << #board[node.pos[0]+1][node.pos[1]-2] if (0..7).include?(node.pos[0]+1) && (0..7).include?(node.pos[1]-2)
node.children = node.children << #board[node.pos[0]-1][node.pos[1]+2] if (0..7).include?(node.pos[0]-1) && (0..7).include?(node.pos[1]+2)
node.children = node.children << #board[node.pos[0]+1][node.pos[1]+2] if (0..7).include?(node.pos[0]+1) && (0..7).include?(node.pos[1]+2)
end
end
end
def cell (row, column)
#board[row][column]
end
def knight_moves (start, finish)
raise StandardError.new("Invalid start") unless (0..7).include?(start[0]) || (0..7).include?(start[1])
raise StandardError.new("Invalid finish") unless (0..7).include?(finish[0]) || (0..7).include?(finish[1])
queue = []
root = #board[finish[0]][finish[1]]
root.search_info[:distanse] = 0
queue << root
until queue.empty?
node = queue.shift
break if node.pos == [start[0],start[1]]
node.children.each do |child|
unless child.search_info[:distanse]
child.search_info[:distanse] = node.search_info[:distanse] + 1
child.search_info[:predecessor] = node
queue << child
end
end
end
end
end
#This part is for testing
puts a = Board.new
puts a.show.to_s
a.fill_with_nodes
puts a.show.to_s
a.add_edges
a.knight_moves([0,0], [0,1])
def show_cell(board,row, column)
puts ""
puts board.cell(row,column).pos.to_s, board.cell(row,column).children.map {|child| child.pos}.to_s ,board.cell(row,column).search_info.to_s
end
show_cell(a,2,2)
Edit: I've found that line "child.search_info[:predecessor] = node" crashes the programm. And if I use #variable to store "predecessor" instead of hash the programm runs. I don't know why though. What's the reason?
As for me, the main issue with the code is its unnecessary ("incidental") complexity.
Yes, the task you're solving can be reduced to a graph traversal problem, but it doesn't mean you must represent the graph explicitly. For this particular task - where all the possible moves from the arbitrary cell are well-defined and the board itself is limited - you can easily calculate the graph edges on the fly (and without all this additional machinery that makes your code so hard to reason about - even for you). Explicit representation of the board looks redundant too (again, for this particular task).
Taking all this into account, the solution might be as simple as:
class Knight
def initialize
#knight_moves = [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]
end
def move(start, stop)
visited = {}
queue = [[stop, nil]]
while queue.any?
current_cell, next_cell = queue.shift
next if visited.has_key?(current_cell)
visited[current_cell] = next_cell
return build_path(start, stop, visited) if current_cell == start
possible_moves(current_cell).each do |next_move|
queue << [next_move, current_cell] unless visited.has_key?(next_move)
end
end
end
private
def possible_moves(cell)
#knight_moves.
map { |x, y| [cell.first + x, cell.last + y] }.
select(&method(:valid_move?))
end
def build_path(start, stop, visited)
path = [start]
while next_cell = visited[path.last]
path << next_cell
end
path.last == stop ? path : nil
end
def valid_move?(cell)
cell.all? { |n| n >= 0 && n <= 7 }
end
end
knight = Knight.new
knight.move [0,0], [0,1] #=> [[0, 0], [2, 1], [1, 3], [0, 1]]

How to create a Minimax algorithm comparing arrays

I'm trying to code a "minimax" algorithm for Tic Tac Toe.
Each node of the tree is of the form [nil/Int, String] where the last element is a nine character string describing the board, and the first is an Integer ranking the node, or nil by default.
If the value is nil, it tries to inherit the appropriate value from child nodes.
This is where I get an error, when comparing an array with an array failed.
class Scene_TicTacToe #Script 2/2
def initialize
#Boardstate as a str from top left corner to bottom right corner.
#boardstate = "---------"
#1 = player, -1 = comp
#active_player = 1
end
def wincheck(boardstate=#boardstate)
#should return -1 for loss, 0 for draw, 1 for win
["OOO","XXX"].each do |f|
for i in 0..2
if (boardstate[i]+boardstate[i+3]+boardstate[i+6]).chr == f || boardstate[(3*i)..(3*i)+2] == f
return f == "OOO" ? 1 : -1
end
end
if (boardstate[0]+boardstate[4]+boardstate[8]).chr == f || (boardstate[2]+boardstate[4]+boardstate[6]).chr == f
return f == "OOO" ? 1 : -1
end
end
return 0
end
def computer_play
#Sets depth,and alpha/beta for pruning, so far so good
depth = 3
alpha = -100
beta = 100
##boardstate starts as "---------"
##active_player: 1=player, -1=computer
play(minimax(#boardstate, depth, alpha, beta, #active_player))
end
def play(array)
#Check actual boardside with parameter boardside to see what move has been
#selected and plays that move
for i in 0...array[1].length
if #boardstate[i] != array[1][i]
#color = array[1][i].chr == "X" ? #ai : #player
##cursor.y = (i / 3) * #side
##cursor.x = (i % 3) * #side
##board.bitmap.fill_rect(#cursor.x,#cursor.y,#side,#side,color)
#boardstate = array[1].dup
end
end
end
def minimax(boardstate, depth, alpha, beta, active_player)
#If bottom node reached, returns [boardstate_score, boardstate]
#wincheck returns 1 if player wins, -1 if computer wins, and 0 otherwise
if depth == 0 or wincheck(boardstate) != 0 or (/-/ =~ boardstate) == nil
return [wincheck(boardstate),boardstate]
end
if active_player == 1 #if player's turn
#Gets an array of all the next possible boardstates and return the one with
#the best eval.
child = generate_child(boardstate, active_player)
child.each do |f| #f = [Int/nil, String]
if f[0] == nil
#This should turn all the nil wincheck values to the best value of children nodes
f[0] = minimax(f[1], depth-1, alpha, beta, -active_player).last[0]
end
alpha = [f[0], alpha].max
if beta <= alpha
break
end
end
return child.sort_by{|c| c[0]}
end
if active_player == -1 #if computer's turn
#Same as above but with worst eval.
child = generate_child(boardstate, active_player)
child.each do |f|
if f[0] == nil
f[0] = minimax(f[1], depth-1, alpha, beta, -active_player).first[0]
end
beta = [f[0], beta].min
if beta <= alpha
break
end
end
#Following line raises "comparison of array with array failed" error :
return child.sort_by{|c| c[0]}
end
end
def generate_child(boardstate, active_player)
#returns boardstate string with one X or O more than current boardstate
#and sets nil as a default wincheck value
c = active_player == 1 ? "O" : "X"
a = []
for i in 0...boardstate.length
if boardstate[i].chr == "-"
s = boardstate.dup
s[i]= c
a << [nil, s]
end
end
return a
end
end
Error: comparison of array with array failed

Adding value from parent node

I am trying to implement the A* Algorithm to find the shortest path from A to B. I am using a linked list with nodes to find the best path.
One of the values I need to calculate is g, which is the cost of movement from the starting point to the current node. Moving horizontal or vertical cost 10, and moving diagonal costs 14.
For example,
11 22 33
44 55 66
Moving from 11 to 22 cost 10. From 22 to 66 cost 14, so moving from 11 to 66 cost 24 total according to that particular path.
In my node class, I have a method that calculate g from the previous node to the current node, in the previous example, 22 to 66. In order to find the total g cost, I added the g value from the parent's node. However, this is extremely expensive and takes forever to run, especially when the number of possible paths is huge.
What can I do to speed up this algorithm? Is there a better way to calculate the g value?
Here is the relevant code from my Node class.
class Node
attr_accessor :position, :g, :h, :parent
def initialize(position, parent = nil)
#position = position
#parent = parent
if parent.nil?
#g = 0
else
#g = calc_g + #parent.g #<= this line costs the issue.
end
end
def calc_h(end_pos)
(#position.first - end_pos.first).abs + (#position.last - end_pos.last).abs
end
def calc_g
row_diff = (#position.first - self.parent.position.first).abs
col_diff = (#position.last - self.parent.position.last).abs
if [row_diff, col_diff] == [1,1]
g = 14
else
g = 10
end
end
end
Here is my MazeSolver class.
require_relative 'node'
class MazeSolver
attr_accessor :open_list, :closed_list, :maze, :start_node, :end_node, :current_node
def initialize(file_name)
#open_list = []
#closed_list = {}
#maze = create_maze_array(file_name)
#start_node = Node.new(find_point("S"))
#end_node = Node.new(find_point("E"))
#current_node = #start_node
end
def create_maze_array(file_name)
maze = File.readlines(file_name).map(&:chomp)
maze.map! do |line|
line.split('')
end
maze
end
def find_point(sym)
#maze.each_with_index do |line, row|
line.each_with_index do |el, col|
return [row, col] if el == sym
end
end
end
def find_neighbors(position)
row, col = position
(row-1..row+1).each do |r|
(col-1..col+1).each do |c|
next if #closed_list.values.include?([r, c])
unless #maze[r][c] == '*'
#open_list << Node.new([r, c], #current_node)
end
end
end
#open_list
end
def solved?
#current_node.position == #end_node.position
end
def solve
until solved?
#closed_list[#current_node] = #current_node.position
find_neighbors(#current_node.position)
#current_node = lowest_f(#open_list)
#open_list.delete(#current_node)
end
path = get_path(#current_node)
display_route(path)
end
# determine the lowest f score node
def lowest_f(nodes)
f_score = {}
nodes.each do |node|
f_score[node] = node.g + node.calc_h(#end_node.position)
end
f_score.sort_by { |node, score| score }.first.first
end
# create path array
def get_path(node)
path = []
until node.position == #start_node.position
path << node.position
node = node.parent
end
path
end
# display maze without route and maze with route
def display_route(path)
display_maze
path.each do |move|
unless #maze[move.first][move.last] == 'E'
#maze[move.first][move.last] = 'X'
end
end
puts
display_maze
end
# display the maze as it is.
def display_maze
#maze.each do |line|
puts line.join
end
end
end
if __FILE__ == $PROGRAM_NAME
puts "What is the name of the maze file?"
begin
MazeSolver.new(gets.chomp).solve
rescue
puts "File does not exist. Try Again"
retry
end
end
Here is the link to repl with example mazes that I worked with. maze1.txt is the one that takes a long time to run.

Getting a stack overflow when trying to impliment Negamax in Tic Tac Toe

I apologize in advance if this is a re-post, but I have read other posts on the topic and I am still can't figure out what to do. I am trying to implement Negamax in a ruby tic tac toe game and I am getting a stack overflow error on line 55. I have read so many posts and articles on Negamax and I still can't get it to work.
This file isn't part of a larger program yet, I just wanted to pass in a board and see if it would make a move.
class Negamax
attr_accessor :board, :mark, :depth, :winning_routes
def initialize
#board = ["X","2","3","4","5","6","7","8","9"]
#mark = "O"
#depth = 1
#winning_routes = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
end
def negamax(board, mark, depth)
if winner?(mark) || isboardfull?(board)
return game_result(mark)
else
max = -1.0/0
mark == "O" ? opponent = "X" : "O"
available_moves.each do |space|
board[space] = mark
score = -negamax(board, opponent, depth + 1)
board[space] = "#{space + 1}"
if score > max
max = score
best_move = space if depth == 1
board[best_move] = mark
end
end
return max
end
end
def available_moves()
board.each_index.select { |s| board[s] != "X" && board[s] != "O"}
end
def isboardfull?(board)
boardtos = board.join(",")
boardtos =~ /\d/ ? false : true
end
def game_result(mark)
if winner?(mark)
return 1
elsif winner?(mark == "O" ? "X" : "O")
return -1
else
return 0
end
end
def winner?(mark)
result = false
marker = mark
winning_routes.each do |group|
if board[group[0]] == marker && board[group[1]] == marker && board[group[2]] == marker
result = true
end
end
result
end
end
game = Negamax.new()
game.negamax(game.board, game.mark, game.depth)
print game.board

Debugging Recursive MinMax in TicTacToe

I'm trying to get the minmax algorithm (computer AI) to work in my game of tic-tac-toe. I've been stuck on this for days. Essentially, I don't understand why the computer AI simply places it's marker ("O") in sequential order from board pieces 0-8.
For example, as the human player, if I choose 1, then the computer will choose 0:
O| X| 2
--+---+--
3| 4| 5
--+---+--
6| 7| 8
Next, if I choose 4, then the computer will choose 2:
O| X| O
--+---+--
3| X| 5
--+---+--
6| 7| 8
And so on:
O| X| O
--+---+--
O| X| O
--+---+--
X| 7| X
I've debugged the minmax algorithm as much as I can, but it's getting really hard to follow what's going on.
Here's the ComputerPlayer class with the algorithm (and without all my print statements). The minmax method is where I'm having a lot of trouble. (I'm not 100% sure on using worst_score or even the associated logic.)
class ComputerPlayer < Player
def move(game_board)
minmax(game_board) #minmax to create #best_move
game_board.place_piece(#best_move, marker)
end
def minmax(board, player_tracker = 0)
if board.game_over?
return score(board)
else
worst_score = (1.0/0.0) #Infinity
best_score = -(1.0/0.0) #-Infinity
#best_move = board.get_available_positions.first
new_marker = player_tracker.even? ? 'O' : 'X'
player_tracker += 1
board.get_available_positions.each do |move|
new_board = board.place_piece(move, new_marker)
current_score = minmax(new_board, player_tracker)
if new_marker == marker #if the player is the computer player
if current_score > best_score
#best_move = move
best_score = current_score
end
else
if current_score < worst_score
worst_score = current_score
end
end
end
end
return best_score
end
def score(board)
if board.winner == "O" #'O' == 'O', 'nil' == 'O'
10
elsif board.winner == "X" #'X' != 'O', 'nil' != 'O'
-10
elsif board.winner == nil
0
end
end
end
The problem is that minmax always returns best_score.
The minmax routine constantly toggles between the two players. When the current player being simulated is the computer player, then the best score is the highest score, when the current player being simulated is the human player, then the best score is the lowest score.
I rewrote the routine to try all remaining moves for an iteration and keep track of the corresponding score in a local hash. When finished, the best score is returned and the best move is set, depending on the currently simulated player.
def minmax(board, player_tracker = 0, iteration = 0) #minmax
if board.game_over?
return score(board, iteration)
end
new_marker = player_tracker.even? ? 'O' : 'X'
scores = {}
board.get_available_positions.each do |move|
new_board = board.place_piece(move, new_marker)
scores[move] = minmax(new_board, player_tracker + 1, iteration + 1)
end
if player_tracker.even?
#best_move = scores.sort_by {|_key, value| value}.reverse.to_h.keys[0]
else
#best_move = scores.sort_by {|_key, value| value}.to_h.keys[0]
end
return scores[#best_move]
end
To even increase accuracy, I rewrote the score routine to also consider the iterations needed to create the board to score. Being able to win in 1 iteration should be preferred over winning in 3 iterations, right?
def score(board, iteration)
# "O", "X", "nil"
if board.winner == "O" #'O' == 'O', 'nil' == 'O'
10.0 / iteration
elsif board.winner == "X" #'X' != 'O', 'nil' != 'O'
-10.0 / iteration
elsif board.winner == nil
0
else
raise "ERROR"
end
end
With these 2 routines replaces, the steps taken by the computer seem much more logical.

Resources