diff --git a/tictactoe.py b/tictactoe.py new file mode 100644 index 0000000..c8cd073 --- /dev/null +++ b/tictactoe.py @@ -0,0 +1,157 @@ +import random + +class Player(object): + + def __init__(self,rep): + self.rep = rep + + def __repr__(self): + return self.rep + + def count_win_chances(self, board): + chances = 0 + for line in board.get_all_lines(): + if all( map( lambda v: v == self or v == Board.empty, line) ): + chances += 1 + return chances + +class Board(object): + draw = object() + empty = Player(" ") + def __init__(self,size=3): + self.board = [ [Board.empty] * size for i in range(size) ] + self.size = size + + def set(self, x, y, value): + if x < 0 or x >= self.size or y < 0 or y >= self.size: + raise Exception("Out of bounds!") + if value == Board.empty or isinstance(value,Player): + self.board[x][y] = value + else: + raise Exception("Invalid value %s" % str(value)) + + def get(self, x, y): + if x < 0 or x >= self.size or y < 0 or y >= self.size: + raise Exception("Out of bounds!") + else: + return self.board[x][y] + + def get_all_lines(self): + lines = [] + #diagonals + lines.append( [ self.board[xy][xy] for xy in range(self.size) ] ) + lines.append( [ self.board[x][self.size-x-1] for x in range(self.size) ] ) + + #rows + lines += self.board + + #cols + lines += [ [ self.board[x][y] for x in range(self.size) ] for y in range(self.size) ] + + return lines + + def get_empty_fields(self): + for x in range(self.size): + for y in range(self.size): + if self.board[x][y] == Board.empty: + yield (x,y) + + def get_winner(self): + for line in self.get_all_lines(): + if line[0] != self.empty and all(map(lambda v: v == line[0],line)): + return line[0] + for field in self.get_empty_fields(): + return None + return Board.draw + + def copy(self): + c = Board(size=self.size) + for x in range(self.size): + for y in range(self.size): + c.set(x,y, self.get(x,y)) + return c + + def __str__(self): + return ("\n-"+("*-"*(self.size-1)) +"\n").join( [ "|".join( [ str(pos) for pos in line ] ) for line in self.board ] ) + + +class AI(Player): + def play(self,enemy, board): + if board.get_winner() is not None: + raise Exception("Game over!") + boardcopy = board.copy() + + moves = [] + + #Check for win chance + for x,y in board.get_empty_fields(): + boardcopy.set(x,y,self) + # Check for win + if boardcopy.get_winner() == self: + return (x,y) + + # Check for chance + chance = self.count_win_chances(boardcopy) - enemy.count_win_chances(boardcopy) + + moves.append( (chance,(x,y)) ) + + + # Check if enemy win + boardcopy.set(x,y,enemy) + + if boardcopy.get_winner() == enemy: + return (x,y) + + boardcopy.set(x,y,Board.empty) + + moves.sort(key=lambda x: -x[0]) + + best_move = moves[0] + + good_moves = [] + + for move in moves: + if move[0] == best_move[0]: + good_moves.append(move) + + return random.choice(good_moves)[1] + +class Human(Player): + def play(self, enemy, board): + while True: + s = input("Player %s's turn:") + try: + (x,y) = map(int, s.split(",")) + return (x,y) + except Exception: + print("Try again?") + +def rotate(l): + return l[1:] + l[:1] + +if __name__ == "__main__": + board = Board(size=6) + players = [ AI("X"), AI("O") ] + + print(str(board)) + + while board.get_winner() is None: + (x,y) = players[0].play(players[1],board) + while board.get(x,y) != Board.empty: + print("Try again!") + (x,y) = players[0].play(players[1],board) + + board.set(x,y,players[0]) + players = rotate(players) + + print() + print(str(board)) + + winner = board.get_winner() + + if winner == Board.draw: + print("Round draw!") + else: + print("%s has won!" % str(winner) ) + +