Teaching Python 3.6 with Games¶
Introduction¶
- My goal with this webinar is to share what I’ve learned teaching students to program using Python to create video games, along with a new Arcade library for 2D graphics.
- So if you are an educator, or if you work with interns and new employees, hopefully you’ll find some ideas to try.
- If you are more of a visual learner, or if you want to follow up on anything I’m talking about later, you can see my notes for this webinar at: http://2017-craven-webinar.readthedocs.io
Background/Overview¶
- I’m a computer science professor at Simpson College in Iowa.
- I’ve taught an Intro to Programming class using Python and Pygame for many years.
- I created Program Arcade Games, a website, book, and video series to learn programming.
- Students learn by creating their own video games.
- About 1,800 people use it to learn each weekday during the school year.
- Pygame got to be a limiting factor to make the curriculum better.
- Buggy, inconsistent, didn’t use new Python 3 features.
- Students spent too much time learning the ‘oddities’ of Pygame when they could have been learning more programming.
- Create the Arcade Library
- Easy library for 2D video games
- Fix issues with Pygame
- Faster, OpenGL based
- Take advantage of Python 3 features like type hinting.
- I have a new version of my on-line book that I’m working on that uses the Arcade Library instead of Pygame: Learn Python with Arcade Academy
Tip 1: Revise Your Pedagogy¶
- Pedagogy (how you teach) should be treated like software.
- We keep releasing “learn to program” books, as one-and-done books. This is terrible.
- This applies even if you aren’t writing a “how to program” book. For example, if you are writing docs on your in-house software, your docs on your company’s development procedures.
- Most books probably haven’t even had one set of people go through them to learn how to program before being released to market.
- Realize the first release of learning material, just like software, will be terrible.
- Revise your notes. Teach from the materials, find student questions, update it, repeat.
- Even the second version will likely be bad. When I create materials, it isn’t until at least the third version I start being happy with the materials.
- Don’t restart from scratch.
- After 8 years of revisions, 16 semesters of teaching the class, you can fine tune a class to a thing of beauty. For example, imagine updating a question where you add the text “If you think the answer is zero, go back and reread Section 12.3.” can save you time, the student time, and you end up with a student who understands the material better than last semester’s student. It is engineering.
- Remember: Any documentation you use watch people use it, take notes on how to make it better, and then engineer it. Make science out of learning.
Tip 2: Use a Good Tool-Chain for Teaching Materials¶
- How do you make this constant revision easy?
- I suggest using Sphinx. Sphinx is the software used to create the official
Python documentation. It is a “Static Content Generator.”
- Note, there are a lot of other Static Content Generators out there you can use for your materials. I’ve also tried using a Wiki. I prefer Sphinx for its Python integration.
- Sphinx uses Restructured Text, which is much better than editing HTML by hand. You can also make e-books out of it.
- See this example of a Networking class I did: https://github.com/pvcraven/networking-class/commits/master
- You can easily build
.rst
files using Sphinx in PyCharm with the Sphinx task:
- PyCharm is a great editor to type up your restructured text files, as it can spell check, auto-complete rst directives, and even check and let you know if you have undefined references. You can look up references.
- It is easy to include syntax-highlighted code directly in your materials. You can include code snippets, or have the code in separate files.
print("Simple code snippet")
.. literalinclude:: ../../examples/bouncing_ball.py
:caption: bouncing_ball.py
:linenos:
- Store revisions to your materials using Git and a site like GitHub. (Note, BitBucket offers free private repositories if you don’t want to share the source to your materials.)
- Use Read the Docs to automatically build and host your materials, or use cloud hosting.
- The end result:
- Use any computer to update your teaching materials
- Work with other developers in your team to keep docs up to date using your same in-house VCS.
- Push the changes and have them automatically built
- Even use bug/feature tracking to keep track of to-do lists
- If you teach people over-and-over, you’ll end up with high-quality materials.
- Web Development
- Networking
- Intro to Programming
- 3D Graphics with Blender
Tip 3: Parameter and Type Hinting¶
The beauty of modern editors, is that for most students, you won’t even have
to point out that this exists. As soon as you type arcade.
when using an IDE like PyCharm, you get a pop-up
with the Arcade Library functions. This helps the students quite a bit.
Once you are start typing the function parameters, you also get a pop-up with the parameters and types:
Note
Parameter Names are Important
This means the parameter names have to be good. What might make sense to the library programmer does not always make sense to the new programmer trying to figure use the library.
I’ve spent time time tuning the parameter names for the Arcade Library based on my in-person observations of what names seem to work best with students figuring out the library.
If you type the parameters incorrectly, you’ll get an warning before you get any further:
The Arcade Library specifies parameter types using the new type hinting conventions introduced in Python 3.5 via PEP 484 and PEP 526.
The numbers and typing modules in Python help support type hinting. The code to implement type hinting in the Arcade Library looks like this:
def open_window(width: Number, height: Number, window_title: str, resizable: bool = False):
In short, I’ve found these PyCharm features to be very effective with new programmers:
- Pop-up lists of functions.
- When typing a function, having the IDE pop up a list of good parameter variable names. IDE choice aside, it is important that the parameter names proven understandable as shown by actual experience observing programmers new to the library.
- Type hinting. Get some of the advantages of strong typing without the hassle of requiring new programmers to declare variables before using them. (If you’ve forgotten the difference between strong, weak, dynamic, and static typing, I suggest a quick review.)
Note
I hope someday we will also see docs for functions as you type them. Or a link to the HTML docs.
Students often treat the type hinting warnings as errors rather than warnings. This helps a lot with new programmers because they figure out their errors as they do the initial coding, rather than trying to figure out why a 500 line program doesn’t work after they’ve coded it.
Tip 4: Show Students the Linter¶
Stereotypically, programmers are shown with terrible clothing style choices. In reality, we are all about style. Style of our code. For Python, this is all defined in PEP-8.
This might catch new programmers off-guard. A person might celebrate that feeling of accomplishment when their algorithm works. But get discouraged with the teacher nit-picks their program’s style. New programmers often have issues with:
- Inconsistent indentation
- Forgetting to put spaces after commas
- Huge sections of blank lines in the middle of their program
- No blank lines in their program
In my experience:
- Feedback after-the-fact on style issues is mostly ignored, and if it isn’t ignored it just serves to discourage students.
- Asking students to run a linter before turning in their program is just an extra step that they often skip.
I like Python because it is not so rigid as to discourage new programmers. But I do want to encourage new programmers to learn and adhere to programming standards. With pop-up warnings in the code, we get the best of both worlds. Here is what PyCharm does:
Since it comes from the editor, it seems more “official” than coming from the instructor. It also feels less like a personal insult to their intelligence. Poor students who don’t care about programming can still ignore it. Average to good students can pay attention and write code that conforms to style guidelines.
In my experience, once students got PEP-8 warnings in their editor, the percent of student code that adhered to style guidelines improved.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | """
Tip 3:
Function completion
PEP-8
Spell checking
Tip 4:
Use type hinting
"""
# Function completion when you type-to
# Pull up documentation with Ctrl-Q
# If you try setting the background color with only 2 numbers, you'll get an type hint warning
# (Show how to open declaration, and how type hinting is done)
# Forget to put a space after a comma, and get a PEP-8 warning
# This is a program a student wrote his second week of class
# He wrote it and, like many students, also made sure PyCharm showed no issues.
# See that you don't have to declare or create classes to do stuff in arcade!
import arcade
arcade.open_window(800, 600, "Flying V")
arcade.set_background_color((53, 242, 252))
arcade.start_render()
"""Guitar Body"""
arcade.draw_triangle_filled(450, 300, 150, 300, 20, 100, arcade.color.CADMIUM_RED)
arcade.draw_triangle_filled(450, 300, 150, 300, 20, 500, arcade.color.CADMIUM_RED)
"""Guitar Neck"""
arcade.draw_rectangle_filled(450, 300, 350, 50, arcade.color.DEEP_CHAMPAGNE)
"""Guitar Head"""
arcade.draw_triangle_filled(625, 260, 625, 340, 751, 300, arcade.color.BLACK)
"""Guitar Pickups"""
arcade.draw_rectangle_filled(270, 300, 50, 80, arcade.color.SILVER)
"""Fret Separators"""
arcade.draw_line(625, 325, 625, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(604, 325, 604, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(583, 325, 583, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(562, 325, 562, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(541, 325, 541, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(520, 325, 520, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(499, 325, 499, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(478, 325, 478, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(457, 325, 457, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(436, 325, 436, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(415, 325, 415, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(394, 325, 394, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(373, 325, 373, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(352, 325, 352, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(331, 325, 331, 275, arcade.color.BROWN, border_width=2)
arcade.draw_line(310, 325, 310, 275, arcade.color.BROWN, border_width=2)
"""Fret Indicators"""
arcade.draw_circle_filled(572.5, 300, 5, arcade.color.LIGHT_BROWN)
arcade.draw_circle_filled(530.5, 300, 5, arcade.color.LIGHT_BROWN)
arcade.draw_circle_filled(383.5, 312, 5, arcade.color.LIGHT_BROWN)
arcade.draw_circle_filled(383.5, 288, 5, arcade.color.LIGHT_BROWN)
"""Strings"""
arcade.draw_line(260, 280, 640, 280, arcade.color.GRAY, border_width=1)
arcade.draw_line(260, 288, 670, 288, arcade.color.GRAY, border_width=1)
arcade.draw_line(260, 296, 700, 296, arcade.color.GRAY, border_width=1)
arcade.draw_line(260, 304, 700, 304, arcade.color.GRAY, border_width=1)
arcade.draw_line(260, 312, 670, 312, arcade.color.GRAY, border_width=1)
arcade.draw_line(260, 320, 640, 320, arcade.color.GRAY, border_width=1)
"""String Exits"""
arcade.draw_circle_filled(640, 280, 2, arcade.color.SILVER)
arcade.draw_circle_filled(670, 288, 2, arcade.color.SILVER)
arcade.draw_circle_filled(700, 296, 2, arcade.color.SILVER)
arcade.draw_circle_filled(700, 304, 2, arcade.color.SILVER)
arcade.draw_circle_filled(670, 312, 2, arcade.color.SILVER)
arcade.draw_circle_filled(640, 320, 2, arcade.color.SILVER)
"""String Entrances"""
arcade.draw_circle_filled(260, 280, 2, arcade.color.BLACK)
arcade.draw_circle_filled(260, 288, 2, arcade.color.BLACK)
arcade.draw_circle_filled(260, 296, 2, arcade.color.BLACK)
arcade.draw_circle_filled(260, 304, 2, arcade.color.BLACK)
arcade.draw_circle_filled(260, 312, 2, arcade.color.BLACK)
arcade.draw_circle_filled(260, 320, 2, arcade.color.BLACK)
"""Volume and Tone Knobs"""
arcade.draw_circle_filled(160, 190, 10, arcade.color.BLACK)
arcade.draw_circle_filled(210, 215, 10, arcade.color.BLACK)
arcade.draw_circle_filled(260, 240, 10, arcade.color.BLACK)
arcade.draw_circle_outline(160, 190, 11, arcade.color.ANTI_FLASH_WHITE, border_width=2)
arcade.draw_circle_outline(210, 215, 11, arcade.color.ANTI_FLASH_WHITE, border_width=2)
arcade.draw_circle_outline(260, 240, 11, arcade.color.ANTI_FLASH_WHITE, border_width=2)
arcade.finish_render()
arcade.run()
|
Tip 5: Making IDEs Easier¶
Text editors are simple. Students write a file, and then run the file.
IDEs are more complex. The most common issues for new students:
- Students must create a project, then add the files to the project. Some
students have difficulty with this concept, and even basic file/folder
management. If a student just wants to run a two-line
for
loop example, forcing her to create a project to run it is crazy. - Configurable IDEs have a lot of tool windows and other things that can be messed up, making it difficult for a student to get back to the state where they match the instructor’s setup.
- Students often run the wrong program:
However, the trade-off for this complexity:
- Code-completion
- PEP-8 Linting
- Type hinting
- Spell checking
- If an error occurs, the IDE points right at the error without needing to decode a stack trace.
For many years I was in the “text editor” camp. This last year, after seeing the improved code quality with the PEP-8 linting, I’m in the IDE camp.
To mitigate the issues with forcing a project on the student, we create one project to use for the whole class. Students start with a pre-defined set of folders. There is one folder for each lab, and a folder for “scratch” code that students can store quick examples in.
Tip 6: Teach Students to Search for Code Examples¶
Real-life programming rarely involves writing code from scratch. In fact, feedback we get from recent alumni is that they had not enough experience programming with large pre-existing project.
To help teach this, I provide a lot of code samples:
http://arcade.academy/examples/index.html
Don’t just give students one example and step through it. That’s a snooze-fest. Give lots of example code that form building blocks. Make it easy to navigate. Then give tasks that requires students to go through those code samples and build their own program.
Tip 7: Show Students API Docs¶
Don’t just show students how to program from your materials. Make it a point to show them the API docs. Have them look up functions out of the API docs and use them:
Tip 8: Explain IDE Features As You Use Them¶
- Moving lines up and down
- Indent/unindent blocks of code
- Multi-cursor
- Go to definition
Results¶
Playlist with some of the games:
https://www.youtube.com/playlist?list=PLUjR0nhln8ub1tPayFjz7w-LCTQ_gYs7V
Note that these aren’t paint-by-number programs. There is a lot of creativity in what the students have put together.
Here is a breakout program that a student did:
The code mostly passes the linter check, and for a first-semester programming student is a rather impressive feat:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 | """
Lab 12 - Final Project
Audio from http://opengameart.org
"""
import arcade
import random
SPRITE_SCALING = 0.5
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
MOVEMENT_SPEED = 8
BALL_SPEED = 4
INSTRUCTION_PAGE = 0
GAME_RUNNING = 1
LEVEL_COMPLETE_PAGE = 2
GAME_OVER = 3
GAME_END = 4
def create_power_up(x, y, power_up_list, all_sprites_list):
power_up = None
power_up_type = random.randrange(3)
if power_up_type == 0:
# Image from Game Freezer: http://www.gamesfreezer.eu/2015_06_01_archive.html
power_up = arcade.Sprite("images/extra_life.png", 0.4 * SPRITE_SCALING)
if power_up_type == 1:
# Image from Prek-8.com:
power_up = arcade.Sprite("images/add_balls.png", 0.4 * SPRITE_SCALING)
if power_up_type == 2:
# image from ClipartFest: https://clipartfest.com/categories/view/
power_up = arcade.Sprite("images/speed_up.png", 0.4 * SPRITE_SCALING)
power_up.power_up_value = power_up_type
power_up.change_y = -2
power_up.center_x = x
power_up.center_y = y
power_up_list.append(power_up)
all_sprites_list.append(power_up)
class Ball(arcade.Sprite):
def __init__(self, all_sprites_list, power_up_list, wall_list,
brick_list, blue_brick_list, orange_brick_list, wall_brick_list,
player_sprite, application):
# Image from Kenney.nl
super().__init__("images/ball.png", 2 * SPRITE_SCALING)
self.all_sprites_list = all_sprites_list
self.wall_list = wall_list
self.brick_list = brick_list
self.blue_brick_list = blue_brick_list
self.orange_brick_list = orange_brick_list
self.wall_brick_list = wall_brick_list
self.power_up_list = power_up_list
self.player_sprite = player_sprite
self.brick_sound = arcade.load_sound("sounds/brick_hit.wav")
self.center_x = self.player_sprite.center_x
self.center_y = 0.05 * SCREEN_HEIGHT + SCREEN_HEIGHT/15
self.change_x = random.randrange(-3, 4)
self.change_y = BALL_SPEED
self.application = application
def update(self):
self.center_x += self.change_x
orange_brick_hit_list = arcade.check_for_collision_with_list(self, self.orange_brick_list)
for brick in orange_brick_hit_list:
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = brick.center_x
blue_brick.center_y = brick.center_y
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
brick.kill()
arcade.play_sound(self.brick_sound)
self.application.score += 1
if self.change_x > 0:
self.right = brick.left
else:
self.left = brick.right
if len(orange_brick_hit_list) > 0:
self.change_x *= -1
blue_brick_hit_list = arcade.check_for_collision_with_list(self, self.blue_brick_list)
for brick in blue_brick_hit_list:
brick.kill()
arcade.play_sound(self.brick_sound)
self.application.score += 1
if random.randrange(10) == 0:
create_power_up(self.center_x, self.center_y, self.power_up_list, self.all_sprites_list)
if self.change_x > 0:
self.right = brick.left
else:
self.left = brick.right
if len(blue_brick_hit_list) > 0:
self.change_x *= -1
wall_hit_list = arcade.check_for_collision_with_list(self, self.wall_list)
for wall in wall_hit_list:
if self.change_x > 0:
self.right = wall.left
else:
self.left = wall.right
if len(wall_hit_list) > 0:
self.change_x *= -1
wall_brick_hit_list = arcade.check_for_collision_with_list(self, self.wall_brick_list)
for wall_brick in wall_brick_hit_list:
if self.change_x > 0:
self.right = wall_brick.left
else:
self.left = wall_brick.right
if len(wall_brick_hit_list) > 0:
self.change_x *= -1
self.center_y += self.change_y
orange_brick_hit_list = arcade.check_for_collision_with_list(self, self.orange_brick_list)
for brick in orange_brick_hit_list:
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = brick.center_x
blue_brick.center_y = brick.center_y
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
brick.kill()
arcade.play_sound(self.brick_sound)
if self.change_y > 0:
self.top = brick.bottom
else:
self.bottom = brick.top
self.application.score += 1
if len(orange_brick_hit_list) > 0:
self.change_y *= -1
blue_brick_hit_list = arcade.check_for_collision_with_list(self, self.blue_brick_list)
for brick in blue_brick_hit_list:
brick.kill()
arcade.play_sound(self.brick_sound)
if random.randrange(10) == 0:
create_power_up(self.center_x, self.center_y, self.power_up_list, self.all_sprites_list)
if self.change_y > 0:
self.top = brick.bottom
else:
self.bottom = brick.top
self.application.score += 1
if len(blue_brick_hit_list) > 0:
self.change_y *= -1
wall_hit_list = arcade.check_for_collision_with_list(self, self.wall_list)
for wall in wall_hit_list:
if self.change_y > 0:
self.top = wall.bottom
else:
self.bottom = wall.top
if len(wall_hit_list) > 0:
self.change_y *= -1
wall_brick_hit_list = arcade.check_for_collision_with_list(self, self.wall_brick_list)
for wall_brick in wall_brick_hit_list:
if self.change_y > 0:
self.top = wall_brick.bottom
else:
self.bottom = wall_brick.top
if len(wall_brick_hit_list) > 0:
self.change_y *= -1
def get_score(self):
arcade.draw_text("Score: " + str(self.score), 40, 10, arcade.color.WHITE, 24)
class MyApplication(arcade.Window):
""" Main application class. """
def __init__(self, width, height):
super().__init__(width, height)
# Sprite lists
self.all_sprites_list = None
# Set up the player.
self.score = 0
self.player_sprite = None
self.player_sound = arcade.load_sound("sounds/player_hit.wav")
self.wall_list = None
self.brick_list = None
self.wall_brick_list = None
self.blue_brick_list = None
self.orange_brick_list = None
self.power_up_value = None
self.power_up_list = None
self.ball = None
self.ball_list = None
self.lives = 3
self.next_level = False
self.level = None
self.current_state = INSTRUCTION_PAGE
def setup(self):
# Sprite lists
self.score = 0
self.all_sprites_list = arcade.SpriteList()
self.wall_list = arcade.SpriteList()
self.brick_list = arcade.SpriteList()
self.blue_brick_list = arcade.SpriteList()
self.orange_brick_list = arcade.SpriteList()
self.wall_brick_list = arcade.SpriteList()
self.power_up_list = arcade.SpriteList()
self.ball_list = arcade.SpriteList()
self.level = 0
self.next_level = False
# Image from Kenney.nl
self.player_sprite = arcade.Sprite("images/player.png",
0.25 * SPRITE_SCALING)
self.player_sprite.center_x = 0.5 * SCREEN_WIDTH
self.player_sprite.center_y = 0.075 * SCREEN_HEIGHT
self.all_sprites_list.append(self.player_sprite)
arcade.set_background_color(arcade.color.AMAZON)
# -- Set up the boundary walls.
for y in range(18, 600, 32):
# Image from Kenney.nl
wall = arcade.Sprite("images/wall.png", 0.5 * SPRITE_SCALING)
wall.center_x = 18
wall.center_y = y
self.all_sprites_list.append(wall)
self.wall_list.append(wall)
for y in range(18, 600, 32):
# Image from Kenney.nl
wall = arcade.Sprite("images/wall.png", 0.5 * SPRITE_SCALING)
wall.center_x = 582
wall.center_y = y
self.all_sprites_list.append(wall)
self.wall_list.append(wall)
for x in range(12, 600, 32):
# Image from Kenney.nl
wall = arcade.Sprite("images/wall.png", 0.5 * SPRITE_SCALING)
wall.center_x = x
wall.center_y = 596
self.all_sprites_list.append(wall)
self.wall_list.append(wall)
def level_1(self):
self.next_level = False
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
for row in range(4):
for column in range(-(8 - 2 * row), 0):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = SCREEN_WIDTH / 10 * row + SCREEN_WIDTH / 2
blue_brick.center_y = 30 * column + 590
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(4):
for row in range(-2 * column, 0):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 60 * column + 60
blue_brick.center_y = 30 * row + 590
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
def level_2(self):
self.next_level = False
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
for row in range(4):
for column in range(0, (4 - row)):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = SCREEN_WIDTH / 10 * row + 70
blue_brick.center_y = 30 * column + 300
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(4):
for column in range(-(4 - row), 0):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = SCREEN_WIDTH / 10 * row + 70
blue_brick.center_y = 30 * column + 590
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(5):
for row in range(0, column):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 60 * column + 290
blue_brick.center_y = 30 * row + 300
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(5):
for row in range(-column, 0):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 60 * column + 290
blue_brick.center_y = 30 * row + 590
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(5):
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 60 * column + 180
wall_brick.center_y = 430
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
def level_3(self):
self.next_level = False
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = SCREEN_WIDTH / 2
orange_brick.center_y = 3 * SCREEN_HEIGHT / 4
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for row in range(3):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 240
blue_brick.center_y = 30 * row + 420
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(3):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 360
blue_brick.center_y = 30 * row + 420
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 300
blue_brick.center_y = 420
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 300
blue_brick.center_y = 480
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(5):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 180
orange_brick.center_y = 30 * row + 390
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for row in range(5):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 420
orange_brick.center_y = 30 * row + 390
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for column in range(3):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 60 * column + 240
orange_brick.center_y = 390
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for column in range(3):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 60 * column + 240
orange_brick.center_y = 510
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for row in range(7):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 120
blue_brick.center_y = 30 * row + 360
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(7):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 480
blue_brick.center_y = 30 * row + 360
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(5):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 60 * column + 180
blue_brick.center_y = 360
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(5):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 60 * column + 180
blue_brick.center_y = 540
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
def level_4(self):
self.next_level = False
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
for row in range(5):
for column in range(8):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = SCREEN_WIDTH / 10 * column + 90
blue_brick.center_y = 30 * row + 430
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for column in range(4):
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 120 * column + 110
wall_brick.center_y = 220
wall_brick.change_x = -2
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
for column in range(4):
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 120 * column + 130
wall_brick.center_y = 280
wall_brick.change_x = 2
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
def level_5(self):
self.next_level = False
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 300
orange_brick.center_y = 350
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for row in range(3):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 240
blue_brick.center_y = 30 * row + 320
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(3):
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 360
blue_brick.center_y = 30 * row + 320
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 300
blue_brick.center_y = 320
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
# Image from Kenney.nl
blue_brick = arcade.Sprite("images/blue_brick.png", 0.6 * SPRITE_SCALING)
blue_brick.center_x = 300
blue_brick.center_y = 380
self.all_sprites_list.append(blue_brick)
self.brick_list.append(blue_brick)
self.blue_brick_list.append(blue_brick)
for row in range(5):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 180
orange_brick.center_y = 30 * row + 290
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for row in range(5):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 420
orange_brick.center_y = 30 * row + 290
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for column in range(3):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 60 * column + 240
orange_brick.center_y = 290
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for column in range(3):
# Image from Kenney.nl
orange_brick = arcade.Sprite("images/orange_brick.png", 0.6 * SPRITE_SCALING)
orange_brick.center_x = 60 * column + 240
orange_brick.center_y = 410
self.all_sprites_list.append(orange_brick)
self.brick_list.append(orange_brick)
self.orange_brick_list.append(orange_brick)
for column in range(4):
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 140 * column + 90
wall_brick.center_y = 220
wall_brick.change_x = 2
wall_brick.change_y = 0
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
for column in range(4):
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 140 * column + 90
wall_brick.center_y = 480
wall_brick.change_x = -2
wall_brick.change_y = 0
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 90
wall_brick.center_y = 350
wall_brick.change_x = 0
wall_brick.change_y = -2
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
# Image from Kenney.nl
wall_brick = arcade.Sprite("images/black_brick.png", 0.6 * SPRITE_SCALING)
wall_brick.center_x = 510
wall_brick.center_y = 350
wall_brick.change_x = 0
wall_brick.change_y = 2
self.all_sprites_list.append(wall_brick)
self.wall_brick_list.append(wall_brick)
def add_life(self):
self.lives += 1
def add_balls(self):
for i in range(3):
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
def speed_up(self):
for ball in self.ball_list:
ball.change_y += 1
def draw_instructions_page(self):
arcade.draw_text("Welcome to Brick Breaker!", 50, 500, arcade.color.WHITE, 32)
arcade.draw_text("Press SPACE to Continue", 55, 350, arcade.color.WHITE, 32)
self.lives = 3
def draw_game(self):
"""
Draw all the sprites, along with the score.
"""
# Draw all the sprites.
self.all_sprites_list.draw()
# Draw the score in the bottom left.
Ball.get_score(self)
arcade.draw_text("Level " + str(self.level), 455, 10, arcade.color.WHITE, 24)
arcade.draw_text(str(self.lives) + " Lives left", 230, 10, arcade.color.WHITE, 24)
def draw_level_complete(self):
# Draw at the end of every level
arcade.draw_text("Level " + str(self.level), 150, 400, arcade.color.WHITE, 64)
arcade.draw_text("Complete!", 100, 290, arcade.color.WHITE, 64)
arcade.draw_text("Press Space to Continue", 65, 210, arcade.color.WHITE, 32)
def draw_game_over(self):
arcade.draw_text("Game Over", 80, 330, arcade.color.WHITE, 64)
arcade.draw_text("Press R to Restart", 80, 220, arcade.color.WHITE, 40)
arcade.draw_text("Press Q to Quit", 120, 140, arcade.color.WHITE, 40)
def draw_game_end(self):
arcade.draw_text("Congratulations!", 50, 520, arcade.color.WHITE, 52)
arcade.draw_text("You Won!", 90, 420, arcade.color.WHITE, 72)
arcade.draw_text("Your Final Score was ", 40, 300, arcade.color.WHITE, 40)
arcade.draw_text(str(self.score), 280, 240, arcade.color.WHITE, 40)
arcade.draw_text("Press R to Restart", 80, 140, arcade.color.WHITE, 40)
arcade.draw_text("Press Q to Quit", 120, 70, arcade.color.WHITE, 40)
def on_draw(self):
arcade.start_render()
# Draw all the sprites.
self.all_sprites_list.draw()
if self.current_state == INSTRUCTION_PAGE:
self.draw_instructions_page()
elif self.current_state == GAME_RUNNING:
self.draw_game()
elif self.current_state == LEVEL_COMPLETE_PAGE:
self.draw_level_complete()
elif self.current_state == GAME_END:
self.draw_game_end()
else:
self.draw_game_over()
Ball.get_score(self)
if len(self.brick_list) == 0 and self.next_level:
if self.level < 5:
self.level += 1
if self.level == 1:
self.level_1()
elif self.level == 2:
self.level_2()
elif self.level == 3:
self.level_3()
elif self.level == 4:
self.level_4()
elif self.level == 5:
self.level_5()
else:
self.draw_game_end()
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
if key == arcade.key.Q:
MyApplication.close(self)
elif key == arcade.key.SPACE and len(self.brick_list) == 0:
if not self.next_level:
if self.level < 6:
self.current_state = GAME_RUNNING
self.draw_game()
self.next_level = True
else:
self.current_state = GAME_END
elif key == arcade.key.R:
self.current_state = INSTRUCTION_PAGE
self.draw_instructions_page()
MyApplication.setup(self)
elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_sprite.change_x = -MOVEMENT_SPEED
elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = MOVEMENT_SPEED
def on_key_release(self, key, modifiers):
if key == arcade.key.LEFT or key == arcade.key.A \
or key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = 0
def animate(self, delta_time):
for ball in self.ball_list:
if ball.center_y < 0:
ball.kill()
if self.lives > 0 and len(self.ball_list) == 0:
self.lives -= 1
ball = Ball(self.all_sprites_list, self.power_up_list,
self.wall_list, self.brick_list, self.blue_brick_list,
self.orange_brick_list, self.wall_brick_list, self.player_sprite, self)
self.ball_list.append(ball)
self.all_sprites_list.append(ball)
elif self.lives <= 0:
for power_up in self.power_up_list:
power_up.kill()
self.draw_game_over()
self.current_state = GAME_OVER
for power_up in self.power_up_list:
if power_up.top <= 0:
power_up.kill()
if len(self.brick_list) == 0 and self.level > 0:
if not self.next_level:
for wall_brick in self.wall_brick_list:
wall_brick.kill()
for ball in self.ball_list:
ball.kill()
for power_up in self.power_up_list:
power_up.kill()
self.current_state = LEVEL_COMPLETE_PAGE
self.draw_level_complete()
wall_player_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
for wall in wall_player_hit_list:
if self.player_sprite.right >= wall.left and self.player_sprite.center_x > SCREEN_WIDTH / 2:
self.player_sprite.right = wall.left
elif self.player_sprite.left <= wall.right:
self.player_sprite.left = wall.right
for wall_brick in self.wall_brick_list:
if self.current_state != GAME_RUNNING:
wall_brick.change_x = 0
wall_brick.change_y = 0
elif self.level == 4:
if wall_brick.left < 70:
for brick in self.wall_brick_list:
brick.change_x *= -1
elif wall_brick.right > 550:
for brick in self.wall_brick_list:
brick.change_x *= -1
elif self.level == 5:
if wall_brick.center_x < 90 and wall_brick.change_x < 0:
wall_brick.change_x = 0
wall_brick.change_y = -2
elif wall_brick.center_y < 220 and wall_brick.change_y < 0:
wall_brick.change_x = 2
wall_brick.change_y = 0
elif wall_brick.center_x > 510 and wall_brick.change_x > 0:
wall_brick.change_x = 0
wall_brick.change_y = 2
elif wall_brick.center_y > 480 and wall_brick.change_y > 0:
wall_brick.change_x = -2
wall_brick.change_y = 0
ball_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.ball_list)
for ball in ball_hit_list:
arcade.play_sound(self.player_sound)
if ball.change_y < 0:
ball.change_y *= -1
if ball.center_x < self.player_sprite.center_x:
ball.change_x = random.randrange(-5, -2)
elif ball.center_x > self.player_sprite.center_x:
ball.change_x = random.randrange(3, 6)
else:
ball.change_x = 0
power_up_hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.power_up_list)
for power_up in power_up_hit_list:
power_up.kill()
if power_up.power_up_value == 0:
self.add_life()
elif power_up.power_up_value == 1:
self.add_balls()
elif power_up.power_up_value == 2:
self.speed_up()
self.all_sprites_list.update()
self.ball_list.update()
self.player_sprite.update()
self.wall_brick_list.update()
window = MyApplication(SCREEN_WIDTH, SCREEN_HEIGHT)
window.setup()
arcade.run()
|