Created
February 1, 2016 21:08
-
-
Save zeimusu/8568f3691bd96af504be to your computer and use it in GitHub Desktop.
A game of noughts and crosses,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Usage: | |
tictactoe : Play the graphical game | |
tictactoe t(ext) : play the text game, using the keypad | |
""" | |
import logging as log | |
import random, sys | |
import tkinter as tk | |
log.basicConfig(level=log.WARNING) | |
class App(tk.Tk): | |
def __init__(self, *args, **kwargs): | |
tk.Tk.__init__(self, *args, **kwargs) | |
self.canvas = tk.Canvas(self,width=320,height=320) | |
self.canvas.create_line(110,10,110,310) | |
self.canvas.create_line(210,10,210,310) | |
self.canvas.create_line(10,110,310,110) | |
self.canvas.create_line(10,210,310,210) | |
self.canvas.bind("<Button-1>",self.get_move) | |
self.canvas.pack() | |
self.game=Game() | |
# 0.5 chance of the computer starting | |
if random.random()<0.5: | |
self.computer_move() | |
def draw_winner(self): | |
"""Draw a bold line to show a winner""" | |
win,p1,p2 = self.game.winner() | |
if win == 0: | |
return None | |
centerx1, centery1 = self.pos2center(p1) | |
centerx2, centery2 = self.pos2center(p2) | |
# The next two lines shift the start and end points | |
# towards the edge of the board | |
centerx1 -= abs(centerx1-centerx2)/4 | |
centerx2 += abs(centerx1-centerx2)/4 | |
centery1 += abs(centery1-centery2)/4 | |
centery2 -= abs(centery1-centery2)/4 | |
self.canvas.create_line(centerx1,centery1, | |
centerx2,centery2, | |
width=3) | |
def pos2center(self,pos): | |
"""Utility function. convert an index in the array to | |
screen coordinates""" | |
x = pos % 3 | |
y = 2 - pos //3 | |
return x*100 + 60, y*100+60 | |
def get_move(self,event): | |
"""Callback for clicks on the board. | |
plays move, the after checking for wins (which can't happen) | |
finds computer's move""" | |
log.info("click at {}, {}".format((event.x-10)//100, | |
(event.y-10)//100)) | |
# The next lines convert the click coordinates to an index | |
# in the array | |
x, y = (event.x-10)//100, (event.y-10)//100 | |
# The clicks may be outside of the board, deal with this | |
x = max(min(2,x),0) | |
y = 2 - max(min(2,y),0) | |
pos = 3*y + x | |
if pos in self.game.get_available_moves(): | |
self.game.make_move(pos,-1) | |
self.draw("O",x,y) | |
if self.game.complete(): | |
# if the game is over, accept no further clicks. | |
self.canvas.unbind("<Button-1>") | |
self.draw_winner() | |
else: | |
self.computer_move() | |
def draw(self,symbol,x,y): | |
"""draws a symbol, X or O on the board, given coordinates | |
in x,y in {0,1,2}""" | |
y = 2 - y | |
centerx,centery = x*100 + 60, y*100 + 60 | |
# The bbox is an 80 pixel region that will be filled with | |
# the symbol. | |
bbox = (centerx-40, centery-40, centerx+40, centery+40) | |
if symbol == "X": | |
self.canvas.create_line(*bbox) | |
self.canvas.create_line(bbox[0],bbox[3],bbox[2],bbox[1]) | |
elif symbol == "O": | |
self.canvas.create_oval(bbox) | |
def computer_move(self): | |
"""Make a move for the computer""" | |
pos = self.determine(1) | |
self.game.make_move(pos,1) | |
x = pos % 3 | |
y = pos // 3 | |
self.draw("X",x,y) | |
if self.game.complete(): | |
self.canvas.unbind("<Button-1>") | |
self.draw_winner() | |
def determine(self,player): | |
"""Chose a move """ | |
a = -2 * player # why currently player == 1 | |
choices = [] | |
if len(self.game.get_available_moves()) == 9: | |
return random.randrange(0, 9) # first play random | |
for move in self.game.get_available_moves(): | |
self.game.make_move(move, player) | |
val = minimax(self.game, player * -1,-2,2,0) | |
log.debug(move, val) | |
self.game.make_move(move, 0) | |
if val * player > a: | |
log.debug(val, ">", a) | |
a = val * player | |
choices = [move] | |
elif val * player == a: | |
log.debug(val, "=", a) | |
choices.append(move) | |
log.debug(choices) | |
return random.choice(choices) | |
class Game(): | |
def __init__(self, game=[0, 0, 0, | |
0, 0, 0, | |
0, 0, 0]): | |
self.game = game # X = 1, O = -1 | |
self.subdict = {1: "X", 0: " ", -1: "O"} | |
def show_board(self): | |
xogame = [self.subdict[i] for i in self.game] | |
print() | |
print(" {} | {} | {}".format(*xogame[6:9])) | |
print("---+---+---") | |
print(" {} | {} | {}".format(*xogame[3:6])) | |
print("---+---+---") | |
print(" {} | {} | {}".format(*xogame[0:3])) | |
def get_available_moves(self): | |
return [i for (i, box) in enumerate(self.game) if box == 0] | |
def get_move(self): | |
available = [n+1 for n in self.get_available_moves()] | |
while True: | |
try: | |
move = int(input("Your move.. ")) | |
if move not in available: | |
raise ValueError | |
except ValueError: | |
print("Please choose a move from {}.".format(available)) | |
else: | |
break | |
return move - 1 | |
def complete(self): | |
win = self.winner()[0] | |
if win != 0: | |
return win | |
if len(self.get_available_moves()) == 0: | |
return True | |
else: | |
return False | |
def winner(self): | |
game = self.game | |
if game[0] == game[1] == game[2] != 0: | |
return game[0], 0, 2 | |
if game[3] == game[4] == game[5] != 0: | |
return game[3], 3, 5 | |
if game[6] == game[7] == game[8] != 0: | |
return game[6], 6, 8 | |
if game[0] == game[3] == game[6] != 0: | |
return game[0], 0, 6 | |
if game[1] == game[4] == game[7] != 0: | |
return game[1], 1, 7 | |
if game[2] == game[5] == game[8] != 0: | |
return game[2], 2, 8 | |
if game[0] == game[4] == game[8] != 0: | |
return game[0], 0, 8 | |
if game[2] == game[4] == game[6] != 0: | |
return game[2], 2, 6 | |
return 0,0,0 | |
def get_squares(self, player): | |
return [i for (i, box) in enumerate(self.game) | |
if box == player] | |
def make_move(self, position, player): | |
self.game[position] = player | |
def minimax(node, player, alpha, beta, depth): | |
""" Evaluate the board, with "player" to move next. """ | |
if node.complete(): | |
return node.winner()[0] # 1 if player 1 wins, 0 for draw | |
for move in node.get_available_moves(): | |
node.make_move(move, player) | |
score = minimax(node, player * -1, alpha, beta, depth+1) | |
node.make_move(move, 0) | |
if player == 1: | |
if score > alpha: | |
alpha = score | |
if alpha >= beta: | |
return beta | |
else: | |
if score < beta: | |
beta = score | |
if beta <= alpha: | |
return alpha | |
if player == 1: | |
return alpha | |
else: | |
return beta | |
def determine(board, player): | |
a = -2 * player # why currently player == 1 | |
choices = [] | |
if len(board.get_available_moves()) == 9: | |
return random.randint(0, 9) # first play random | |
for move in board.get_available_moves(): | |
board.make_move(move, player) | |
val = minimax(board, player * -1,-2,2,0) | |
log.debug(move, val) | |
board.make_move(move, 0) | |
if val * player > a: | |
log.debug(val, ">", a) | |
a = val * player | |
choices = [move] | |
elif val * player == a: | |
log.debug(val, "=", a) | |
choices.append(move) | |
log.debug(choices) | |
return random.choice(choices) # should randomize, | |
def test(): | |
tests = [ | |
# Game([1,1,1,-1,0,-1,0,-1,0]), # complete, win for 1 | |
# Game([-1,1,0,1,-1,1,0,0,-1]), # complete, win for -1 | |
# Game([1,1,-1, | |
# -1,-1,1, | |
# 1,-1,1]), | |
# single move win for either player | |
Game([1, 0, 1, -1, 0, -1, 0, -1, 1]), | |
Game([0, 0, 0, | |
0, 0, 0, | |
-1, 1, 0]), # win for -1 | |
] | |
for t in tests: | |
t.show_board() | |
print(determine(t, 1)) | |
print("--------") | |
def text_game(): | |
board = Game() | |
player = 1 | |
board.show_board() | |
while not board.complete(): | |
move = determine(board, player) | |
board.make_move(move, player) | |
board.show_board() | |
if board.complete(): | |
winner = board.winner()[0] | |
if winner == 1: | |
print("I win") | |
if winner == -1: | |
print("You win") | |
assert("The minimax algorithm didn't block you") | |
if winner == 0: | |
print("Strange game, the only winning move is not to play") | |
break | |
l = board.get_move() | |
board.make_move(l, -1) | |
board.show_board() | |
def main(): | |
try: | |
if sys.argv[1][0] == 't': | |
text_game() | |
sys.exit() | |
except IndexError: | |
pass | |
app = App() | |
app.title("Noughts & Crosses") | |
tk.mainloop() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment