Last active
January 5, 2022 20:17
-
-
Save Niall47/fa78f955cd812ce87273e892471902e9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tictactoe | |
# Player records the symbol being used | |
class Player | |
attr_reader :symbol | |
def initialize(symbol) | |
@symbol = symbol | |
end | |
end | |
# Game handles the board and turn updates | |
class Game | |
attr_reader :board | |
def initialize(symbol1 = 'X', symbol2 = 'O', bot: false) | |
# Setup turn counter and create an array to represent the board | |
@turn = 0 | |
@board = Array.new(9, '-') | |
@player1 = Player.new(symbol1) | |
@player2 = Player.new(symbol2) | |
@bot = bot | |
end | |
def board | |
@board | |
end | |
def run | |
display_board | |
if @bot | |
puts 'You are playing a bot. ' | |
sleep 2 | |
end | |
until game_over? | |
update_board(take_input) | |
system 'clear' | |
display_board | |
break if game_over? | |
@turn += 1 | |
end | |
winner = win? | |
puts "Winner = #{current_player.symbol}" if winner | |
puts 'Draw, how boring' unless winner | |
puts 'Gonna cry?' if @bot && winner | |
end | |
private | |
def current_player | |
# always 2 players so we just check the turns to determine who's up | |
@turn.even? ? @player1 : @player2 | |
end | |
def win? | |
full_column? || full_row? || full_column? || full_diag? | |
end | |
def game_over? | |
# Check if current symbol has a winning combo, or if the board is full | |
full_board? || win? | |
end | |
def full_row? | |
# We count the number of player symbols in each row and | |
# return true if we find 3 of the same character in any of the arrays we iterate over | |
board.each_slice(3).any? { |line| line.count(current_player.symbol) == 3 } | |
end | |
def full_column? | |
# We split the board into a nested array of rows, then use this cool .transpose method which swaps the rows and columns | |
# we run map on the result and return true if we find 3 of the same character in the resulting array | |
board.each_slice(3).to_a.transpose.any? { |row| row.count(current_player.symbol) == 3 } | |
end | |
def full_diag? | |
# theres only two diagonal combinations, so no fancy loops here | |
# We check if the specific pieces all contain the player symbol | |
[board[0], board[4], board[8]].all? { |tile| tile == current_player.symbol } || [board[2], board[4], board[6]].all? { |tile| tile == current_player.symbol } | |
end | |
def full_board? | |
# If theres no more blank spaces on the board then its game over | |
board.none? { |value| value == '-' } | |
end | |
def display_board | |
puts " #{board[0]} | #{board[1]} | #{board[2]} " | |
puts ' ----------- ' | |
puts " #{board[3]} | #{board[4]} | #{board[5]} " | |
puts ' ----------- ' | |
puts " #{board[6]} | #{board[7]} | #{board[8]} " | |
end | |
def take_input | |
if @bot && current_player.symbol == 'X' | |
bot_move = Bot.move(board) | |
throw "The bot broke and returned #{bot_move} using board #{board}" unless input_valid?(bot_move) | |
bot_move | |
else | |
human_input | |
end | |
end | |
def human_input | |
# We ensure input_valid? returns false at least once to drop into the loop | |
# is this good practice? ¯\_(ツ)_/¯ | |
puts "Player #{current_player.symbol} Please enter a number between 1 and 9." | |
input = -1 | |
until input_valid?(input) | |
puts "Available inputs #{available_inputs}" | |
# We're taking away 1 because arrays start at 0 lmao | |
input = STDIN.gets.to_i - 1 | |
end | |
input | |
end | |
def update_board(location) | |
# update the board array with players symbol in their desired position | |
board[location] = current_player.symbol | |
end | |
def available_inputs | |
# iterate over board creating an array of the indexes for the values that matched for a empty tile and join them together | |
# we use an offest of 1 because of the whole arrays starting at 0 thing | |
board.map.with_index(1) { |n, i| i.to_s if n == '-' }.join(' ') | |
end | |
def input_valid?(input) | |
# is it between 1 and 9 and is it blank? | |
input.between?(0, 8) && board[input] == '-' | |
end | |
end | |
end | |
# takes the board and returns a position number in the array to play the bot's hand | |
module Bot | |
def self.move(board) | |
case board | |
when %w[- - - - - - - - -] | |
0 | |
when %w[X - - O - - - - -], %w[X - - - O - - - -], %w[X - - - - - O - -], | |
%w[X - X O X O - - O], %w[X - X - X O O - O], %w[X - X - X O - O O], | |
%w[X - X - - - O O -], %w[X - X O - - - O -], %w[X - X - O - - O -], | |
%w[X - X - - O - O -], %w[X - X - - - - O O], %w[X - X - O - - - O], | |
%w[X - X - - O - - O], %w[X - X O - - - - O], %w[X - X - - - O - O] | |
1 | |
when %w[X - - - - - - O -], %w[X - - - - - - - O], %w[X X - O O - - - -], | |
%w[X X - O - O - - -], %w[X X - O - - O - -], %w[X X - O - - - O -], | |
%w[X X - O - - - - O], %w[X - - - X O - - O], %w[X X - - O - O - -], | |
%w[X X - - O - - O -], %w[X X - - O O - - -], %w[X X - - - O - O], | |
%w[X X - - - - O - O], %w[X X - - - - O O -] | |
2 | |
when %w[X O - - - - - - -], %w[X - O - - - - - -], %w[X X O - O - X O -], | |
%w[X O X - O - X - O], %w[X O X - - - X O O], %w[X X O - O X X - -], | |
%w[X X O - O - X - O], %w[X X O - O O X - -] | |
3 | |
when %w[X - - - - O - - -], %w[X O - X - - O - -], %w[X - O X - - O - -], | |
%w[X X O O - - - - -], %w[X X O - - - O - -], %w[X O X - - - - O -], | |
%w[X O X O - - X - O] | |
4 | |
when %w[X O O X X - O - -], %w[X O - X X - O O -], %w[X O - X X - O - O], | |
%w[X - O X X - O O -], %w[X - O X X - O - O], %w[X X O O O X - - -], | |
%w[X X O O O - X - -] | |
5 | |
when %w[X O O X - - - - -], %w[X O - X O - - - -], %w[X O - X - O - - -], | |
%w[X O - X - - - O -], %w[X O - X - - - - O], %w[X - O X O - - - -], | |
%w[X - O X - O - - -], %w[X X O - O - - - -], %w[X O X - X O - - O], | |
%w[X O X - - - - - O], %w[X - O X - - - - O] | |
6 | |
when %w[X X O O X O - - -], %w[X X O O X - O - -], %w[X X O O X - - - O], | |
%w[X X O O O X X - O], %w[X X O - X O O - -], %w[X X O - X - O - O] | |
7 | |
when %w[X O - X X O O - -], %w[X - O X X - O - -], %w[X X O O X - - O -], | |
%w[X X O O O X X O -], %w[X - O - X O - - -], %w[X O - - X O - - -], | |
%w[X - - O X O - - -], %w[X - - - X O O - -], %w[X - - - X O - O -], | |
%w[X X O - X - O O -], %w[X - O X X O O - -] | |
8 | |
else | |
# If we dont know what move to make pick a random available slot | |
p 'Panic mode! Taking a guess' | |
sleep(2) | |
board.map.with_index { |n, i| i.to_s if n == '-' }.compact.sample.to_i | |
end | |
end | |
end | |
ARGV.first.nil? ? Tictactoe::Game.new.run : Tictactoe::Game.new(bot: true).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment