Level 6: Challenge 2 - IncrediCoders/Python1 GitHub Wiki

Queen Cobra added this page on June 6, 2023


Let's begin your second challenge for Level 6!

In this challenge, you are going to add in another plasma ball projectile for the Cryptic Creeper to attack with!

To find the Level 6 Challenge 2 code template, open the Level 6 folder, the Challenges folder, and then the Challenge 2 folder. You should already have the files downloaded onto your computer (see Load the IncrediCoders Files). Open the BossBattle_Challenge2.py file in Visual Studio Code to build the program by following along with my instructions below!

I broke these instructions down into a few sections:

Explaining the Code

On Line 2, from init import * initializes the program (init). This runs code that sets up the file path for the images and text file, gets the fonts ready for this game, sets the window size, loads the images, and includes the code that handles collision in the game (when one object touches another). This Line 2 statement allows you to use all the information in that file, for the rest of your program.

On Line 4, def update(delta_time): updates each time a key is pressed or a button is clicked (these are called events). This method will continuously check for these events while the game is running.

The Line 5 comment, # Check if Paul collides with the walls, explains that Lines 6-13 are going to check if Paul runs into any of the walls (on the top of the screen, bottom, left, or right).

On Line 6, if MY.player.location.x < MY.wall_height: checks to see if the current location of the player sprite is less than the maximum height from the dimensions of the wall. Because this code checks the X-axis, it's looking at the left side of the screen. In other words, Lines 6-7 make sure Paul doesn't run through the left wall.

On Line 7, MY.player.location.x = MY.wall_height is run when the if statment on Line 6 is true. It sets the player's location to the maximum height set by the dimensions of the wall (again, to make sure Paul doesn't go through the left wall).

On Line 8, if MY.player.location.x > WINDOW_WIDTH - MY.wall_height: checks to see if the current location of the player sprite is greater than the width of the screen (minus the height from the dimensions of the wall). This makes sure Paul doesn't run through the right wall.

On Line 9, MY.player.location.x = WINDOW_WIDTH - MY.wall_height is run if the if statment on Line 8 is true, and it sets the player's location to the location that is the width of the window screen, minus the height of the wall.

On Line 10, if MY.player.location.y < MY.wall_height: checks to see if the current location of the player sprite is less than the maximum height from the dimensions of the wall. This makes sure Paul doesn't run through the top wall (and go off the screen).

On Line 11, MY.player.location.y = MY.wall_height is run when the if statment on Line 10 is true, and it sets the player's location to the maximum height set by the dimensions of the wall.

On Line 12, if MY.player.location.y > WINDOW_LENGTH - (MY.wall_height + 15): checks to see if the current location of the player sprite is greater than the length of the window, subtracted by the height of the wall plus 15. The extra amount of space (15 units) is added to make sure that Paul doesn't go through the bottom wall on the screen. The bottom wall pushes up a little higher from the bottom of the screen, because Paul can't overlap over some it, like he can on the top of the screen. (It would look like he's standing on the wall.)

On Line 13, MY.player.location.y = WINDOW_LENGTH - (MY.wall_height + 15) is run when the if statment on Line 12 is true, and it sets the player's location to the length of the window, subtracted by the height of the wall, plus 15. Again, we add the 15 units so that Paul doesn't stand on the bottom wall when he reaches the bottom of the screen.

On Line 15, we run the handle_pillar_collision() function. When Paul runs into a pillar, the code in this function checks to see the location of Paul's sprite. The code for this function is stored in the init.py file.

On Line 18, if MY.player.collides_with_boss(): checks to see if Paul has collided with the Creeper, which is the boss. (Afterall, this game is called Boss Battle!) If Paul collides with the Creeper, then we run Lines 19-21.

If that statement is true, on Line 19, the player_pain_anim() function runs, which displays the player's pain animation. In other words, if Paul runs into the Creeper, he gets hurt!

On Line 20, MY.player_health -= 1 subtracts one health from the player's current health, so if the player had been hit before, it would go down one more from that value. Paul loses some health!

On Line 21, MY.player_hitbox.active = False allows the player time to recover and move away from the boss by removing the active hitbox for a few seconds.

On Line 24, if MY.player_dir == UP: checks to see if the direction that the player is going is up.

On Lines 25-26, the code MY.player_hitbox.location = pygame.math.Vector2(MY.player.location.x + 20, MY.player.location.y - 20) runs if the Line 24 statement is true. This code sets the hitbox in front of Paul when he is facing up. The reason why this code is on two lines, is so that it makes more sense when describing it in the book.

On Line 27, elif MY.player_dir == DOWN: checks to see if the direction that the player is going is down. In other words, the player presses the down arrow to move Paul facing down on the screen.

On Lines 28-29, MY.player_hitbox.location = pygame.math.Vector2(MY.player.location.x - 10, MY.player.location.y + 25) runs if the Line 27 statement is true. It sets the hitbox for when the player goes down. Again, we have the code on two lines because it's a long line of code and didn't fit in the book (or in my text messages that were transferred into the book).

On Line 30, elif MY.player_dir == LEFT: checks to see if the direction that the player is facing is to the left.

On Lines 31-32, MY.player_hitbox.location = pygame.math.Vector2(MY.player.location.x - 20, MY.player.location.y) runs if the Line 30 statement is true. It sets the hitbox for when the player faces or moves to the left.

On Line 33, elif MY.player_dir == RIGHT: checks if the direction that the player is facing is to the right.

On Lines 34-35, MY.player_hitbox.location = pygame.math.Vector2(MY.player.location.x + 20, MY.player.location.y) runs if the Line 33 statement is true. It sets the hitbox for when the player goes to the left.

On Line 38, we run if MY.player_hitbox.active and MY.boss.collides_with_hitbox(): This statement checks to see if the Creeper has been attacked by Paul. It means Paul's melee attack (with his Laser Blade) has hit the Creeper.

On Line 39, MY.boss_health -= 1 runs after the Creeper is hit. Creeper's health goes down by 1. The symbol "-=" reduces the value of the variable (the total number) by the amount on the right (which is 1 in this case). And then it updates the variable with the new value (which is one less).

On Line 40, MY.player_hitbox.active = False gives the Creeper time to recover, so that the player doesn't hit the Creeper repeatedly and do a lot of damage at once. Also, the player will want to run away from the Creeper after attacking him, so that the Creeper doesn't hit Paul with a plasma bolt.

NEW CODE: On Lines 42-55, you are going to write your own code to change the projectile path of the Creeper's plasma bolts. You'll make them shoot out in a square path instead. See the next section, "Write Your Own Code", for guidance.

Next, in Lines 57-60 are for when Paul is hit by a projectile.

On Line 57, you'll find the code if projectile.collides_with(MY.player): It checks to see if Paul is hit by a projectile. If he is, then the program runs Lines 58-60.

On Line 58, the code MY.player_health -= MY.proj_damage subtracts health from the player (Paul), after being hit by the projectile. The amount of health is stored in the MY.proj_damage variable, so that it's easy to change that amount in one place. (The default amount of damage is set to 0.1 in the init.py file.) The minus-equals symbol (-=) subtracts that amount from the variable on the left (MY.player_health) and then updates it to the new value. So, after this line runs, MY.player_health contains the new value of Paul's health, after the projectile hits him.

On Line 59, we run the player_pain_anim() function, which displays the animation for when the player (Paul) is hit by a projectile. We define that function in the init.py file, which plays the correct animation, depending on which direction Paul is facing.

On Line 60, the code MY.player.hit = True makes the player temporarily invincible once hit by the projectile. This code is used in the init.py file to run an if block, which makes sure Paul has time to run away before he can be attacked again.

On Line 62, player_attack_update() runs a function that's defined in the init.py file. The function includes an if statement that checks to see if the player presses the spacebar. If so, it turns on the player's hitbox, so that Paul can attack, and it plays the attack animation animations while Paul attacks.

On Line 64, update_assets(delta_time) updates all the animations and other information about the level, using the delta_time variable, which keeps all the animations on the level in sync. The function is defined in the init.py file.

On Line 66, the check_win() function checks to see if the player has won. If so, the game ends with the "You Win" overlay. The function is defined in the init.py file, and it contains if statements that check whether the Creeper ran out of health.

On Line 68, the check_events() function checks whether the boss attacks and whether you closed the game window. This function is also in the init.py file, and it contains a for loop with embedded if, elif, and else statements.

Lines 71-74 register the different game states:

  • On Line 71, Manager.register(sys.modules[__name__]) sets up the state machine for the current file, so that the game launches.
  • On Line 72, Manager.register(GameOver) moves the game into the Game Over state (NOTE TO PLAY THE GAME AND DESCRIBE)
  • On Line 73, Manager.register(PlayAgain) opens the Play Again state, where the player can press the Play Again button. (NOTE TO PLAY THE GAME AND DESCRIBE)
  • On Line 74, Manager.register(Intro) opens the Start Screen when the game loads.

On Line 77, Manager.run(SCREEN, WINDOW, BLACK, "CHALLENGE1") opens a new window and runs the game.

Write Your Own Code

Lines 43-60 make the projectiles follow a square path around the Creeper. You're going to write Lines 43-55.

Let's start by writing out Line 43:

  • On Line 43, type for to start a for loop. This is a continuous loop that could occur multiple times, for the number of projectiles on the screen. This means that the for loop runs once for every projectile on the screen.
  • Next, enter a space, and then write the variable projectile on the same line. This is going to cycle through and access all the elements in the MY.shield.projectiles list, one at a time.
  • Type in MY.shield_projectiles next. You're looking in the MY.shield.projectiles list (also called an array) to process each projectile.
  • Since this is a for loop, end the line with a : symbol (a colon).

Under the comment on Line 44, write the if statement on Line 45:

  • if(projectile.location.x >= 320 and projectile.location.x < 480 and projectile.location.y == 240):

Line 45 is a nested if loop that checks if the projectile is in place to be moved to the right. First, it looks at the range for the x-axis. In this case, the projectile location needs to be greater than or equal to 320 for the x-position. You write projectile.location.x >= 320 in the parentheses, followed by the and operator. The the projectile's x-position would also have to be less than 480, so you write projectile.location.x < 480 next, followed by the and operator. Finally, for this if statement, the y-position needs to be equal to 240, so you would write on the same line: projectile.location.y == 240):

Note: Do not forget to put your parameters in parentheses, after the if statement. Then, make sure to add a colon at the end, after the closing parenthesis.

On Line 46, remember to indent to the right, so the new line begins further to the right than Line 45. if the above statement is true, then type projectile.location.x += MY.projectile_velocity to add the projectile velocity to the location of the projectile on the x-axis.

You took the velocity of the player's projectile, which is MY.projectile_velocity., and then you added it to the value of the projectile.location.x variable. The += operator takes the most recent value of the velocity variable and adds that to x-position variable.

Below the code comment on Line 47, you're going to write the code for Line 48:

  • elif(projectile.location.x == 480 and projectile.location.y >= 240 and projectile.location.y < 400):

On Line 48, you started a nested elif loop, which stands for "else-if". It presents an alternative if statement, if the first condition (on Line 45) isn't true. This statement checks if the projectile is ready to be moved down. In this case, the projectile's location needs to be equal to 480 for the x-position. The projectile's y-position would have to be greater than or equal to 240. And finally, for this elif statement, the y-position will also need to be less than 400.

Note: Do not forget to use parentheses after the elif conditional statement, and be sure to add a colon at the end of the line.

On Line 49 (remember to indent first), type in the code that adds the projectile velocity to the y location of the projectile. Type in the code:

  • projectile.location.y += MY.projectile_velocity

This is very similar to Line 46, but you replace the x coordinate with the y coordinate in the projectile variable name.

If the Line 48 elif statement is true, then Line 49 adds the projectile velocity to the location of the projectile on the y-axis.

On Line 51, type in a similar elif statement (indented directly under the comment on Line 50):

  • elif(projectile.location.x <= 480 and projectile.location.x > 320 and projectile.location.y == 400):

This elif statement checks if the projectile is ready to be moved to the left. Don't forget to include the colon at the end of this elif statement.

On Line 52, indent to the right. If Line 51 is true, then you'll subtract the projectile velocity from the location of the projectile on the x-axis. Type this code:

  • projectile.location.x -= MY.projectile_velocity

Similarly, the -= operator subtracts the value of the MY.projectile_velocity variable from the projectile.location.x variable and saves it back into the projectile.location.x variable.

On Line 54, you're going to start our final nested elif statement, which checks if the projectile is ready to be moved up. Type in the code:

  • elif(projectile.location.x == 320 and projectile.location.y > 240 and projectile.location.y <= 400):

In this case, the projectile needs to be equal to 320 for the x-position. The projectile's y-position would have to be greater than 240. Finally, for this elif statement, the y-position will also need to be less than or equal to 400. You end the line with a colon.

Line 55 runs if Line 54 is true. You'll subtract the projectile velocity from the location of the projectile on the y-axis. On Line 55, indent to the right, and then type in this code:

  • projectile.location.y -= MY.projectile_velocity

You did it! It's time to run your code, and make sure it works!

The Final Code

I included a solution file for Level 6: Boss Battle Challenge 2. This file has all the code filled in, so if you run into any issues with your code (for example, it doesn't compile/run, or something isn't working correctly in your program), then you can take a look at the final code file to see what you did differently:

IMPORTANT: Please don't cheat yourself! Finish the game first!

Next Steps

When you're done, you can move on to Level 7, IncrediCards!

More Level 6 Resources

In addition to this Online Articles page and the instructions for our Level 6 challenges, we also have a Help Page, a Learning Quiz, an Unplugged Activity, and a Rewards article:

  • Level 6: Help - This page helps you complete the instructions in the book, in case you get stuck.

  • Level 6: Learning Quiz - I wrote some questions in case you want to quiz yourself about what you learned. Or you can teach others and quiz them!

  • Level 6: Unplugged Activity - I wrote this page with more details than what you saw in the book. In this game,

  • Level 6: Rewards - If you completed the Boss Battle program that we talked about, then I set up this page to act as a reward. You can see some illustrations of me and learn more about who I am! You'll also find the Mech Award digital download, to show off your accomplishment!

Level 7

After you're completely done with Level 6 (did you do both challenges and get your reward?), then it's time to move on to Level 7! While you read through Level 7 in your book, you can check out the resources from Grafika & Syntax, as they teach you how to build the IncrediCards program:

Thank you for helping build the Boss Battle simulation, so that Paul Python could have a fighting chance!

--Queen Cobra

⚠️ **GitHub.com Fallback** ⚠️