Tic Tac Toe Game using Turtle

Question:

This is for an extra credit assignment in Python. I’ve finished most until the last part where I have to determine the area of the tictactoe box chosen.

I can only detect diagonal boxes, used from combining both code reply’s below.
I can detect those 3 boxes but the rest still show as none and most logic is used with loop so I can understand where I am going wrong.

import turtle
from time import sleep
import sys

CURSOR_SIZE = 20
SQUARE_SIZE = 99
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')
BOXES = {}
# TRACK BOX
pen = turtle.Turtle()
pen.penup()

def mouse(x, y):
    print('|--------------X={0} Y={1}--------------|'.format(x, y))
    for key in BOXES:
        minx, miny, maxx, maxy = BOXES[key]
        print(key, BOXES[key])
        if (minx <= x <= maxx) and (miny <= y <= maxy):
            print("Found", key)
            return key
    print('None')
    return None  # Not found.

class TicTacToe:
    global BOXES
    def __init__(self):
        # CREATES 2D LIST FOR INTERROGATION
        self.board = [['?'] * 3 for i in range(3)]

    def minmax(self, points):
        """ Find extreme x and y values in a list of 2-D coordinates. """
        minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
        for x, y in points[1:]:
            if x < minx:
                minx = x
            if y < minx:
                miny = y
            if x > maxx:
                maxx = x
            if y > maxy:
                maxy = y
        return minx, miny, maxx, maxy


    def drawBoard(self):
        ##############################################
        turtle.shape('square')
        turtle.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
        turtle.color('black')
        turtle.stamp()
        turtle.hideturtle()
        ##############################################
        for j in range(3):
            for i in range(3):
                # CREATES SHAPE AND STORES IN PLACEHOLDER
                turtle.shape('square')
                box = turtle.shape('square')
                # CREATES SHAPE SIZE AND STORES IN PLACEHOLDER
                turtle.shapesize(SQUARE_SIZE / CURSOR_SIZE)
                boxsize = turtle.shapesize()
                # CREATES SHAPE COLOR
                turtle.color('white')
                turtle.penup()
                # CREATES SHAPE POS AND STORES IN PLACEHOLDER
                turtle.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
                boxpos = turtle.pos()

                mypos = []

                pen.goto(boxpos[0]-50,boxpos[1]+50)
                ##############################################
                for line in range(0, 4):
                    pen.forward(SQUARE_SIZE)
                    pen.right(90)
                    mypos.append(pen.pos())
                turtle.showturtle()
                turtle.stamp()
                ##############################################
                a = mypos[0]
                b = mypos[1]
                c = mypos[2]
                d = mypos[3]
                self.board[j][i] = [a, b, c, d]
        ##############################################
        BOXES['BOX01'] = self.minmax(self.board[0][0])
        BOXES['BOX02'] = self.minmax(self.board[0][1])
        BOXES['BOX03'] = self.minmax(self.board[0][2])
        ##############################################
        BOXES['BOX11'] = self.minmax(self.board[1][0])
        BOXES['BOX12'] = self.minmax(self.board[1][1])
        BOXES['BOX13'] = self.minmax(self.board[1][2])
        ##############################################
        BOXES['BOX21'] = self.minmax(self.board[2][0])
        BOXES['BOX22'] = self.minmax(self.board[2][1])
        BOXES['BOX23'] = self.minmax(self.board[2][2])
        ##############################################
        turtle.onscreenclick(mouse)

turtle.setup(800, 600)
wn = turtle.Screen()
z = TicTacToe()
z.drawBoard()
turtle.mainloop()

Answers:

This should give you the basic idea, which is compute the minimum and maximum x and y values of each box and store them in BOXES. This makes it very easy to determine if the given x and y coordinates passed to the mouse() callback function are within any of them.

With the multiple boxes in your real code, make sure to apply the new minmax() function to the corners of each one of them.

import turtle

""" This will be based off 1 box  instead of all 9"""
pen = turtle.Turtle()
corners = []
BOXES = {}

for line in range(0, 4):
    pen.forward(50)
    pen.left(90)
    corners.append(pen.pos())

def minmax(points):
    """ Find extreme x and y values in a list of 2-D coordinates. """
    minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
    for x, y in points[1:]:
        if x < minx:
            minx = x
        if y < minx:
            miny = y
        if x > maxx:
            maxx = x
        if y > maxy:
            maxy = y
    return minx, miny, maxx, maxy


BOXES['MIDDLEBOX'] = minmax(corners)

for i in BOXES:
    print(i, BOXES[i])
"""
    POINTS FROM LEFT TO RIGHT
    (1) - TOP LEFT CORNER
    (2) - BOTTOM LEFT CORNER
    (3) - BOTTOM RIGHT CORNER
    (4) - TOP RIGHT CORNER
"""

def mouse(x, y):
    """ Return key of box if x, y are within global BOXES
        or None if it's not.
    """
    for key in BOXES:
        minx, miny, maxx, maxy = BOXES[key]
        if (minx <= x <= maxx) and (miny <= y <= maxy):
            print(key)
            return key
    print('None')
    return None  # Not found.

turtle.onscreenclick(mouse)
turtle.mainloop()
Answered By: martineau

I believe you’re making the problem harder than necessary by not taking full advantage of Python turtle. Instead of trying to find a square within the board when clicking on the screen, make the squares of the board themselves turtles that respond to mouse clicks. Then there’s nothing to figure out, position-wise.

Here’s a reimplementation that draws a board, allows you to click on it, alternately sets the clicked sections to ‘X’ or ‘O’:

from turtle import Turtle, Screen

CURSOR_SIZE = 20
SQUARE_SIZE = 50
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')

class TicTacToe:
    def __init__(self):
        self.board = [['?'] * 3 for i in range(3)] # so you can interrogate squares later
        self.turn = 'X'

    def drawBoard(self):
        background = Turtle('square')
        background.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
        background.color('black')
        background.stamp()
        background.hideturtle()

        for j in range(3):
            for i in range(3):
                box = Turtle('square', visible=False)
                box.shapesize(SQUARE_SIZE / CURSOR_SIZE)
                box.color('white')
                box.penup()
                box.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
                box.showturtle()
                box.stamp()  # blank out background behind turtle (for later)

                self.board[j][i] = box
                box.onclick(lambda x, y, box=box, i=i, j=j: self.mouse(box, i, j))

    def mouse(self, box, i, j):
        box.onclick(None)  # disable further moves on this square

        # replace square/turtle with (written) X or O
        box.hideturtle()
        box.color('black')
        box.sety(box.ycor() - FONT_SIZE / 2)
        box.write(self.turn, align='center', font=FONT)

        self.board[j][i] = self.turn  # record move

        self.turn = ['X', 'O'][self.turn == 'X']  # switch turns

screen = Screen()

game = TicTacToe()

game.drawBoard()

screen.mainloop()

You can use board to do scoring, or implement a smart computer player, or whatever you desire.

enter image description here

Answered By: cdlane