I wrote code for a checkers game and the rules for the game are as follows:
- You can move a checker one diagonal space forward
- Jump your opponents checkers to remove them from the board (forced capture)
- If you can recapture after capturing a checker you must do so in the same move (You can chose which one if multiple captures are available)
- When checkers reach the end of the opponents side they can move backwards and are known as 'king checkers'
- You can win if all of your opponents checkers are over or your opponent has no legal moves
When you run the program, a TKinter window for the game pops up as shown below:
After a capture, when a checker is moved, it's position doesn't update which leads to the misplacement of the green buttons used to move the checkers.
Below is a video which consistently recreates the bug:
Below is a drive link with a series of images which also consistently recreates the bug:
You may notice that in the second last image, the green movement button is placed incorrectly. Here there are 2 problems. The button should be diagonally placed but it is horizontally placed. There should be 2 buttons but there is 1. My hypothesis is that this is the result of the position of the Red Checkers not being updated. If the checker was in its original position, it is understandable that the green button that apeared was in the correct place and the other green button didn't have space to spawn.
THIS IS A SILENT BUG AND THERE ARE NO ERROR MESSAGES
I will now proceed to explain the code so that the person trying to help me has background information on how the code works.
The below code makes a non resizable tkinter window and draws the board:
from tkinter import *
root = Tk()
root.title("Checkers")
root.resizable(False, False)
canvas_width, canvas_height = 400, 400
canvas = Canvas(root, width=canvas_width, height=canvas_height)
canvas.pack()
x_0, y_0, x_1, y_1 = 0, 0, 50, 50
for i in range(4):
for x in range(4):
canvas.create_rectangle(x_0, y_0, x_1, y_1, fill="brown")
x_0 += 100
x_1 += 100
x_0, x_1 = 50, 100
y_0 += 50
y_1 += 50
for y in range(4):
canvas.create_rectangle(x_0, y_0, x_1, y_1, fill="brown")
x_0 += 100
x_1 += 100
x_0, x_1 = 0, 50
y_0 += 50
y_1 += 50
After this I create 2 dictionaries with the values of the coordinates of each checker stored as a list corresponding to it's number. Along with this I also assign variables for the images of the checkers and the green movement buttons (The images are available in the zip file at the end of this question).
red_checkers = {1: [75, 25], 2: [175, 25],
3: [275, 25], 4: [375, 25],
5: [25, 75], 6: [125, 75],
7: [225, 75], 8: [325, 75],
9: [75, 125], 10: [175, 125],
11: [275, 125], 12: [375, 125]}
red_checker_image = PhotoImage(file="Red Checker.png").subsample(15, 15)
black_checker_image = PhotoImage(file="Black Checker.png").subsample(15, 15)
green_circle = PhotoImage(file="Green Circle.png"). subsample(20, 20)
black_checkers = {1: [25, 275], 2: [125, 275],
3: [225, 275], 4: [325, 275],
5: [75, 325], 6: [175, 325],
7: [275, 325], 8: [375, 325],
9: [25, 375], 10: [125, 375],
11: [225, 375], 12: [325, 375]}
I then assign a variable to each checker button. The buttons are initially created with the 'basic movement' function set as their command.
RedChecker1 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 1, red_checkers[1][0], red_checkers[1][1]))
RedChecker2 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 2, red_checkers[2][0], red_checkers[2][1]))
RedChecker3 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 3, red_checkers[3][0], red_checkers[3][1]))
RedChecker4 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 4, red_checkers[4][0], red_checkers[4][1]))
RedChecker5 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 5, red_checkers[5][0], red_checkers[5][1]))
RedChecker6 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 6, red_checkers[6][0], red_checkers[6][1]))
RedChecker7 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 7, red_checkers[7][0], red_checkers[7][1]))
RedChecker8 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 8, red_checkers[8][0], red_checkers[8][1]))
RedChecker9 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 9, red_checkers[9][0], red_checkers[9][1]))
RedChecker10 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 10, red_checkers[10][0], red_checkers[10][1]))
RedChecker11 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 11, red_checkers[11][0], red_checkers[11][1]))
RedChecker12 = Button(root, image=red_checker_image, border="0",
command=lambda: basic_movement("red", 12, red_checkers[12][0], red_checkers[12][1]))
BlackChecker1 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 1, black_checkers[1][0], black_checkers[1][1]))
BlackChecker2 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 2, black_checkers[2][0], black_checkers[2][1]))
BlackChecker3 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 3, black_checkers[3][0], black_checkers[3][1]))
BlackChecker4 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 4, black_checkers[4][0], black_checkers[4][1]))
BlackChecker5 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 5, black_checkers[5][0], black_checkers[5][1]))
BlackChecker6 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 6, black_checkers[6][0], black_checkers[6][1]))
BlackChecker7 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 7, black_checkers[7][0], black_checkers[7][1]))
BlackChecker8 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 8, black_checkers[8][0], black_checkers[8][1]))
BlackChecker9 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 9, black_checkers[9][0], black_checkers[9][1]))
BlackChecker10 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 10, black_checkers[10][0], black_checkers[10][1]))
BlackChecker11 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 11, black_checkers[11][0], black_checkers[11][1]))
BlackChecker12 = Button(root, image=black_checker_image, border="0",
command=lambda: basic_movement("black", 12, black_checkers[12][0], black_checkers[12][1]))
Next I create a dictionary of the buttons with an integer representing the key and the buttoon representing the value. I also make a list of the integers that represent the keys:
dict_of_black_checkers = {1: BlackChecker1, 2: BlackChecker2, 3: BlackChecker3, 4: BlackChecker4,
5: BlackChecker5, 6: BlackChecker6, 7: BlackChecker7, 8: BlackChecker8,
9: BlackChecker9, 10: BlackChecker10, 11: BlackChecker11, 12: BlackChecker12}
dict_of_red_checkers = {1: RedChecker1, 2: RedChecker2, 3: RedChecker3, 4: RedChecker4,
5: RedChecker5, 6: RedChecker6, 7: RedChecker7, 8: RedChecker8,
9: RedChecker9, 10: RedChecker10, 11: RedChecker11, 12: RedChecker12}
list_of_keys_black = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
list_of_keys_red = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
I then create a function to check the board for any available captures. this function work by checking to see if 2 spaces diagonal to a checker is empty and if 1 space diagonal to the checker is an enemy checker:
def check_captures(color):
captures_list = []
if color == 'red':
for j in list_of_keys_red:
if red_checkers[j + 1][0] not in [325, 375] and red_checkers[j + 1][1] not in [325, 375]:
if [red_checkers[j + 1][0] + 50, red_checkers[j + 1][1] + 50] in black_checkers.values():
if [red_checkers[j + 1][0] + 100, red_checkers[j + 1][1] + 100] not in red_checkers.values() and \
[red_checkers[j + 1][0] + 100, red_checkers[j + 1][1] + 100] not in black_checkers.values():
captures_list.append(j)
for k in list_of_keys_red:
if red_checkers[k + 1][0] not in [25, 75] and red_checkers[k + 1][1] not in [325, 375]:
if [red_checkers[k + 1][0] - 50, red_checkers[k + 1][1] + 50] in black_checkers.values():
if [red_checkers[k + 1][0] - 100, red_checkers[k + 1][1] + 100] not in red_checkers.values() and \
[red_checkers[k + 1][0] - 100, red_checkers[k + 1][1] + 100] not in black_checkers.values():
captures_list.append(k)
elif color == 'black':
for j in list_of_keys_black:
if black_checkers[j + 1][0] not in [25, 75] and black_checkers[j + 1][1] not in [25, 75]:
if [black_checkers[j + 1][0] - 50, black_checkers[j + 1][1] - 50] in red_checkers.values():
if [black_checkers[j + 1][0] - 100, black_checkers[j + 1][1] - 100] not in red_checkers.values() \
and [black_checkers[j + 1][0] - 100, black_checkers[j + 1][1] - 100] \
not in black_checkers.values():
captures_list.append(j)
for k in list_of_keys_black:
if black_checkers[k + 1][0] not in [325, 375] and black_checkers[k + 1][1] not in [25, 75]:
if [black_checkers[k + 1][0] + 50, black_checkers[k + 1][1] - 50] in red_checkers.values():
if [black_checkers[k + 1][0] + 100, black_checkers[k + 1][1] - 100] not in red_checkers.values() \
and [black_checkers[k + 1][0] + 100, black_checkers[k + 1][1] - 100] \
not in black_checkers.values():
captures_list.append(k)
return captures_list
I then create 6 functions to enable, disable and place the checkers on the board for both the colors.
def disabled_black():
for j in dict_of_black_checkers:
dict_of_black_checkers[j].configure(state="disabled")
def normal_black():
for j in dict_of_black_checkers:
dict_of_black_checkers[j].configure(state="normal")
def place_black():
for j in dict_of_black_checkers:
dict_of_black_checkers[j].place(x=black_checkers[j][0], y=black_checkers[j][1], anchor=CENTER)
def normal_red():
for j in dict_of_red_checkers:
dict_of_red_checkers[j].configure(state="normal")
def disabled_red():
for j in dict_of_red_checkers:
dict_of_red_checkers[j].configure(state="disabled")
def place_red():
for j in dict_of_red_checkers:
dict_of_red_checkers[j].place(x=red_checkers[j][0], y=red_checkers[j][1], anchor=CENTER)
I also call the place functions and the 'disabled_red' function since black starts first:
place_red()
place_black()
disabled_red()
now I define the 'basic_movement' function that was previusly used for the checker buttons. This function checks to see if one space diagonal to the checker is empty. If it is then it places a green movement button there.
def basic_movement(color, specific_checker, checker_x, checker_y):
movement_button_1.place(x=1000, y=1000)
movement_button_2.place(x=1000, y=1000)
if color == "red":
if checker_x + 50 < 376 and checker_y + 50 < 376 and [checker_x + 50, checker_y + 50] not in red_checkers.\
values() and [checker_x + 50, checker_y + 50] not in black_checkers.values() and checker_x != 375:
movement_button_2.place(x=checker_x + 50, y=checker_y + 50, anchor=CENTER)
movement_button_2.configure(command=lambda: movement_2(
specific_checker, color, [checker_x + 50, checker_y + 50]))
if checker_x - 50 > 0 and checker_y + 50 < 376 and [checker_x - 50, checker_y + 50] not in red_checkers.\
values() and [checker_x - 50, checker_y + 50] not in black_checkers.values() and checker_x != 25:
movement_button_1.place(x=checker_x - 50, y=checker_y + 50, anchor=CENTER)
movement_button_1.configure(command=lambda: movement_1(
specific_checker, color, [checker_x - 50, checker_y + 50]))
if color == "black":
if checker_x + 50 < 376 and checker_y - 50 > 0 and [checker_x + 50, checker_y - 50] not in red_checkers.\
values() and [checker_x + 50, checker_y - 50] not in black_checkers.values() and checker_x != 375:
movement_button_2.place(x=checker_x + 50, y=checker_y - 50, anchor=CENTER)
movement_button_2.configure(command=lambda: movement_2(
specific_checker, color, [checker_x + 50, checker_y - 50]))
if checker_x - 50 > 0 and checker_y - 50 > 0 and [checker_x - 50, checker_y - 50] not in red_checkers.\
values() and [checker_x - 50, checker_y - 50] not in black_checkers.values() and checker_x != 25:
movement_button_1.place(x=checker_x - 50, y=checker_y - 50, anchor=CENTER)
movement_button_1.configure(command=lambda: movement_1(
specific_checker, color, [checker_x - 50, checker_y - 50]))
I now define 2 functions, movement_1 and movement_2. These functions simply move the checkers when the green movement buttons are clicked.
def movement_1(specific, color, location):
if color == "red":
disabled_red()
normal_black()
red_checkers.update({specific: location})
place_red()
if len(check_captures('black')) != 0:
disabled_black()
for j in check_captures('black'):
dict_of_black_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=black_checkers[j + 1]
[0], checker_y=black_checkers[j + 1][1]: capture('black', specific_checker, checker_x, checker_y))
movement_button_1.place(x=1000, y=1000)
movement_button_2.place(x=1000, y=1000)
if color == "black":
disabled_black()
normal_red()
black_checkers.update({specific: location})
if len(check_captures('red')) != 0:
disabled_red()
for j in check_captures('red'):
dict_of_red_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=red_checkers[j + 1]
[0], checker_y=red_checkers[j + 1][1]: capture('red', specific_checker, checker_x, checker_y))
place_black()
movement_button_1.place(x=1000, y=1000)
movement_button_2.place(x=1000, y=1000)
def movement_2(specific, color, location):
if color == "red":
disabled_red()
normal_black()
red_checkers.update({specific: location})
place_red()
if len(check_captures('black')) != 0:
disabled_black()
for j in check_captures('black'):
dict_of_black_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=black_checkers[j + 1]
[0], checker_y=black_checkers[j + 1][1]: capture('black', specific_checker, checker_x, checker_y))
movement_button_1.place(x=1000, y=1000)
movement_button_2.place(x=1000, y=1000)
if color == "black":
disabled_black()
normal_red()
black_checkers.update({specific: location})
place_black()
if len(check_captures('red')) != 0:
disabled_red()
for j in check_captures('red'):
dict_of_red_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=red_checkers[j + 1]
[0], checker_y=red_checkers[j + 1][1]: capture('red', specific_checker, checker_x, checker_y))
movement_button_1.place(x=1000, y=1000)
movement_button_2.place(x=1000, y=1000)
I then define 2 functions for the captures. These functions move the checker to their final position upon a capture and delete the checker that it captured. Along with updating the relevant dictionaries after the capture.
def capture_movement_2(key, specific, color, final_x, final_y):
global red_checkers
global black_checkers
if color == 'red':
red_checkers.update({specific: [final_x, final_y]})
del black_checkers[key]
list_of_keys_black.remove(key - 1)
dict_of_black_checkers[key].place(x=1000, y=1000)
del dict_of_black_checkers[key]
del checker_type_black[key]
place_red()
normal_red()
if specific - 1 in check_captures('red'):
dict_of_red_checkers[specific].configure(command=lambda: capture(
'red', specific, red_checkers[specific][0], red_checkers[specific][1]))
for j in dict_of_red_checkers:
if j == specific:
continue
dict_of_red_checkers[j].configure(state='disabled')
else:
disabled_red()
normal_black()
for k in list_of_keys_red:
dict_of_red_checkers[k + 1].configure(command=lambda specific_checker=k + 1, checker_x=red_checkers[
k + 1][0], checker_y=red_checkers[k + 1][1]: basic_movement(
"red", specific_checker, checker_x, checker_y))
for k in list_of_keys_black:
dict_of_black_checkers[k + 1].configure(
command=lambda specific_checker=k + 1, checker_x=black_checkers[
k + 1][0], checker_y=black_checkers[k + 1][1]: basic_movement(
"black", specific_checker, checker_x, checker_y))
for j in check_captures('black'):
if check_captures('black')[0] == j:
disabled_black()
dict_of_black_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=black_checkers[j + 1]
[0], checker_y=black_checkers[j + 1][1]: capture('black', specific_checker, checker_x,
checker_y))
elif color == 'black':
black_checkers.update({specific: [final_x, final_y]})
del red_checkers[key]
list_of_keys_red.remove(key - 1)
dict_of_red_checkers[key].place(x=1000, y=1000)
del dict_of_red_checkers[key]
del checker_type_red[key]
place_black()
normal_black()
if specific - 1 in check_captures('black'):
dict_of_black_checkers[specific].configure(command=lambda: capture(
'black', specific, black_checkers[specific][0], black_checkers[specific][1]))
for j in dict_of_black_checkers:
if j == specific:
continue
dict_of_black_checkers[j].configure(state='disabled')
else:
disabled_black()
normal_red()
for k in list_of_keys_red:
dict_of_red_checkers[k + 1].configure(command=lambda specific_checker=k + 1, checker_x=red_checkers[
k + 1][0], checker_y=red_checkers[k + 1][1]: basic_movement(
"red", specific_checker, checker_x, checker_y))
for k in list_of_keys_black:
dict_of_black_checkers[k + 1].configure(
command=lambda specific_checker=k + 1, checker_x=black_checkers[
k + 1][0], checker_y=black_checkers[k + 1][1]: basic_movement(
"black", specific_checker, checker_x, checker_y))
for j in check_captures('red'):
if check_captures('red')[0] == j:
disabled_red()
dict_of_red_checkers[j + 1].configure(
state='normal', command=lambda specific_checker=j + 1, checker_x=red_checkers[j + 1]
[0], checker_y=red_checkers[j + 1][1]: capture('red', specific_checker, checker_x, checker_y))
movement_button_1_capture.place(x=1000, y=1000)
movement_button_2_capture.place(x=1000, y=1000)
The capture function that I define next just changes the command for the relevant checkers so that they place the movement buttons relevant to the capture rather than the basic movement.
def capture(color, specific_checker, checker_x, checker_y):
movement_button_1_capture.place(x=1000, y=1000)
movement_button_2_capture.place(x=1000, y=1000)
if color == 'red':
for k, v in black_checkers.items():
if checker_x not in [325, 375] and checker_y not in [325, 375]:
if [checker_x + 50, checker_y + 50] == v:
if [checker_x + 100, checker_y + 100] not in black_checkers.values():
if [checker_x + 100, checker_y + 100] not in red_checkers.values():
movement_button_2_capture.place(x=checker_x + 100, y=checker_y + 100, anchor=CENTER)
movement_button_2_capture.configure(command=lambda: capture_movement_2(
k, specific_checker, 'red', checker_x + 100, checker_y + 100))
break
for key, value in black_checkers.items():
if checker_x not in [25, 75] and checker_y not in [325, 375]:
if [checker_x - 50, checker_y + 50] == value:
if [checker_x - 100, checker_y + 100] not in black_checkers.values():
if [checker_x - 100, checker_y + 100] not in red_checkers.values():
movement_button_1_capture.place(x=checker_x - 100, y=checker_y + 100, anchor=CENTER)
movement_button_1_capture.configure(command=lambda: capture_movement_1(
key, specific_checker, 'red', checker_x - 100, checker_y + 100))
break
if color == 'black':
for key1, val in red_checkers.items():
if checker_x not in [325, 375] and checker_y not in [25, 75]:
if [checker_x + 50, checker_y - 50] == val:
if [checker_x + 100, checker_y - 100] not in black_checkers.values() \
and [checker_x + 100, checker_y - 100] not in red_checkers.values():
movement_button_2_capture.place(x=checker_x + 100, y=checker_y - 100, anchor=CENTER)
movement_button_2_capture.configure(command=lambda: capture_movement_2(
key1, specific_checker, 'black', checker_x + 100, checker_y - 100))
break
for key2, value2 in red_checkers.items():
if checker_x not in [25, 75] and checker_y not in [25, 75]:
if [checker_x - 50, checker_y - 50] == value2:
if [checker_x - 100, checker_y - 100] not in black_checkers.values() \
and [checker_x - 100, checker_y - 100] not in red_checkers.values():
movement_button_1_capture.place(x=checker_x - 100, y=checker_y - 100, anchor=CENTER)
movement_button_1_capture.configure(command=lambda: capture_movement_1(
key2, specific_checker, 'black', checker_x - 100, checker_y - 100))
break
I then create the buttons that the capture and movement functions use and finish with root.mainloop():
movement_button_1 = Button(root, image=green_circle, border="0")
movement_button_2 = Button(root, image=green_circle, border="0")
movement_button_1_capture = Button(root, image=green_circle, border="0")
movement_button_2_capture = Button(root, image=green_circle, border="0")
root.mainloop()
This is the entire code and it is also there in the Zip file attached at the end of this question. The Zip file also contains the relevant images and the main.py file so that you can run the program on your computer directly from that file.
I think the bug emerges in the 'capture_movement_1' and 'capture_movement_2' functions. After a capture is made. I run a loop to reassign the 'basic_movement' command to each checker. Perhaps this command assigns old values for some reason or the entire function uses an outdated version of the dictionary. This is the only explanation I can find since this bug occurs only after captures. The code snipet I am talking about is below:
for k in list_of_keys_red:
dict_of_red_checkers[k + 1].configure(command=lambda specific_checker=k + 1, checker_x=red_checkers[
k + 1][0], checker_y=red_checkers[k + 1][1]: basic_movement(
"red", specific_checker, checker_x, checker_y))
for k in list_of_keys_black:
dict_of_black_checkers[k + 1].configure(
command=lambda specific_checker=k + 1, checker_x=black_checkers[
k + 1][0], checker_y=black_checkers[k + 1][1]: basic_movement(
"black", specific_checker, checker_x, checker_y))
The Zip file is attached below as I have mention earlier: Zip File
I have now spent several hours Debugging with no improvements whatsoever so I have nothing to show for those attempts. I felt it necessery to explain the code for anyone looking at this project for the first time (thus the post is so long)
It is because the values of the arguments
checker_xandchecker_yofbasic_movement()somehow are not the same as the location of the corresponding checkers (red_checkersorblack_checkers).Actually the location of the corresponding checkers can be used instead of the passed arguments: