import sys
from curtsies.fmtfuncs import *
from curtsies import FullscreenWindow, Input, fsarray
class Board:
"""
>>> Board().rows
((' ', ' ', ' '), (' ', ' ', ' '), (' ', ' ', ' '))
>>> Board().columns
((' ', ' ', ' '), (' ', ' ', ' '), (' ', ' ', ' '))
>>> Board().turn
0
>>> Board().whose_turn
'x'
>>> b = Board().move(2); print(b)
| |x
-----
| |
-----
| |
>>> b.possible()
[< Board |o.x......| >, < Board |.ox......| >, < Board |..xo.....| >, < Board |..x.o....| >, < Board |..x..o...| >, < Board |..x...o..| >, < Board |..x....o.| >, < Board |..x.....o| >]
"""
def __init__(self, width=3, height=3):
self._rows = [[' ' for _ in range(width)] for _ in range(height)]
rows = property(lambda self: tuple(tuple(row) for row in self._rows))
columns = property(lambda self: tuple(zip(*self._rows)))
spots = property(lambda self: tuple(c for row in self._rows for c in row))
def __str__(self):
return ('\n'+'-'*(len(self.columns)*2-1) + '\n').join(['|'.join(row) for row in self._rows])
def __repr__(self): return '< Board |'+''.join(self.spots).replace(' ','.')+'| >'
@property
def turn(self):
return 9 - self.spots.count(' ')
@property
def whose_turn(self):
return 'xo'[self.turn % 2]
def winner(self):
"""Returns either x or o if one of them won, otherwise None"""
for c in 'xo':
for comb in [(0,3,6), (1,4,7), (2,5,8), (0,1,2), (3,4,5), (6,7,8), (0,4,8), (2,4,6)]:
if all(self.spots[p] == c for p in comb):
return c
return None
def move(self, pos):
if not self.spots[pos] == ' ': raise ValueError('That spot it taken')
new = Board(len(self.rows), len(self.columns))
new._rows = list(list(row) for row in self.rows)
new._rows[pos // 3][pos % 3] = self.whose_turn
return new
def possible(self):
return [self.move(p) for p in range(len(self.spots)) if self.spots[p] == ' ']
def display(self):
colored = {' ':' ', 'x':blue(bold('x')), 'o':red(bold('o'))}
s = ('\n'+green('-')*(len(self.columns)*2-1) + '\n').join([green('|').join(colored[mark] for mark in row) for row in self._rows])
a = fsarray([bold(green('enter a number, 0-8' if self.whose_turn == 'x' else 'wait for computer...'))] + s.split('\n'))
return a
def opp(c):
"""
>>> opp('x'), opp('o')
('o', 'x')
"""
return 'x' if c == 'o' else 'o'
def value(board, who='x'):
"""Returns the value of a board
>>> b = Board(); b._rows = [['x', 'x', 'x'], ['x', 'x', 'x'], ['x', 'x', 'x']]
>>> value(b)
1
>>> b = Board(); b._rows = [['o', 'o', 'o'], ['o', 'o', 'o'], ['o', 'o', 'o']]
>>> value(b)
-1
>>> b = Board(); b._rows = [['x', 'o', ' '], ['x', 'o', ' '], [' ', ' ', ' ']]
>>> value(b)
1
>>> b._rows[0][2] = 'x'
>>> value(b)
-1
"""
w = board.winner()
if w == who:
return 1
if w == opp(who):
return -1
if board.turn == 9:
return 0
if who == board.whose_turn:
return max([value(b, who) for b in board.possible()])
else:
return min([value(b, who) for b in board.possible()])
def ai(board, who='x'):
"""
Returns best next board
>>> b = Board(); b._rows = [['x', 'o', ' '], ['x', 'o', ' '], [' ', ' ', ' ']]
>>> ai(b)
< Board |xo.xo.x..| >
"""
return sorted(board.possible(), key=lambda b: value(b, who))[-1]
def main():
with Input() as input:
with FullscreenWindow() as window:
b = Board()
while True:
window.render_to_terminal(b.display())
if b.turn == 9 or b.winner():
c = input.next() # hit any key
sys.exit()
while True:
c = input.next()
if c == '':
sys.exit()
try:
if int(c) in range(9):
b = b.move(int(c))
except ValueError:
continue
window.render_to_terminal(b.display())
break
if b.turn == 9 or b.winner():
c = input.next() # hit any key
sys.exit()
b = ai(b, 'o')
if __name__ == '__main__':
main()