Skip to content

Instantly share code, notes, and snippets.

@stephenroller
Created July 23, 2012 14:46
Show Gist options
  • Save stephenroller/3163995 to your computer and use it in GitHub Desktop.
Save stephenroller/3163995 to your computer and use it in GitHub Desktop.
2 Player Dots and Boxes
#!/usr/bin/env python
import curses, sys, re
from time import sleep
from random import randint
OFFSET_X, OFFSET_Y = 2, 1
BOARD_SIZE = 7
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# init curses
stdscr = curses.initscr()
curses.noecho()
curses.start_color()
stdscr.keypad(1)
curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_RED)
def init_board():
# The structure of the board is a bit confusing, but it looks like this
# The even indexed rows have fewer values because there are fewer possible
# lines on them.
# o---o o [ [ True, False ],
# | [ True, False, False],
# o o---o [ False, True ],
# | [ False, True, False],
# o---o---o [ True, True] ]
even = [False for i in xrange(BOARD_SIZE - 1)]
odd = [False for i in xrange(BOARD_SIZE)]
board = []
for i in xrange(BOARD_SIZE * 2 - 1):
if i % 2 == 0:
board.append(even[:])
else:
board.append(odd[:])
return board
def init_score():
# The scorecard is a little bit redundant, but makes things easier
# Its size is (BOARD_SIZE-1)^2 because we have to store more lines than
# dots.
#
# o - o o [
# | 1 | [ 1, None ],
# o - o - o
# | 2 | [ None, 2 ]
# o o - o ]
#
return [[None for a in xrange(BOARD_SIZE-1)] for b in xrange(BOARD_SIZE-1)]
def closest_free(board, x, y):
def distance(x1, y1, x2, y2):
from math import sqrt
return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
distances = []
for i,row in enumerate(board):
for j,val in enumerate(row):
if val == False:
distances.append((distance(x, y, i, j), i, j))
if len(distances) == 0:
return False
distances.sort(key=lambda x: x[0])
return distances[0][1:]
def first_available_move(board):
try:
return closest_free(board, 0, 0)
except IndexError:
return False
def draw_dot(x, y):
sx = OFFSET_X + x * 4
sy = OFFSET_Y + y
stdscr.addstr(sy, sx, "o")
stdscr.addstr(sy, sx + 4, "o")
def draw_line(x, y, color=0):
# print "%d, %d" % (x, y)
if y % 2 == 0:
sy = OFFSET_Y + y
sx = OFFSET_X + x * 4 + 1
stdscr.addstr(sy, sx, '---', curses.color_pair(color))
else:
sy = OFFSET_Y + y
sx = OFFSET_X + x * 4
stdscr.addstr(sy, sx, "|", curses.color_pair(color))
def draw_filling(x, y, player):
screen_x = OFFSET_X + x * 4 + 2
screen_y = OFFSET_Y + y * 2 + 1
stdscr.addstr(screen_y, screen_x, str(player), curses.color_pair(player))
def play_game():
board = init_board()
score = init_score()
draw_board(board, score)
current_player = 1 # alternates between 1 and 2
def check_and_set_square(x, y):
if score[x][y]:
return 0
if (board[x * 2][y] and # above
board[x * 2 + 1][y] and # left
board[x * 2 + 1][y + 1] and # right
board[x * 2 + 2][y]): # bottom
score[x][y] = current_player
return 1
return 0
selected_x, selected_y = 0, 0
while first_available_move(board):
y = OFFSET_Y + BOARD_SIZE * 2 + 1
# Let's handle the selector:
selected_x, selected_y = closest_free(board, selected_x, selected_y)
# Read from the keyboard
c = None
while c != ord("\n") or board[selected_x][selected_y]:
stdscr.clear()
draw_board(board, score)
color = board[selected_x][selected_y] and 3 or current_player
draw_line(selected_y, selected_x, color)
c = stdscr.getch()
if c == curses.KEY_UP:
size = selected_x % 2 == 0 and 2 * BOARD_SIZE - 1 or 2 * BOARD_SIZE
selected_x = (selected_x - 1) % size
if selected_y >= len(board[selected_x]):
selected_y = len(board[selected_x]) - 1
elif selected_y == len(board[selected_x]) - 2 and selected_x % 2 == 1:
selected_y += 1
elif c == curses.KEY_DOWN:
size = selected_x % 2 == 0 and 2 * BOARD_SIZE - 1 or 2 * BOARD_SIZE
selected_x = (selected_x + 1) % size
if selected_y >= len(board[selected_x]):
selected_y = len(board[selected_x]) - 1
elif selected_y == len(board[selected_x]) - 2 and selected_x % 2 == 1:
selected_y += 1
elif c == curses.KEY_LEFT:
size = selected_x % 2 == 0 and BOARD_SIZE - 1 or BOARD_SIZE
selected_y = (selected_y - 1) % size
elif c == curses.KEY_RIGHT:
size = selected_x % 2 == 0 and BOARD_SIZE - 1 or BOARD_SIZE
selected_y = (selected_y + 1) % size
x1,y1 = selected_x,selected_y
filled_boxes = 0
board[selected_x][selected_y] = True
if selected_x % 2 == 0:
# Now it's time to check for new boxes
# o o
#
# o---o A new horizontal line can only fill in above
# or below
# o o
if x1 > 0:
# check above
filled_boxes += check_and_set_square((x1-1)/2, y1)
if (x1-1)/2+1 < BOARD_SIZE-1:
# check below
filled_boxes += check_and_set_square((x1-1)/2+1, y1)
else:
# Now it's time to check for new boxes
# o o o A new vertical line can only fill in
# | on the left or right
# o o o
if y1 > 0:
# check left
filled_boxes += check_and_set_square((x1-1)/2, y1-1)
if y1 < BOARD_SIZE - 1:
# check right
filled_boxes += check_and_set_square((x1-1)/2, y1)
if filled_boxes == 0:
# switch players
current_player = current_player % 2 + 1
draw_board(board, score)
stdscr.refresh()
def draw_board(board, scores):
# +-------> y-axis
# | 1 2
# | A . .
# | B . .
# v
# x-axis
#
# draw the headers
# now draw the board
for i, row in enumerate(board):
for j, column in enumerate(row):
# print "here %d, %d" % (i, j)
if i % 2 == 0:
draw_dot(j, i)
if column:
draw_line(j, i)
stdscr.refresh()
# We need to draw the letters in the boxes too and add the score
player1, player2 = 0, 0
for i, row in enumerate(scores):
for j, score in enumerate(row):
if score == 1: player1 += 1
if score == 2: player2 += 1
if score:
draw_filling(j, i, score)
# Draw the score board
screen_y = OFFSET_Y + BOARD_SIZE * 2
screen_x = OFFSET_X
stdscr.addstr(screen_y, screen_x, "Player 1: %d" % player1,
curses.color_pair(1))
stdscr.addstr(screen_y + 1, screen_x, "Player 2: %d" % player2,
curses.color_pair(2))
stdscr.refresh()
if __name__ == '__main__':
try:
play_game()
except KeyboardInterrupt:
pass
finally:
curses.endwin()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment