Skip to content

Instantly share code, notes, and snippets.

@ryanbraganza
Created May 7, 2016 17:44
Show Gist options
  • Select an option

  • Save ryanbraganza/b3916283bbfc4a3d99c44695e1b7906d to your computer and use it in GitHub Desktop.

Select an option

Save ryanbraganza/b3916283bbfc4a3d99c44695e1b7906d to your computer and use it in GitHub Desktop.
connect4
#!/usr/bin/env ruby
# Usage:
#
# ruby connect4.rb test # run the tests
# ruby connect4.rb play # launch a CLI-based game
# ruby connect4.rb # Simulate a simple game
require 'set'
PLAYER_ONE = 'X'
PLAYER_TWO = 'O'
EMPTY = '*'
WIDTH = 7
HEIGHT = 6
class Connect4
attr_accessor :board, :current_player
def initialize(board: nil)
# TODO validate input
if board
self.board = board
else
# TODO consider supporting variable-sized boards
self.board = HEIGHT.times.map { WIDTH.times.map { EMPTY } }
end
@player_order = [PLAYER_ONE, PLAYER_TWO].cycle
self.current_player = @player_order.next
end
# Play into the given column
# Return true on success, false if the move is illegal
def play(col_num)
# TODO validate input and assert game not completed
column = board.transpose[col_num]
empty_row = column.rindex(EMPTY)
if empty_row
board[empty_row][col_num] = current_player
self.current_player = @player_order.next
true
else
false
end
end
def completed?
!!winner || board.all? { |row| !row.any? { |cell| cell == EMPTY } }
end
def to_s
output = ''
if completed?
if winner
output += "winner: #{winner}"
else
output += "Draw!"
end
else
output += "current player: #{current_player}\n"
end
output += "\n\n"
output += board.map do |row|
row.join
end.join("\n")
output
end
# Return the winner or nil
def winner
# TODO cache
# check horizontally
board.each do |row|
return PLAYER_ONE if row.join.match(PLAYER_ONE * 4)
return PLAYER_TWO if row.join.match(PLAYER_TWO * 4)
end
# check vertically
board.transpose.each do |col|
return PLAYER_ONE if col.join.match(PLAYER_ONE * 4)
return PLAYER_TWO if col.join.match(PLAYER_TWO * 4)
end
# check diagonally
board.each_with_index do |row, m|
row.each_with_index do |cell, n|
# searching down-right
if n + 3 < row.length && m + 3 < board.length
potential_match = [cell, board[m+1][n+1], board[m+2][n+2], board[m+3][n+3]].join
return PLAYER_ONE if (potential_match == PLAYER_ONE * 4)
return PLAYER_TWO if (potential_match == PLAYER_TWO * 4)
end
# searching down-left
if n - 3 >= 0 && m + 3 < board.length
potential_match = [cell, board[m+1][n-1], board[m+2][n-2], board[m+3][n-3]].join
return PLAYER_ONE if (potential_match == PLAYER_ONE * 4)
return PLAYER_TWO if (potential_match == PLAYER_TWO * 4)
end
end
end
# no winner
nil
end
end
class ConsoleGame
def initialize
@connect4 = Connect4.new
end
def run
puts @connect4
while !@connect4.completed?
column = prompt_column
@connect4.play(column)
puts @connect4
end
puts 'game over!'
end
private
def prompt_column
loop do
print "Enter a column [0 - #{WIDTH}]> "
user_input = $stdin.readline
begin
int = Integer(user_input)
p int
if (0...WIDTH).cover? int
return int
end
rescue ArgumentError
end
end
end
end
if ARGV.include? 'test'
require 'minitest/autorun'
class TestConnect4 < Minitest::Test
def test_initial_game
g = load_game('
*******
*******
*******
*******
*******
*******
')
assert_equal nil, g.winner
assert !g.completed?
end
def test_horizontal_winner
g = load_game('
*******
*******
*******
*******
**OO***
XXXXO**
')
assert_equal PLAYER_ONE, g.winner
assert g.completed?
end
def test_vertical_winner
g = load_game('
*******
*******
***XO**
**XOO**
*XOOO**
XOXXO**
')
assert_equal PLAYER_TWO, g.winner
assert g.completed?
end
def test_draw
g = load_game('
XXOOXXO
OXXOOXX
XOOXXXO
XXXOOOX
OXOOXXX
OOOXOOX
')
assert_equal nil, g.winner
assert g.completed?
end
private
def load_game str
rows = str.strip.split("\n")
grid = rows.map{ |row| row.strip.split('')}
Connect4.new(board: grid)
end
end
end
def main
return if ARGV.include?('test')
if ARGV.include?('play')
ConsoleGame.new.run
else
# Play a game where player one always plays the first column
# and player two always plays the second column
connect4 = Connect4.new
puts connect4
3.times do
connect4.play(0)
puts connect4
connect4.play(1)
puts connect4
end
# The winning move
connect4.play(0)
puts connect4
end
end
if __FILE__ == $0
main
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment