0

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()
ggorlen
  • 44,755
  • 7
  • 76
  • 106
14sirs
  • 1
  • 4
  • 2
    Look into using lists and loops rather than `thing1`, `thing2`, `thing3`... which leads to massive, repetitive `if`-`elif` statements like these. – ggorlen Apr 30 '23 at 17:30
  • 1
    You might also consider separating the game logic from the GUI,. This will make debugging your game logic easier and relegate the GUI to just display and data entry – itprorh66 Apr 30 '23 at 18:05
  • 1
    Following on to what ggorlen said, you should use a matrix for the board. – sbottingota May 01 '23 at 09:29
  • @sbottingota how would I do so? (I don't know how to use matrix, is it easy to learn?) – 14sirs May 02 '23 at 13:02
  • @itprorh66 if possible, I would like to use turtle, but if I were to change it, what should I use? – 14sirs May 02 '23 at 13:04
  • @ggorlen which bit are you talking about thing1, thing2 and thing3 being in? – 14sirs May 02 '23 at 13:06
  • `column1`, `column2`, `column3`; `col1`, `col2`, `col3`; `count1`, `count2`, `count3`. All of those should be lists. – ggorlen May 02 '23 at 14:50
  • I don't see a problem with using turtle as the GUI, I personally have no experience with the library so can't say it will work or not. In general when building GUI based apps, I like to separate the logic of the game from the display of the results, that is my only suggestion. – itprorh66 May 02 '23 at 14:57
  • @14sirs a matrix is just a 2D array (or list in this case). – sbottingota May 02 '23 at 15:50
  • @ggorlen they are lists... do you mean they should be the same list? (count1, count2 etc. are lists because for some reason that is the only way it can update them in the function to place counters) – 14sirs May 02 '23 at 16:00
  • @sbottingota are you suggesting that for the board I use a list, instead of using turtle graphics to display it? or are you suggesting I use that as the background task? or are you suggesting that is how I get the winning combinations thing? – 14sirs May 02 '23 at 16:01
  • @14sirs `columns = [[380, 164, 476, 860], [488, 164, 575, 860], ...]` so you can loop over them. One matrix data structure, not 8 separate variables. See [How do I create variable variables?](https://stackoverflow.com/questions/1373164/how-do-i-create-variable-variables) – ggorlen May 02 '23 at 16:57
  • @ggorlen ok, do you know a way to get the winning combinations though? – 14sirs May 03 '23 at 10:56
  • Not with the program designed like this. I don't have time to write an answer, only suggest a redesign. There are many tic tac toe completion algorithms out there. One way is to loop from the row and column just moved in all 8 directions, checking for 4 in a row. – ggorlen May 03 '23 at 15:35
  • @ggorlen you said tic tac toe, but this is connect 4, what algorithm would you suggest? – 14sirs May 09 '23 at 16:18
  • Sorry, I meant connect 4. I described the algorithm, but a quick [web search should yield thousands of results](https://duckduckgo.com/?q=connect+4+win+algorithm&t=ffab). There's a [codewars kata](https://www.codewars.com/kata/529962509ce9545c76000afa) for it, for example. – ggorlen May 09 '23 at 16:20
  • How does Codewars kata work? It doesn't seem to have answers except for "higher ranks" – 14sirs May 11 '23 at 10:33

1 Answers1

0

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

Alain T.
  • 40,517
  • 4
  • 31
  • 51