Skip to content

Instantly share code, notes, and snippets.

@zeimusu
Created February 1, 2016 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeimusu/8568f3691bd96af504be to your computer and use it in GitHub Desktop.
Save zeimusu/8568f3691bd96af504be to your computer and use it in GitHub Desktop.
A game of noughts and crosses,
#!/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