How do I alter my code so that the projectile is able to move? (using python and tkinter)

73 views Asked by At

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)
0

There are 0 answers