Last active
June 25, 2021 20:38
-
-
Save airicbear/abaad8462dd2c3b04b9de623ce5c8fc9 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 | |
# 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