Seen below is the code i have written for a maze game where the user controls a green square which they move around the maze using wasd, where they also have a projectile they can shoot using M1 and aiming the mouse cursor, However I am currently having issues with the moving of the projectile and I am not sure how i will be able to fix this problem.
import random
import tkinter as tk
import time
# Set the size of the maze
maze_width = 800
maze_height = 500
#Creating the character size
character_size = 20
maze = tk.Tk()
canvas = tk.Canvas(maze, width = maze_width, height = maze_height)
canvas.pack()
# Create the game over screen, making it red with text displaying that the game is over
game_over_screen = canvas.create_rectangle(0, 0, maze_width, maze_height, fill='red')
game_over_text = canvas.create_text(maze_width/2, maze_height/2, text='GAME OVER!', font=('Arial', 30), fill='white')
# Hide the game over screen so that it is only displayed when the player dies
canvas.itemconfig(game_over_screen, state='hidden')
canvas.itemconfig(game_over_text, state='hidden')
# Create the victory screen which will be siaplayed when the user escapes the maze
victory_screen = canvas.create_rectangle(0, 0, maze_width, maze_height, fill='green')
victory_text = canvas.create_text(maze_width/2, maze_height/2, text='YOU ESCAPED THE MAZE!', font=('Arial', 30), fill='white')
canvas.itemconfig(victory_screen, state='hidden')
canvas.itemconfig(victory_text, state='hidden')
# Create a 2D array to represent the whole maze
maze_array = [[0 for x in range(maze_width//20)] for y in range(maze_height//20)]
# Generate the maze, ensuring that every 20 units of the maze, there is a 50% chance of a wall being created, storing the walls as 1s and the 0 representing free space
for x in range(0, maze_width, 20):
for y in range(0, maze_height, 20):
if x == 0 or x == maze_width - 20 or y == 0 or y == maze_height - 20:
canvas.create_rectangle(x, y, x + 20, y + 20, fill='black', tags='wall')
maze_array[y//20][x//20] = 1
elif random.random() > 0.6:
canvas.create_rectangle(x, y, x + 20, y + 20, fill='black', tags='wall')
maze_array[y//20][x//20] = 1
# Making sure the path starts at the entrance point of the maze in the top left corner
x, y = 20, 20
path = [(x, y)]
# Stating the amount of vertical and horizontal units the path should move by
move_across = 37
move_down = 22
# Move the path sideways by the amount of units stated earlier
for i in range(move_across):
x += 20
path.append((x, y))
maze_array[y//20][x//20] = 0
# Move the path down by the amount of units stated earlier
for i in range(move_down):
y += 20
path.append((x, y))
maze_array[y//20][x//20] = 0
# Place squares over the path to simulate creating a path
for point in path:
x, y = point
#Fill the path using the hexadecimal colour value of the navigable part of the maze
canvas.create_rectangle(x, y, x + 20, y + 20, outline='#d9d9d9',fill='#d9d9d9')
# Adding a black square to the top right corner of the maze, so that the path is less obvious
canvas.create_rectangle(maze_width - 40, 20, maze_width - 20, 40, fill='black', tags='wall')
# Create the entrance and exit
# Change coordinates of the entrance and exit so they move one square in the diagonal direction
canvas.create_rectangle(20, 20, 40, 40, fill='blue')
exit_ = canvas.create_rectangle(maze_width - 40, maze_height - 40, maze_width -20, maze_height -20, fill='blue')
entrance = (20, 20) # top-left corner of the maze
exit = (750, 450)
projectile_x, projectile_y = (entrance)
# Algorithm for aiming the projectile with the mouse pointer
def aim_projectile(direction):
global projectile_direction
x,y = direction.x, direction.y
if x > player_x + character_size:
projectile_direction = 'right'
elif x < player_x:
projectile_direction = 'left'
elif y < player_y:
projectile_direction = 'up'
elif y > player_y + character_size:
projectile_direction = 'down'
# Define projectile so it can be called upon in the fire_projectile function
projectile = None
# Algorithm to fire the projectile
def fire_projectile(event):
global projectile
# Make it so the projectile is deleted if the user tries spawning multiple on the screen
if projectile is not None:
canvas.delete(projectile)
x1,y1,x2,y2 = canvas.coords(player)
center_x = (x1+x2) / 2
center_y = (y1 + y2) / 2
global projectile_x, projectile_y
#Shoot the projectile in the direction that has previously been stated in the movement algorithm
if projectile_direction is not None:
projectile = canvas.create_oval(center_x - 5,center_y - 5, center_x + 5,center_y + 5, fill='red')
if projectile_direction == 'up':
canvas.move(projectile, 0, -20)
projectile_y -= 20
elif projectile_direction == 'down':
canvas.move(projectile, 0, 20)
projectile_y += 20
elif projectile_direction == 'left':
canvas.move(projectile, -20, 0)
projectile_x -= 20
elif projectile_direction == 'right':
canvas.move(projectile, 20, 0)
projectile_x += 20
# Move the projectile after 1/4 seconds
maze.after(250, fire_projectile)
# Bind the canvas to the aim_projectile function
canvas.bind("<Motion>", aim_projectile)
# Bind the left mouse button to the fire_projectile function
canvas.bind("<Button-1>",fire_projectile)
# Create the player model which will be spawned at the entrance of the maze
player_x, player_y = 20, 20
player = canvas.create_rectangle(player_x, player_y, player_x+20, player_y+20, fill='green')
def check_enemy_collision():
# get player and enemy coordinates
player_coords = canvas.coords(player)
enemy_coords = canvas.coords(enemy)
# check if player and enemy coordinates align
if (player_coords[0] == enemy_coords[0] and player_coords[2] == enemy_coords[2]) and (player_coords[1] == enemy_coords[1] and player_coords[3] == enemy_coords[3]):
# Display the game over screen
canvas.itemconfigure(game_over_screen, state='normal')
canvas.itemconfigure(game_over_text, state='normal' )
# Ensure that the game over screen and text are displayed over the walls
canvas.tag_raise(game_over_screen)
canvas.tag_raise(game_over_text)
def check_exit_collision():
# Gather the coordinates of the player and of the exit to the maze
player_x1, player_y1, player_x2, player_y2 = canvas.coords(player)
exit_x1, exit_y1, exit_x2, exit_y2 = canvas.coords(exit_)
# Compare these values, and if they all match up, then the victory screen should be displayed
if player_x1 >= exit_x1 and player_x2 <= exit_x2 and player_y1 >= exit_y1 and player_y2 <= exit_y2:
# Display the victory screen
canvas.itemconfig(victory_screen, state='normal')
canvas.itemconfig(victory_text, state='normal')
#Ensure the text will be displayed over the walls
canvas.tag_raise(victory_screen)
canvas.tag_raise(victory_text)
# Delete the player once they reach the exit
canvas.delete(player)
# Algorithm for moving the player as well as adding collision to the walls of the maze
def move_player(event):
# Calls upon the previously stated x and y values of the player so they can be modified
global player_x, player_y
x1, y1, x2, y2 = canvas.coords(player)
new_x, new_y = player_x, player_y
if event.char == 'w':
new_y -= 20
elif event.char == 's':
new_y += 20
elif event.char == 'a':
new_x -= 20
elif event.char == 'd':
new_x += 20
x1,y1,x2,y2 = canvas.coords(player)
player_x, player_y = int((x1+x2)/2), int((y1+y2)/2)
# Convert new_x and new_y to indexes and store them as integers
new_x_index = int(new_x // 20)
new_y_index = int(new_y // 20)
# Check if the new position would put the player inside any of the walls
if maze_array[new_y_index][new_x_index] == 1:
# If player aligns with the maze walls do not allow them to move
return
# If there is no collision, allow the player to move
canvas.move(player, new_x - player_x, new_y - player_y)
player_x, player_y = new_x, new_y
# Check for collision between the player, enemy and exit every time the player moves
check_enemy_collision()
check_exit_collision()
#bind the 'w','a','s','d' keys to the move_player function
canvas.bind("<KeyPress-w>", move_player)
canvas.bind("<KeyPress-a>", move_player)
canvas.bind("<KeyPress-s>", move_player)
canvas.bind("<KeyPress-d>", move_player)
canvas.focus_set()
# Create the enemy at the exit of the maze
exit = (maze_width-40,maze_height-40)
enemy = canvas.create_rectangle(exit[0], exit[1], exit[0]+20, exit[1]+20, fill='red')
# Function to move the enemy towards the player
def move_enemy():
global player_x, player_y
# Gather the coordinates of the player so the enemy is able to make its way towards the user
x1,y1,x2,y2 = canvas.coords(player)
player_x, player_y = (x1+x2)/2, (y1+y2)/2
global enemy_x, enemy_y
x1,y1,x2,y2 = canvas.coords(enemy)
enemy_x, enemy_y = (x1+x2)/2, (y1+y2)/2
# Finds where the player is and appropriately moves the enemy towards this position
if player_x > enemy_x:
canvas.move(enemy, 20, 0)
elif player_x < enemy_x:
canvas.move(enemy, -20, 0)
if player_y > enemy_y:
canvas.move(enemy, 0, 20)
elif player_y < enemy_y:
canvas.move(enemy, 0, -20)
# Update the new position of the enemy coordinates
x1,y1,x2,y2 = canvas.coords(enemy)
enemy_x, enemy_y = int((x1+x2)/2), int((y1+y2)/2)
# Move the enemy towards the player once every second
maze.after(500, move_enemy)
#Check for collision between the player and the enemy every time the enemy moves
check_enemy_collision()
move_enemy()
# Create the timer text in the top right corner
timer_text = canvas.create_text(maze_width - 20, 20, text='0', font=('Arial', 20), fill='yellow')
def update_timer():
# Increment the timer value
global timer
# Increment the timer by a value of 1
timer += 1
# Update the timer text on the canvas
canvas.itemconfig(timer_text, text=timer)
# Make it so the timer updates after 1 second
canvas.after(1000, update_timer)
# Start the timer and initialise the value as 0, calling upon the funciton to update it
timer = 0
update_timer()
# Initialise the score as 0 and make it so this is displayed in the top left of the screen
score = 0
# Create the text that will be shown at the top of the screen displaying score
score_text = canvas.create_text(80, 20, text='Score: {}'.format(score), font=('Arial', 20), fill='yellow')
def update_score():
# Increase the score by 1
global score
score += 5
# Update the score text on the canvas
canvas.itemconfig(score_text, text='Score: {}'.format(score))
# Schedule the next score update
canvas.after(1000, update_score)
update_score()
# Find the player and exit coordinates and assign them values so the extra points can be assigned
player_x1, player_y1, player_x2, player_y2 = canvas.coords(player)
exit_x1, exit_y1, exit_x2, exit_y2 = canvas.coords(exit_)
# Compare these values, and if they all match up, then the victory screen should be displayed
if player_x1 >= exit_x1 and player_x2 <= exit_x2 and player_y1 >= exit_y1 and player_y2 <= exit_y2:
# Give the user an extra 1000 points if they escape the maze
score += 1000
canvas.itemconfig(score_text, text='Score: {}'.format(score))
maze.mainloop()
Seen below is the code that is problematic, as when I click M1, a projectile is spawned, however it does not move after it has been shot, and the following error message appears:
Traceback (most recent call last):
File "/nix/store/2vm88xw7513h9pyjyafw32cps51b0ia1-python3-3.8.12/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
File "/nix/store/2vm88xw7513h9pyjyafw32cps51b0ia1-python3-3.8.12/lib/python3.8/tkinter/__init__.py", line 814, in callit
func(*args)
TypeError: fire_projectile() missing 1 required positional argument: 'event'
Is there any way I can fix this, as other solutions i have found elsewhere when implemented have not actually been able to properly solve my problem. Thanks to anyone who helps
projectile_x, projectile_y = (entrance)
# Algorithm for aiming the projectile with the mouse pointer
def aim_projectile(direction):
global projectile_direction
x,y = direction.x, direction.y
if x > player_x + character_size:
projectile_direction = 'right'
elif x < player_x:
projectile_direction = 'left'
elif y < player_y:
projectile_direction = 'up'
elif y > player_y + character_size:
projectile_direction = 'down'
# Define projectile so it can be called upon in the fire_projectile function
projectile = None
# Algorithm to fire the projectile
def fire_projectile(event):
global projectile
# Make it so the projectile is deleted if the user tries spawning multiple on the screen
if projectile is not None:
canvas.delete(projectile)
x1,y1,x2,y2 = canvas.coords(player)
center_x = (x1+x2) / 2
center_y = (y1 + y2) / 2
global projectile_x, projectile_y
#Shoot the projectile in the direction that has previously been stated in the movement algorithm
if projectile_direction is not None:
projectile = canvas.create_oval(center_x - 5,center_y - 5, center_x + 5,center_y + 5, fill='red')
if projectile_direction == 'up':
canvas.move(projectile, 0, -20)
projectile_y -= 20
elif projectile_direction == 'down':
canvas.move(projectile, 0, 20)
projectile_y += 20
elif projectile_direction == 'left':
canvas.move(projectile, -20, 0)
projectile_x -= 20
elif projectile_direction == 'right':
canvas.move(projectile, 20, 0)
projectile_x += 20
# Move the projectile after 1/4 seconds
maze.after(250, fire_projectile)
# Bind the canvas to the aim_projectile function
canvas.bind("<Motion>", aim_projectile)
# Bind the left mouse button to the fire_projectile function
canvas.bind("<Button-1>",fire_projectile)