1

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)
acw1668
  • 40,144
  • 5
  • 22
  • 34
rken
  • 11
  • 2
  • The error seems pretty clear: `fire_projectile` is defined to require an `event` pattern, but you're calling it with `after` which doesn't pass an event argument. Since your function doesn't use `event`, why do you require it? – Bryan Oakley Jan 30 '23 at 18:30
  • Yeah sorry if my error may seem obvious but i am quite new to tkinter, and i am not really sure how i would change my code. I tried altering maze.after() to maze.bind() and got no error message however the projectile still did not move. and also attempted removing (event) pattern however the code still did not run as intended, as it says it requires 0 positional arguments but 1 was given. Sorry if i dont sound the smartest at the moment. – rken Jan 30 '23 at 18:47

0 Answers0