one-file-projects/tictactoe.py

205 lines
5.3 KiB
Python

import random
def rotate(l):
return l[1:] + l[:1]
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():
chances += self.win_chance_line(line)
return chances
def win_chance_line(self,line):
c = 1
for p in line:
if p == self:
c += 1
elif p != Board.empty:
return 0
return c
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 chance
chance = self.count_win_chances(boardcopy) - enemy.count_win_chances(boardcopy)
# Check for win
if boardcopy.get_winner() == self:
chance += 1000
# Check if enemy win
boardcopy.set(x,y,enemy)
if boardcopy.get_winner() == enemy:
chance += 500
boardcopy.set(x,y,Board.empty)
moves.append( (chance,(x,y)) )
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:" % self.rep)
try:
(x,y) = map(int, s.split(","))
return (x,y)
except Exception:
print("Try again?")
class Random(Player):
def play(self, enemy, board):
fields = list(board.get_empty_fields())
return random.choice(fields)
class Game(object):
def __init__(self, players, size=3):
self.size = size
self.players = players
self.reset()
def reset(self):
self.board = Board(size=self.size)
self.winner = None
def run(self,out=True):
if out:
print(str(self.board))
while self.board.get_winner() is None:
(x,y) = self.players[0].play(self.players[1], self.board)
while self.board.get(x,y) != Board.empty:
print("Fail")
(x,y) = self.players[0].play(players[1], self.board)
self.board.set(x,y,self.players[0])
self.players = rotate(self.players)
if out:
print()
print(str(self.board))
return self.board.get_winner()
def menu(prompt,choices):
print("> %s" % prompt)
for i,choice in enumerate(choices):
print("%d. %s" % (i,choice))
return int(input("Your choice: "))
if __name__ == "__main__":
c = menu("Playmode",["Ai vs Ai","Ai vs Random","Ai vs Player","Player vs Player"])
if c == 0:
players = [ AI("X"), AI("O") ]
elif c == 1:
players = [ AI("X"), Random("O") ]
elif c == 2:
players = [ Human("X"), AI("O") ]
elif c == 3:
players = [ Human("X"), Human("O") ]
else:
import sys
sys.exit(0)
game = Game(players)
winner = game.run()
if winner == Board.draw:
print("Round draw!")
else:
print("%s has won!" % str(winner) )