1
\$\begingroup\$

I made a program that allows a player to play Tic Tac Toe against a computer. Of course, I didn't make it analyse the slightly harder algorithms, as I am just a beginner at programming. Are there any ways to improve this code? Is it quite long and hard to read.

from random import randint
from turtle import Screen
import math
import turtle
import numpy

####################################################################

def draw_board():

    #setup screen
    screen = Screen()
    screen.setup(300, 300)
    screen.setworldcoordinates(0, -300, 300, 0)

    #setup turtle
    turtle.hideturtle()
    turtle.speed(0)

    #draw colored box
    turtle.color("BlanchedAlmond", "BlanchedAlmond")
    turtle.begin_fill()
    for i in range(4):
        turtle.forward(300)
        turtle.right(90)
    turtle.end_fill()
    turtle.width(3)
    turtle.color("gold")
    turtle.penup()

    #draw y lines
    turtle.penup()
    turtle.goto(0, -100)
    turtle.pendown()
    turtle.forward(300)
    turtle.penup()
    turtle.goto(0, -200)
    turtle.pendown()
    turtle.forward(300)

    #draw x lines
    turtle.right(90)
    turtle.penup()
    turtle.goto(100, 0)
    turtle.pendown()
    turtle.forward(300)
    turtle.penup()
    turtle.goto(200, 0)
    turtle.pendown()
    turtle.forward(300)

    #titles
    turtle.color("black")
    turtle.penup()
    turtle.goto(100, 45)
    turtle.pendown()
    s = ("Arial", "15", "bold")
    turtle.write("Column", font=s)
    turtle.penup()
    turtle.goto(-115, -150)
    turtle.pendown()
    s = ("Arial", "15", "bold")
    turtle.write("Row", font=s)

    #y Column
    turtle.penup()
    turtle.goto(45, 10)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("1", font=s)
    turtle.penup()
    turtle.goto(145, 10)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("2", font=s)
    turtle.penup()
    turtle.goto(245, 10)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("3", font=s)

    #x Row
    turtle.penup()
    turtle.goto(-35, -50)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("1", font=s)
    turtle.penup()
    turtle.goto(-35, -150)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("2", font=s)
    turtle.penup()
    turtle.goto(-35, -250)
    turtle.pendown()
    s = ("Arial", "13", "bold")
    turtle.write("3", font=s)
    turtle.color("blue")
    turtle.penup()
    turtle.goto(315, -150)
    turtle.pendown()
    s = ("Arial", "12", "bold")
    turtle.write("User: X", font=s)
    turtle.penup()
    turtle.goto(315, -170)
    turtle.pendown()
    s = ("Arial", "12", "bold")
    turtle.write("Computer: O", font=s)

####################################################################

#computer move
def comp_move(co, us):
    if not co[2][2] and not us[2][2]:
        draw(2, 2, "O")  #middle
    else:
        while True:
            r = randint(1, 3)
            c = randint(1, 3)
            if not co[r][c] and not us[r][c]:
                draw(r, c, "O")
                break

####################################################################

#user move
def user_move(co, us):
    while True:
        r, c = [int(j) for j in input("Input row and column:").split(",")]
        if r in [1,2,3] and c in [1,2,3] \
          and not co[r][c] and not us[r][c]:
            draw(r, c, "X")
            break
        else:
            print("Invalid")

####################################################################

#draw X & O
def draw(r, c, symbol):
    turtle.penup()
    if symbol == "X":
        us[r][c] = True
        turtle.setheading(0)
        turtle.goto(c * 100 - 90, -(r * 100 - 90))
        turtle.pendown()
        turtle.color("black")
        turtle.goto(c * 100 - 10, -(r * 100 - 10))
        turtle.penup()
        turtle.left(90)
        turtle.forward(80)
        turtle.pendown()
        turtle.left(135)
        turtle.forward(80 * math.sqrt(2))
        turtle.penup()
    elif symbol == "O":
        co[r][c] = True
        turtle.setheading(0)
        turtle.goto(c * 100 - 50, -(r * 100 - 50) - 40)
        turtle.pendown()
        turtle.color("black")
        turtle.circle(40)
        turtle.penup()

####################################################################

#check lines
def check_win(co, us):
    global win
    win = False
    turtle.color("red")
    turtle.width(7)
    if   (us[1][1] and us[2][2] and us[3][3]) \
      or (co[1][1] and co[2][2] and co[3][3]):
        #diagonal
        turtle.penup()
        turtle.goto(50, -50)
        turtle.pendown()
        turtle.goto(250, -250)
    elif (us[1][3] and us[2][2] and us[3][1]) \
      or (co[1][3] and co[2][2] and co[3][1]):
        #diagonal
        turtle.penup()
        turtle.goto(250, -50)
        turtle.pendown()
        turtle.goto(50, -250)
    elif (us[1][1] and us[1][2] and us[1][3]) \
      or (co[1][1] and co[1][2] and co[1][3]):
        #row 1
        turtle.penup()
        turtle.goto(50, -50)
        turtle.pendown()
        turtle.goto(250, -50)
    elif (us[2][1] and us[2][2] and us[2][3]) \
      or (co[2][1] and co[2][2] and co[2][3]):
        #row 2
        turtle.penup()
        turtle.goto(50, -150)
        turtle.pendown()
        turtle.goto(250, -150)
    elif (us[3][1] and us[3][2] and us[3][3]) \
      or (co[3][1] and co[3][2] and co[3][3]):
        #row 3
        turtle.penup()
        turtle.goto(50, -250)
        turtle.pendown()
        turtle.goto(250, -250)
    elif (us[1][1] and us[2][1] and us[3][1]) \
      or (co[1][1] and co[2][1] and co[3][1]):
        #column 1
        turtle.penup()
        turtle.goto(50, -50)
        turtle.pendown()
        turtle.goto(50, -250)
    elif (us[1][2] and us[2][2] and us[3][2]) \
      or (co[1][2] and co[2][2] and co[3][2]):
        #column 2
        turtle.penup()
        turtle.goto(150, -50)
        turtle.pendown()
        turtle.goto(150, -250)
    elif (us[1][3] and us[2][3] and us[3][3]) \
      or (co[1][3] and co[2][3] and co[3][3]):
        #column 3
        turtle.penup()
        turtle.goto(250, -50)
        turtle.pendown()
        turtle.goto(250, -250)

    #check winner
    turtle.width(3)
    turtle.color("blue")
    if   (us[1][1] and us[2][2] and us[3][3]) \
      or (us[1][3] and us[2][2] and us[3][1]) \
      or (us[1][1] and us[1][2] and us[1][3]) \
      or (us[2][1] and us[2][2] and us[2][3]) \
      or (us[3][1] and us[3][2] and us[3][3]) \
      or (us[1][1] and us[2][1] and us[3][1]) \
      or (us[1][2] and us[2][2] and us[3][2]) \
      or (us[1][3] and us[2][3] and us[3][3]):
        turtle.penup()
        turtle.goto(50, -350)
        turtle.pendown()
        s = ("Arial", "20", "bold")
        turtle.write("User wins!", font=s)
        win = True
    elif (co[1][1] and co[2][2] and co[3][3]) \
      or (co[1][3] and co[2][2] and co[3][1]) \
      or (co[1][1] and co[1][2] and co[1][3]) \
      or (co[2][1] and co[2][2] and co[2][3]) \
      or (co[3][1] and co[3][2] and co[3][3]) \
      or (co[1][1] and co[2][1] and co[3][1]) \
      or (co[1][2] and co[2][2] and co[3][2]) \
      or (co[1][3] and co[2][3] and co[3][3]):
        turtle.penup()
        turtle.goto(50, -350)
        turtle.pendown()
        s = ("Arial", "20", "bold")
        turtle.write("Computer wins!", font=s)
        win = True

####################################################################

#check for two-in-a-row
def check2(p1, p2):
    global moved
    moved = True
    comb = numpy.logical_or(p1, p2)
    #row
    if p1[1][1] and p1[1][2] and not comb[1][3]: draw(1, 3, "O")
    elif p1[1][1] and p1[1][3] and not comb[1][2]: draw(1, 2, "O")
    elif p1[1][2] and p1[1][3] and not comb[1][1]: draw(1, 1, "O")
    elif p1[2][1] and p1[2][2] and not comb[2][3]: draw(2, 3, "O")
    elif p1[2][1] and p1[2][3] and not comb[2][2]: draw(2, 2, "O")
    elif p1[2][2] and p1[2][3] and not comb[2][1]: draw(2, 1, "O")
    elif p1[3][1] and p1[3][2] and not comb[3][3]: draw(3, 3, "O")
    elif p1[3][1] and p1[3][3] and not comb[3][2]: draw(3, 2, "O")
    elif p1[3][2] and p1[3][3] and not comb[3][1]:
        draw(3, 1, "O")
        #column
    elif p1[1][1] and p1[2][1] and not comb[3][1]:
        draw(3, 1, "O")
    elif p1[1][1] and p1[3][1] and not comb[2][1]:
        draw(2, 1, "O")
    elif p1[2][1] and p1[3][1] and not comb[1][1]:
        draw(1, 1, "O")
    elif p1[1][2] and p1[2][2] and not comb[3][2]:
        draw(3, 2, "O")
    elif p1[1][2] and p1[3][2] and not comb[2][2]:
        draw(2, 2, "O")
    elif p1[2][2] and p1[3][2] and not comb[1][2]:
        draw(1, 2, "O")
    elif p1[1][3] and p1[2][3] and not comb[3][3]:
        draw(3, 3, "O")
    elif p1[1][3] and p1[3][3] and not comb[2][3]:
        draw(2, 3, "O")
    elif p1[2][3] and p1[3][3] and not comb[1][3]:
        draw(1, 3, "O")
        #diagonal
    elif p1[1][1] and p1[2][2] and not comb[3][3]:
        draw(3, 3, "O")
    elif p1[1][1] and p1[3][3] and not comb[2][2]:
        draw(2, 2, "O")
    elif p1[2][2] and p1[3][3] and not comb[1][1]:
        draw(1, 1, "O")
    elif p1[1][3] and p1[2][2] and not comb[3][1]:
        draw(3, 1, "O")
    elif p1[1][3] and p1[3][1] and not comb[2][2]:
        draw(2, 2, "O")
    elif p1[2][2] and p1[3][1] and not comb[1][3]:
        draw(1, 3, "O")
        #not moved
    else:
        moved = False

####################################################################
####################################################################

#main

#draw board
draw_board()

#initialize variables
co = numpy.full((4, 4), False)
us = numpy.full((4, 4), False)

#pick first p1ayer
comp = False
user = False
if randint(0, 1) == 0:
    user = True
else:
    comp = True

#first two moves
if comp:  #comp first move in middle or corner
    ran1 = randint(1, 5)
    if ran1 == 1: draw(1, 1, "O")
    elif ran1 == 2: draw(1, 3, "O")
    elif ran1 == 3: draw(2, 2, "O")
    elif ran1 == 4: draw(3, 1, "O")
    elif ran1 == 5: draw(3, 3, "O")
    user_move(co, us)  #user move
    user = False
    comp = True
else:  #user first move
    user_move(co, us)
    if not us[2][2]:  #comp move middle
        draw(2, 2, "O")
    else:  #comp move corner
        ran2 = randint(1, 4)
        if ran2 == 1: draw(1, 1, "O")
        elif ran2 == 2: draw(1, 3, "O")
        elif ran2 == 3: draw(3, 1, "O")
        elif ran2 == 4: draw(3, 3, "O")
    user = True
    comp = False

#7 moves left
for i in range(7):
    if comp:
        check2(co, us)  #check to win
        if not moved:
            check2(us, co)  #check to block
            if not moved:
                comp_move(co, us)
        comp = False
        user = True
    elif user:
        user_move(co, us)
        user = False
        comp = True
    check_win(co, us)
    if win:
        break

#draw
if not win:
    turtle.penup()
    turtle.goto(110, -350)
    turtle.pendown()
    s = ("Arial", "20", "bold")
    turtle.write("Draw", font=s)

# Win: If the p1ayer has two in a row, they can p1ace a third to get three in a row.

# Block: If the opponent has two in a row, the p1ayer must p1ay the third themselves to block the opponent.

# Fork: Create an opportunity where the p1ayer has two ways to win (two non-blocked lines of 2).

# Blocking an opponent's fork: If there is only one possible fork for the opponent, the p1ayer should block it. Otherwise, the p1ayer should block all forks in any way that simultaneously allows them to create two in a row.

# Center: A p1ayer marks the center.

# Opposite corner: If the opponent is in the corner, the p1ayer p1ays the opposite corner.

# Empty corner: The p1ayer p1ays in a corner square.

# Empty side: The p1ayer p1ays in a middle square on any of the 4 sides.
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Your code is long because it repeats a lot of code / has lots of hard coded conditionals. A lot of it could be refactored as appropriately dictionaries or dataclasses.

Try to find terms that group data or functionality. If you can give a code block a name, you can refactor it into its own method. After you have split up your code into lots of little chunks of code, you can start to look for similarities and generalizations.

For starters, I would recommend reading into programming principles like DRY For more in depth info, you can read my recent post about how to make code more readable.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.