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()