#!/usr/bin/env python3 from itertools import chain class Game: """Abstract base class for games""" def finished(self): """Returns True if no more moves are to be played, False otherwise.""" raise NotImplementedError() def current_player(self): """Return an int corresponding to the player who has to do the next move. Returns 0 or 1 for player1 or player2 respectively.""" raise NotImplementedError() def move(self, move): """Make the move denoted by the string 'move'. Return True if the move was valid and False otherwise. In most cases the moves should look like the following: 'e17'.""" raise NotImplementedError() def eval(self): """Return the result of the game as integer: 0 ... draw; >0 player1 won; <0 player2 won. This may only be called if self.finished() is True.""" raise NotImplementedError() def __str__(self): raise NotImplementedError() class Player: """Abstract base class for players""" def compute_move(self, game): """Create a move for the game 'game' and return it. Moves are strings which look like 'e17' most of the time.""" raise NotImplementedError() class TicTacToe(Game): """The constants are 0, 1 and 2 for empty and the two players respectively. """ def __init__(self): self.board = [[0 for _ in range(3)] for _ in range(3)] self.winner = 0 self.moves = 0 def __str__(self): lines = ["|".join([" ", "X", "O"][pos] for pos in row) for row in self.board] lines = reversed(["{} {}".format(num + 1, line) for num, line in enumerate(lines)]) return "\n -----\n".join(lines) + "\n a b c" def current_player(self): return self.moves % 2 def determine_winner(self): """Internal function to find out whether there is a winner. Returns 0, 1 or 2.""" lanes = chain(self.board, zip(*self.board), [[row[i] for i, row in enumerate(self.board)], [list(reversed(row))[i] for i, row in enumerate(self.board)]]) for lane in lanes: for player in [1, 2]: if all(x == player for x in lane): return player return 0 def finished(self): return self.moves >= 9 or self.winner def move(self, move): try: column = ord(move[0]) - 97 row = int(move[1]) - 1 except (IndexError, ValueError): print("Unable to parse move.") return False try: if self.board[row][column]: print("Allready taken.") return False except (IndexError): print("Outside of board.") return False self.board[row][column] = self.current_player() + 1 self.moves += 1 self.winner = self.determine_winner() return True def eval(self): if not self.finished(): raise RuntimeError("Game hasn't finished yet.") translate = {0:0, 1:1, 2:-1} return translate[self.winner] class HumanPlayer(Player): def compute_move(self, game): print(game) return input("Your move: ") def match(game, player1, player2, quiet=False): """Play a match of 'game' between 'player1' and 'player2'.""" players = [player1, player2] while not game.finished(): game.move(players[game.current_player()].compute_move(game)) if not quiet: print(game) winner = game.eval() if not quiet: if not winner: print("Draw") elif winner > 0: print("Player1 won the game with {} point(s)".format(winner)) else: print("Player2 won the game with {} point(s)".format(-winner)) return winner def compete(gamecls, ai1, ai2, runs=1000): results12 = [0, 0] results21 = [0, 0] for _ in range(runs): res = match(gamecls(), ai1, ai2, quiet = True) if res: if res > 0: results12[0] += 1 else: results12[1] += 1 for _ in range(runs): res = match(gamecls(), ai2, ai1, quiet = True) if res: if res > 0: results21[0] += 1 else: results21[1] += 1 print("In {} runs the result is {}:{} for AI1 starting and {}:{} for AI2 starting".format(runs, results12[0], results12[1], results21[0], results21[1])) if __name__ == "__main__": match(TicTacToe(), HumanPlayer(), HumanPlayer())