Skip to content

Instantly share code, notes, and snippets.

@havenwood
Created December 7, 2021 20:03
Show Gist options
  • Save havenwood/7010dc226bcf18e9831beaf3805b60ed to your computer and use it in GitHub Desktop.
Save havenwood/7010dc226bcf18e9831beaf3805b60ed to your computer and use it in GitHub Desktop.
Matrix Tic Tac Toe for fun
# frozen_string_literal: true
require 'matrix'
class TicTacToe
BOARD_SIZE = 3
MARKS = {x: -1, o: 1}.freeze
private_constant :MARKS
ROW_MOVES = %i[top middle bottom].freeze
private_constant :ROW_MOVES
COLUMN_MOVES = %i[left middle right].freeze
private_constant :COLUMN_MOVES
attr_reader :board
def initialize
@board = new_board
@turns = MARKS.keys.cycle
end
def new_board
Matrix.zero(BOARD_SIZE)
end
def move(mark, row, column)
validate_turn(mark)
validate_moves(row, column)
self[ROW_MOVES.index(row), COLUMN_MOVES.index(column)] = mark
{win?: win?(mark), board: @board}
end
def []=(column, row, mark)
validate_mark(mark)
@board[row, column] = MARKS.fetch(mark)
@turns.next
end
def win?(mark)
straight_win?(mark) || diagonal_win?(mark)
end
private
def validate_mark(mark)
unless MARKS.key?(mark)
expected = MARKS.keys.map { |key| "`#{key.inspect}'" }.join(', ')
raise ArgumentError,
"given `#{mark.inspect}', expected #{expected}"
end
end
def validate_turn(mark)
unless mark == @turns.peek
expected =
raise ArgumentError,
"`#{mark.inspect}' given, expected `#{@turns.peek.inspect}' due to turn"
end
end
def validate_moves(row, column)
[[ROW_MOVES, row], [COLUMN_MOVES, column]].each do |possible_moves, given_move|
next if possible_moves.include?(given_move)
expected = possible_moves.map { |move| "`#{move.inspect}'" }.join(', ')
raise ArgumentError,
"given `#{given_move.inspect}', expected #{expected}"
end
end
def straight_win?(mark)
%i[row_vectors column_vectors].any? do |direction|
@board.public_send(direction).any? do |vectors|
vectors.sum == MARKS.fetch(mark) * BOARD_SIZE
end
end
end
def diagonal_win?(mark)
[@board, rotated_board].any? do |board_orientation|
board_orientation.each(:diagonal).sum == MARKS.fetch(mark) * BOARD_SIZE
end
end
def rotated_board
Matrix[*@board.to_a.map(&:reverse).transpose]
end
end
game = TicTacToe.new
game.move(:x, :top, :right)
game.move(:o, :top, :middle)
game.move(:x, :middle, :middle)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment