Skip to content

Instantly share code, notes, and snippets.

@Niall47
Last active January 5, 2022 20:17
Show Gist options
  • Save Niall47/fa78f955cd812ce87273e892471902e9 to your computer and use it in GitHub Desktop.
Save Niall47/fa78f955cd812ce87273e892471902e9 to your computer and use it in GitHub Desktop.
# 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