Skip to content

Instantly share code, notes, and snippets.

@airicbear
Last active June 25, 2021 20:38
Show Gist options
  • Save airicbear/abaad8462dd2c3b04b9de623ce5c8fc9 to your computer and use it in GitHub Desktop.
Save airicbear/abaad8462dd2c3b04b9de623ce5c8fc9 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
# The game engine
class Engine
def initialize(running)
@running = running
end
def game_loop(&update)
update.call while @running
end
def stop
@running = false
end
end
# TicTacToe 3x3 board
class Board
BLANK = ' '
def initialize(state = Array.new(9, BLANK))
@state = state
end
def mark(position, marker)
if @state[position - 1] == BLANK
@state[position - 1] = marker
return true
end
false
end
def to_s
@state.each_slice(3).reduce { |memo, x| "#{memo}\n#{x}" }
end
def [](index)
@state[index - 1]
end
def full?
@state.none?(BLANK)
end
def values_at(values)
@state.values_at(*(values.map { |e| e - 1 }))
end
end
# TicTacToe player
class Player
attr_accessor :name
attr_reader :marker
def initialize(name, marker)
@name = name
@marker = marker
end
end
# The TicTacToe game
class Game
attr_reader(:player1, :player2)
POSSIBLE_MOVES = (1..9).freeze
WINNING_COMBOS = [
[1, 2, 3], [4, 5, 6], [7, 8, 9],
[1, 4, 7], [2, 5, 8], [3, 6, 9],
[1, 5, 9], [3, 5, 7]
].freeze
INVALID_MOVE_OUT_OF_BOUNDS = 'Invalid move (must be between [1-9]).'
INVALID_MOVE_OCCUPIED = "You can't place there. Try again."
DRAW = 'DRAW!'
def initialize(engine = Engine.new(true), board = Board.new)
@player1 = Player.new('Player 1', 'X')
@player2 = Player.new('Player 2', 'O')
@current_player = @player1
@engine = engine
@board = board
end
def start
@engine.game_loop do
update
end
end
private
def quit(player)
puts "Game over. #{player.name} quit." unless player.nil?
@engine.stop
-1
end
def restart
@board = Board.new
start
end
def prompt(question)
print question
gets.chomp
end
def winning_board?(player, move)
WINNING_COMBOS.select { |combo| combo.include?(move) }.each do |combo|
win = true
combo.each do |index|
win = false unless @board[index] == player.marker
end
return win if win == true
end
false
end
def get_move(player)
input = prompt("#{player.name} [#{POSSIBLE_MOVES}, q]: ")
return quit(player) if input == 'q'
move = input.to_i
if POSSIBLE_MOVES.include?(move)
move
else
puts INVALID_MOVE_OUT_OF_BOUNDS
get_move(player)
end
end
def switch_player
@current_player = @current_player == @player1 ? @player2 : @player1
end
def play_again
input = prompt('Do you want to play again? [y/n]: ')
case input
when 'y'
restart
when 'n'
quit(nil)
else
play_again
end
end
def game_over(&action)
puts
puts @board
action.call
play_again
end
def check_win_condition(move)
if winning_board?(@current_player, move)
game_over do
puts "#{@current_player.name} won!"
end
elsif @board.full?
game_over do
puts DRAW
end
end
false
end
def update
puts
puts @board
position = get_move(@current_player)
marked = @board.mark(position, @current_player.marker)
puts INVALID_MOVE_OCCUPIED unless check_win_condition(position) == false && marked
switch_player if marked
end
end
def play
game = Game.new
game.start
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment