Search code examples
pythonturtle-graphicspython-turtle

Get all the winning combinations in connect 4, without having to manually put them in


I am coding in Python 3.10, and I am PyCharm Community edition 2021.2.2 to code in. I was wondering if there was a way in python to code connect 4(i have coded counters being placed and everything), and then also use some sort of formula to check if there are 4 in a row. To check if there were 4 counters in a row so far I can only think of manually going through and typing every combination of 4 in a row possible, be it diagonal, vertical, horizontal, or otherwise. As I said, is there an easy way of doing this without having to type in all the winning combinations manually?

I haven't tried it manually yet, as I was hoping I could get some help on how to do it not manually. This is my code so far:

import turtle
import mouse
from tkinter import *


#turtles
redturtle = turtle.Turtle()
blueturtle = turtle.Turtle()
boredturtle = turtle.Turtle()


#column values- positions
column1 = [380, 164, 476, 860]
column2 = [488, 164, 575, 860]
column3 = [587, 164, 678, 860]
column4 = [684, 164, 779, 860]
column5 = [783, 164, 875, 860]
column6 = [884, 164, 976, 860]
column7 = [983, 164, 1078, 860]

#connect 4 board values - where a placement is for background and to checkwin
col7 = [1,2,3,4,5,6]
col6 = [1,2,3,4,5,6]
col5 = [1,2,3,4,5,6]
col4 = [1,2,3,4,5,6]
col3 = [1,2,3,4,5,6]
col2 = [1,2,3,4,5,6]
col1 = [1,2,3,4,5,6]

#square values- 1 is bottom left corner
x1 = -305
x2 = -200
x3 = -100
x4 = 0
x5 = 100
x6 = 200
x7 = 300

listy = [-290, -170, -50, 70, 190, 300]
y1 = -290
y2 = -170
y3 = -50
y4 = 70
y5 = 190
y6 = 300

#column counters - to check what "Y" to put it at
count1 = [0]
count2 = [0]
count3 = [0]
count4 = [0]
count5 = [0]
count6 = [0]
count7 = [0]


def drawboard():
    boredturtle.hideturtle()
    boredturtle.speed(0)
    boredturtle.pensize(15)
    boredturtle.penup()
    #sideways
    boredturtle.setposition(-1000, -350)
    boredturtle.pendown()
    boredturtle.setposition(1000, -350)

    boredturtle.penup()
    boredturtle.setposition(-1000, -230)
    boredturtle.pendown()
    boredturtle.setposition(1000, -230)
    boredturtle.penup()
    boredturtle.setposition(-1000, -110)
    boredturtle.pendown()
    boredturtle.setposition(1000, -110)
    boredturtle.penup()
    boredturtle.setposition(-1000, 10)
    boredturtle.pendown()
    boredturtle.setposition(1000, 10)
    boredturtle.penup()
    boredturtle.setposition(-1000, 130)
    boredturtle.pendown()
    boredturtle.setposition(1000, 130)
    boredturtle.penup()
    boredturtle.setposition(-1000, 250)
    boredturtle.pendown()
    boredturtle.setposition(1000, 250)
    #top
    boredturtle.penup()
    boredturtle.setposition(-1000, 350)
    boredturtle.pendown()
    boredturtle.setposition(1000, 350)

    #downwards
    boredturtle.penup()
    boredturtle.setposition(-360, 1000)
    boredturtle.pendown()
    boredturtle.setposition(-360, -1000)
    boredturtle.penup()
    boredturtle.setposition(350, 1000)
    boredturtle.pendown()
    boredturtle.setposition(350, -1000)
    boredturtle.penup()
    boredturtle.setposition(-250, 1000)
    boredturtle.pendown()
    boredturtle.setposition(-250, -1000)
    boredturtle.penup()
    boredturtle.setposition(-150, 1000)
    boredturtle.pendown()
    boredturtle.setposition(-150, -1000)
    boredturtle.penup()
    boredturtle.setposition(-50, 1000)
    boredturtle.pendown()
    boredturtle.setposition(-50, -1000)
    boredturtle.penup()
    boredturtle.setposition(50, 1000)
    boredturtle.pendown()
    boredturtle.setposition(50, -1000)
    boredturtle.penup()
    boredturtle.setposition(150, 1000)
    boredturtle.pendown()
    boredturtle.setposition(150, -1000)
    boredturtle.penup()
    boredturtle.setposition(250, 1000)
    boredturtle.pendown()
    boredturtle.setposition(250, -1000)
    boredturtle.penup()
    boredturtle.setposition(350, 1000)
    boredturtle.pendown()
    boredturtle.setposition(350, -1000)

drawboard()

whoseturn = [0]

def redcounterplace(x, y, count):
    redturtle.speed(0)
    redturtle.hideturtle()
    redturtle.color("red")
    redturtle.pensize(80)
    redturtle.penup()
    listofy = y
    valy = listofy[count]
    redturtle.setposition(x,valy)
    redturtle.pendown()
    redturtle.circle(1)

def bluecounterplace(x,y, count):
    blueturtle.speed(0)
    blueturtle.hideturtle()
    blueturtle.color("blue")
    blueturtle.pensize(80)
    blueturtle.penup()
    listofy = y
    valy = listofy[count]
    blueturtle.setposition(x, valy)
    blueturtle.pendown()
    blueturtle.circle(1)

def columnchecker():
    mousepos = mouse.get_position()
    if mousepos[0] >= column1[0] and mousepos[0] <= column1[2] and mousepos[1] >= column1[1] and mousepos[1] <= column1[3]:
        return ("y1")
    if mousepos[0] >= column2[0] and mousepos[0] <= column2[2] and mousepos[1] >= column2[1] and mousepos[1] <= column2[3]:
        return ("y2")
    if mousepos[0] >= column3[0] and mousepos[0] <= column3[2] and mousepos[1] >= column3[1] and mousepos[1] <= column3[3]:
        return ("y3")
    if mousepos[0] >= column4[0] and mousepos[0] <= column4[2] and mousepos[1] >= column4[1] and mousepos[1] <= column4[3]:
        return ("y4")
    if mousepos[0] >= column5[0] and mousepos[0] <= column5[2] and mousepos[1] >= column5[1] and mousepos[1] <= column5[3]:
        return ("y5")
    if mousepos[0] >= column6[0] and mousepos[0] <= column6[2] and mousepos[1] >= column6[1] and mousepos[1] <= column6[3]:
        return ("y6")
    if mousepos[0] >= column7[0] and mousepos[0] <= column7[2] and mousepos[1] >= column7[1] and mousepos[1] <= column7[3]:
        return ("y7")

def putinbackgroundcheck(person, columnnum, count):
    if person == 1:
        if columnnum == "y1":
            col1[count] = "b"
        if columnnum == "y2":
            col2[count] = "b"
        if columnnum == "y3":
            col3[count] = "b"
        if columnnum == "y4":
            col4[count] = "b"
        if columnnum == "y5":
            col5[count] = "b"
        if columnnum == "y6":
            col6[count] = "b"
        if columnnum == "y7":
            col7[count] = "b"
    if person == 0:
        if columnnum == "y1":
            col1[count] = "r"
        if columnnum == "y2":
            col2[count] = "r"
        if columnnum == "y3":
            col3[count] = "r"
        if columnnum == "y4":
            col4[count] = "r"
        if columnnum == "y5":
            col5[count] = "r"
        if columnnum == "y6":
            col6[count] = "r"
        if columnnum == "y7":
            col7[count] = "r"

def main():
    if columnchecker() == "y1" and whoseturn[0] == 0:
        redcounterplace(x1, listy, count1[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count1[0])
        count1[0] = count1[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y2" and whoseturn[0] == 0:
        redcounterplace(x2, listy, count2[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count2[0])
        count2[0] = count2[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y3" and whoseturn[0] == 0:
        redcounterplace(x3, listy, count3[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count3[0])
        count3[0] = count3[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y4" and whoseturn[0] == 0:
        redcounterplace(x4, listy, count4[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count4[0])
        count4[0] = count4[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y5" and whoseturn[0] == 0:
        redcounterplace(x5, listy, count5[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count5[0])
        count5[0] = count5[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y6" and whoseturn[0] == 0:
        redcounterplace(x6, listy, count6[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count6[0])
        count6[0] = count6[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y7" and whoseturn[0] == 0:
        redcounterplace(x7, listy, count7[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count7[0])
        count7[0] = count7[0] + 1
        whoseturn[0] = 1
    elif columnchecker() == "y1" and whoseturn[0] == 1:
        bluecounterplace(x1, listy, count1[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count1[0])
        count1[0] = count1[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y2" and whoseturn[0] == 1:
        bluecounterplace(x2, listy, count2[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count2[0])
        count2[0] = count2[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y3" and whoseturn[0] == 1:
        bluecounterplace(x3, listy, count3[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count3[0])
        count3[0] = count3[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y4" and whoseturn[0] == 1:
        bluecounterplace(x4, listy, count4[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count4[0])
        count4[0] = count4[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y5" and whoseturn[0] == 1:
        bluecounterplace(x5, listy, count5[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count5[0])
        count5[0] = count5[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y6" and whoseturn[0] == 1:
        bluecounterplace(x6, listy, count6[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count6[0])
        count6[0] = count6[0] + 1
        whoseturn[0] = 0
    elif columnchecker() == "y7" and whoseturn[0] == 1:
        bluecounterplace(x7, listy, count7[0])
        putinbackgroundcheck(whoseturn[0], columnchecker(), count7[0])
        count7[0] = count7[0] + 1
        whoseturn[0] = 0

def checkwin():



mouse.on_click(lambda: main())



turtle.done()

Solution

  • You should use a list of lists instead of individual variables to represent and manipulate the board. It would also be easier to isolate the game logic from the presentation. This will allow you to fully test the logic outside of the graphical UI.

    With the board represented as a list of columns, each of which is a list of board cell content (containing ".", "X", or "O"), your end game check can use strings segments for complete rows and columns. Nested loops can be used to extract 4-cell segments from each 4x4 sub block and build strings for diagonal segments. You can then search of a segment containing four Xs or four Os to detect a win. You can also detect a draw by checking if any "." remain on the board.

    def winOrDraw(board,K=4):
        xo  = "|".join(map("".join,(*board,*zip(*board)))) # columns and rows
        for c in range(len(board)-K+1):                     
            for r in range(len(board[0])-K+1):
                xo += "|"+"".join(board[c+i][r+i]     for i in range(K)) # diag 1
                xo += "|"+"".join(board[c+K-1-i][r+i] for i in range(K)) # diag 2
        if "XXXX"  in xo: return "X"
        if "OOOO"  in xo: return "O"
        if "." not in xo: return "DRAW"
        return None
    

    Thesing can then be performed without a graphical UI and, once you get it to work, the UI simply needs to display the content of the board and accept user input.

    Text based testing:

    test = ["......",
            "......",
            ".X....",
            "..X...",
            "...X..",
            "....X."]
    print(winOrDraw(test)) # X
    test = ["....O.",
            "...O..",
            "..O...",
            ".O....",
            "......",
            "......"]
    print(winOrDraw(test)) # O
    test = ["......",
            ".....X",
            ".....X",
            ".....X",
            ".....X",
            ".....X"]
    print(winOrDraw(test)) # X
    test = ["......",
            "......",
            "......",
            "......",
            "......",
            ".OOOO."]
    print(winOrDraw(test)) # O
    

    Text based simulated game play:

    # setup
    cols  = 7
    rows  = 6
    board = [ ["."]*rows for _ in range(cols) ]    
    player = "X"
    
    while True:  # loop until end of game
    
        # display board, stop if game over
        print(*range(1,cols+1))
        for r in reversed(range(rows)):
            print(*(board[c][r] for c in range(cols)))
        if winOrDraw(board): break
    
        # get and validate user input
        col = input(f"player {player} select column (1-{cols}): ")
        if col not in map(str,range(1,cols+1)):
            print("invalid column number")
            continue
        col = int(col)-1
        if not "." in board[col]:
            print("column already full")
            continue
    
        # perform move 
        row = board[col].index(".")
        board[col][row] = player
    
        # switch player
        player = "X" if player == "O" else "O"
    
    print("GAME OVER.  WINNER IS ",winOrDraw(board))
    

    To convert the board data to a graphical representation, you can compute relative coordinates based on a scale of your choosing. With the data in a list of list, everything can be calculated proportionally using loops:

    for example:

    import turtle
    # draw Board content:
    def drawboard(board,scale):
        cols, rows = len(board),len(board[0])
        turtle.setworldcoordinates(0,0,scale*(cols+1),scale*(rows+1))
        t = turtle.Turtle()
        t.hideturtle()
        t.speed(0)
        t.penup()
        t.goto(scale//2,(rows+0.5)*scale)
        t.pendown()
        t.setheading(270)
        t.forward(rows*scale)
        t.left(90)
        for _ in range(cols):
            t.forward(scale)
            t.left(90)
            t.forward(scale*rows)
            t.backward(scale*rows)
            t.right(90)
        t.penup()
        for c,col in enumerate(board):
            for r,cell in enumerate(col):
                if cell==".":break
                t.goto((c+1)*scale,(r+1)*scale)
                t.dot(scale*3,"red" if cell == "X" else "blue")
    

    Using this, you can replace the text based board drawing above with a call to drawboard(board,30) to see the graphical equivalent of the list of lists data. entering column numbers in the text window will update the graphical view as you play the game. This should be a good starting point to make it completely graphical using mouse clicks to chose columns.

    enter image description here