From 33d73dbf7b51936eef81272556125d9f43abce2d Mon Sep 17 00:00:00 2001 From: Alexander Munch-Hansen Date: Tue, 23 Jan 2018 02:04:38 +0100 Subject: [PATCH] Invincible bot actually works --- board.rb | 68 +++++++++++++++++++++++++ bot.rb | 67 ++++++++++++++++++++++++ human.rb | 21 ++++++++ ticcy.rb | 153 +++++++++++++------------------------------------------ 4 files changed, 191 insertions(+), 118 deletions(-) create mode 100644 board.rb create mode 100644 bot.rb create mode 100644 human.rb diff --git a/board.rb b/board.rb new file mode 100644 index 0000000..048efe3 --- /dev/null +++ b/board.rb @@ -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 diff --git a/bot.rb b/bot.rb new file mode 100644 index 0000000..0156461 --- /dev/null +++ b/bot.rb @@ -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 diff --git a/human.rb b/human.rb new file mode 100644 index 0000000..16011fe --- /dev/null +++ b/human.rb @@ -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 diff --git a/ticcy.rb b/ticcy.rb index 411065f..0675594 100644 --- a/ticcy.rb +++ b/ticcy.rb @@ -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" + # 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