Invincible bot actually works

This commit is contained in:
Alexander Munch-Hansen 2018-01-23 02:04:38 +01:00
parent 9376c1f4fb
commit 33d73dbf7b
4 changed files with 191 additions and 118 deletions

68
board.rb Normal file
View File

@ -0,0 +1,68 @@
class Array
def same_values?
self.uniq.length == 1
end
def contains? x
self.include? x
end
end
class Board
attr_reader :spaces, :def_val
def initialize
@def_val = "---"
@spaces = Array.new(9, @def_val)
end
def to_s
"|#{@spaces[0]}|#{@spaces[1]}|#{@spaces[2]}|
-------------
|#{@spaces[3]}|#{@spaces[4]}|#{@spaces[5]}|
-------------
|#{@spaces[6]}|#{@spaces[7]}|#{@spaces[8]}|
-------------"
end
def get_free
@spaces.each_index.select { |s| @spaces[s] == @def_val }
end
def set val, idx
@spaces[idx] = val
end
def remove idx
@spaces[idx] = @def_val
end
def any_winner?
winning_combos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
winning_combos.each do |s|
tmp = s.map {|i| @spaces[i]}
if (not tmp.include? @def_val) and tmp.same_values? then
return true
end
end
return false
end
def is_winner? player
winning_combos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
winning_combos.each do |s|
tmp = s.map {|i| @spaces[i]}
if (not tmp.include? @def_val) and tmp.same_values? and tmp[0] == player then
return true
end
end
return false
end
def is_full?
get_free.empty?
end
end

67
bot.rb Normal file
View File

@ -0,0 +1,67 @@
class Bot
attr_reader :best_choice
def initialize board, piece
@piece = piece
@opponent = switch piece
end
def move board
return "Game is done!" if board.is_full?
return "Someone won!" if board.any_winner?
# move = @board.get_free.sample
# @board.set " O ", move
minmax board, @piece
board.set @piece, @best_choice
end
def minmax board, cur_player
return score(board) if game_done?(board)
scores = {}
board.get_free.each do |space|
# Copy board so we don't mess up original
potential_board = board.dup
potential_board.set cur_player, space
scores[space] = minmax(potential_board, switch(cur_player))
potential_board.remove space
end
@best_choice, best_score = best_choice cur_player, scores
best_score
end
def switch cur
cur == " X " ? " O " : " X "
end
def game_done? board
board.any_winner? || board.is_full?
end
def best_choice piece, scores
if piece == @piece then
#p scores
scores.max_by { |_k, v| v}
else
scores.min_by { |_k, v| v}
end
end
def score board
#p @piece
#p @opponent
if board.is_winner? @piece
return 10
elsif board.is_winner? @opponent
return -10
end
0
end
end

21
human.rb Normal file
View File

@ -0,0 +1,21 @@
class Human
attr_reader :piece
def initialize board, piece
@board = board
@piece = piece
end
def move
print "\nPick a move!\n"
free_moves = @board.get_free
idx = gets.strip.to_i
if not free_moves.include? idx then
print "Square is already taken, try again!"
move
else
@board.set @piece, idx
end
end
end

151
ticcy.rb
View File

@ -1,130 +1,47 @@
# coding: utf-8
DEF = " "
PLAYS = [DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF, DEF]
SCENARIOS = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
COM = "COM"
COM_VAL = " O "
PLAYER = "PLAYER"
GAME_DONE = false
PLAYER_VAL = " X "
require './board'
require './bot'
require './human'
@board = Board.new
class Array
def same_values?
self.uniq.length == 1
end
end
def check_win board, player
cur_play = player == PLAYER ? PLAYER_VAL : COM_VAL
SCENARIOS.each do |s|
tmp = s.map {|i| board[i]}
# p board
if (not tmp.include? " ") and tmp.same_values? and tmp[0] == cur_play then
# p tmp
# p SCENARIOS
return true
else
return false
end
end
end
def check_full board
if not board.include? " " then
return true
else
return false
end
end
def draw_board board
return "
|#{board[0]}|#{board[1]}|#{board[2]}|
-------------
|#{board[3]}|#{board[4]}|#{board[5]}|
-------------
|#{board[6]}|#{board[7]}|#{board[8]}|
-------------"
end
def set player, idx
if player == PLAYER
PLAYS[idx] = PLAYER_VAL
elsif
PLAYS[idx] = COM_VAL
end
end
def ai_turn board, player, depth=0
if (check_win board, player == PLAYER ? COM : PLAYER) then
return -10 + depth
end
if check_full board then
return 0
end
max_seen = -(Float::INFINITY)
index = 0
value = player == PLAYER ? PLAYER_VAL : COM_VAL
(0...9).each do |x|
if board[x] == DEF then
new_board = board.dup
new_board[x] = value
lol = player == PLAYER ? COM : PLAYER
move_eval = -(ai_turn new_board, lol, depth+1)
if move_eval > max_seen then
# p [move_eval, x]
max_seen = move_eval
index = x
end
end
end
if depth == 0 then
set COM, index
end
return max_seen
end
def play_game
def play_game player, bot
while true do
# system "clear"
# if (check_win PLAYS, PLAYER) == true then
# print "We have a winner!\n"
# break
# end
# if check_full board then
# print "Game over!\n"
# break
# end
print @board.to_s
player.move
print draw_board PLAYS
puts "\nPick one between 0-8"
choice = gets
if PLAYS[choice.to_i] != " " then
print "Space is already taken"
else
set PLAYER, choice.to_i
ai_turn PLAYS, COM
if @board.is_full? then
print @board.to_s
print "Game is done!\n"
break
end
if @board.any_winner? then
print @board.to_s
print "Someone won!\n"
break
end
bot.move @board
if @board.is_full? then
print @board.to_s
print "Game is done!\n"
break
end
if @board.any_winner? then
print @board.to_s
print "Someone won!\n"
break
end
end
end
play_game
player = Human.new @board, " X "
bot = Bot.new @board, " O "
play_game player, bot