The final assignment for CS 1110 has always been to develop a game in Python. Game development ties together everything that we have learned in this class into one large project. While modern games are far too advanced for this class, classics of the original arcade era (like Pac-Man, Space Invaders, or Breakout) are well within your ability. This year, our final assignemnt will be a Frogger clone. This is a brand new assignment which includes a lot of the material that was introduced this year for the first time.
|
If you have never played Frogger before (or its modern equivalent Crossy Road), there are a few versions online as Flash games (though Flash is going away soon). You can try out either Frogger Classic or HappyHopper. Either will give you a good feel of the game. Our assignment shown above is a simplified version of the game with no submerging turtles or pick-ups. However, you can add these as extensions at the end if you wish.
This assignment is long - much longer than anything that you have written before. Our final A-level solution to this assignment required us to write 915 lines of code and specifications. However, as always, specifications and class/method headers were the bulk of what we wrote and it was only 390 lines of code without them. The solution for the A/B border appears to be about 310 lines of code without specifications, and the B/C border is about 175 lines of code without specifications. This assignment is designed with the expectation that not everyone in this course will finish it. That is why we are comfortable replacing the final with this assignment (though you should pay close attention to the Academic Integrity policy on this assignment).
Because we do not expect everyone to finish this assignment, we have made the grade boundaries explicit in these instructions. Every single task has a letter grade attached to it. If you make it that far with no bugs (which is a big if), then that is the grade you will earn for your work. Unlike the prelims, we do not want this grade to be a surprise. With that said, we honestly believe that everyone in this class can earn at least a B on this assignment and the majority of you should be able to earn an A. The trick is to start early, break the problem up into manageable pieces, and program/test incrementally. From our experience on similar assignments, we are assuming that the average time to complete this is a little over 20 hours, with many students going to 30 hours. That means if you work on this assignment for either an hour or an hour-and-a-half every day, you should be able to finish it all.
In the past, we have made the game a lot more open-ended than this, with much less structure. While we need the structure to replace the final, there is an opportunity for you to let your creativity shine in the extensions. We will award a limited amount of extra credit (though never more than half a letter grade) for particularly interesting additional features.
Author: W. White
Learning Objectives
This final assignment has several important objectives:
- It gives you practice reading official class documentation and APIs.
- It gives you experience with a complex, interactive application.
- It gives you experience designing helper functions to structure your code properly.
- It gives you experience using constants to make your code more readable.
- It gives you experience with programming animation using coroutines.
- It gives you experience with an open-ended project that is not fully specified.
Table of Contents
- Academic Integrity
- Organization and Scope
- Overview of Froggit
- Task 1: Setup
- Task 2: The Basic Game
- Task 3: Animation and Polish
- Additional Features
- Finishing Touches
Academic Integrity
Collaboration Policy
Because of COVID, we are using this project to replace our final exam. But this means that we have to treat this differently from normal assignments. The reason we have exams is so that we can assess your ability with material without getting help. And that is how this assignment has to be as well. You must work on this assignment alone. If that means you are not going to be able to finish the assignment, understand that this is one of the design decisions of replacing an exam with an assignment.
With that said, we know that people like to show off their work for this project. So you are allowed to show off videos of your game, or allow other people to play your game in a controlled setting. But you may not show anyone your code until after the submission deadline. Be very careful with your code, as someone may copy it.
Because of the huge number of lines of code in this assignment, it is trivially easy for us to discover when someone copies code from another person. Everyone’s code is going to be radically different, and so we will be able to prosecute violations (that hold up under appeal) on as little as 5 lines of code. We will catch at least 99% of all violations on this assignment (and we expect there to be a few).
Because this assignment is replacing the final, the penalties for an academic integrity violation will be severe. It will not be a question of whether or not you fail the assignment. Many violations will result in failure for the entire course. Do not copy code from other students or online resources. Do not hire people to complete your assignment for you. Do not post your code online or send it to your friends (because you will discover that your so-called-friends will copy it). We have seen it all and caught it all. Do not do it.
Copyrighted Material
There is another Academic Integrity issue with this assignment: copyrighted material. Gameplay cannot be copyrighted. You can make a game that plays the same as another. This established in the early days of gaming when Space Invaders lost its court case against Galaxian and Galaga. However, artwork in a game is copyrighted (and in the case of Space Invaders, even trademarked). So you should be careful about adding additional artwork to this game.
While there might be an argument for fair use – this is a class project – your instructor prefers that you avoid the copyright issue entirely. In general, you are only allowed to use copyrighted material if you have a license to do so. For example, many of the songs and sound effects in the NewGrounds library are available for you to use under an Attribution License. That means you are free to use it so long as you cite the source in your documentation (e.g. your header comments). This is okay. A license where you have to pay is not okay.
If you are in doubt as to whether you have a license to use something, ask us on Piazza.
Office Hours Policy
POLICY REVISION: The policy below now only applies to Tasks 2 and 3. You may get normal assignment help on Task 1.
This assignment is replacing an exam. That means we have to be fair about how we help students on this assignment. We cannot allow someone to get a grade that they do not deserve simply because they camped out in office hours and had a TA or consultant pull them through the assignment.
The rules for this assignment will be exactly the same as they were for the function pixellate
on the last assignment. That is, we will be applying the no code rule. When we said that you may not show your code to anyone, that includes the instructors or the course staff. We mean it. We will gladly look at error message. And if you want to share a Zoom video of you playing your game, we will look at that. But you may not show us code unless we explicitly ask for it, and we will only do this in highly unusual circumstances (typically related to a bug in the game2d
module).
This can be a little scary, because there is a lot of new material in this assignment, particularly with animation and graphical applications. Fortunately, all of the final labs in this course will be devoted to this assignment. And the labs have no academic integrity restrictions. You can talk about them and show your code to the instructors or even other students.
If you are really stuck, talk to a staff member, but do not show your code. We may be able to come up with a supportive lab activity (solving a different but similar problem) to help you.
Organization and Scope
This is assignment is even longer than Assignment 6. As with that assignment, the trick is to pacing yourself. This assignment has three full weeks, and this is more than enough time if you work on a little bit every day. In fact, we have designed this assignment so that the average person needs between an hour to an hour-and-a-half each day to get the highest score.
While there are no test cases this time, you should be able to figure out if everything is working simply by playing the game. There are no tricky “restore everything to how it was” like with Turtles; no nasty surprises lurking in the specifications. Just get the game working.
Assignment Source Code
To work on this assignment, you will need all of the following:
File | Description |
---|---|
froggit.zip | The application package, with all the source code |
samples.zip | Several programs that give hints on this assignment |
game2d API | The documentation for how to use the game2d classes |
The last item is a link that you should refer to through the assignment, while the other two are files to download. Only the first is a must download, as it contains the all of the source code necessary to complete the assignment.
The second file is a collection of demo code from the lesson on GUI programming, as well as the lesson on coroutines. This sample code contains a lot of hints on how to approach some of the harder parts of this assignment, and we reference these samples throughout the instructions.
As with the imager application, this assignment is organized as a package with several
files. To run the application, change the directory in your command shell to just
outside of the folder froggit
and type
In this case, Python will run the entire folder. What this really means is that it runs the script in __main__.py
. This script imports each of the other modules in this folder to create a complex application. To work properly, the froggit
folder should contain the following:
File | Description |
---|---|
app.py | The primary controller class for the application |
level.py | A secondary controller for a single playable application |
lanes.py | A collection of mini-controllers for the individual lanes |
models.py | Model classes for the game (i.e. Frog) |
consts.py | All module with all of the constant (global variable) values |
game2d | A package with classes that can display graphics on the screen |
Sounds | A folder of sound effects approved for your use |
Fonts | A folder of True Type fonts approved for your use |
Images | A folder of images for the frog and various obstacles |
JSON | A folder with different levels you can play |
For the most part, you only need to understand the first four files - app.py
, level.py
, lanes.py
and models.py
- as well as the JSON
directory. The other files and folders can be ignored. However, if you decide to add a few extensions, it helps to understand how all of these fit together.
app.py
This module contains the controller class Froggit
. This is the controller that launches the application, and is one of four modules that you must modify for this assignment. While it is the primary controller class, you will note that it has no script code. That is contained in the module __main__.py
(which you should not modify).
level.py
This module contains the secondary controller class Level
. This class manages a single level of the game. It works as a subcontroller, just like the example subcontroller.py
in the provided sample code. It is another of the four modules that you must modify for this assignment, and is the one that will require the most original code.
lanes.py
Levels in Frogger consist of horizontal lanes that the frog has to traverse. These could be roads, water, or even just a strip of grass. And each of these lanes is very different from one another. The frog does not die from touching a road, but will die if it is hit by a car on the road. On the other hand, the frog dies in the water (it cannot swim) unless it is touching a log.
This will make the code quite complicated. So to make things much easier, you will make a class for each type of lane: road, water, grass and (exit) hedge. Technically these classes will act as subcontrollers to Level
just as Level
is a subcontroller for Froggit
. In complex applications there are many layers of controllers.
models.py
This module contains the model class Frog
. This class is similar to those found in the pyro.py
demo in the provided sample code. If you want to add other model classes (e.g. turtles or pick-ups), then you should add those here as well. This is the last of the four files you must modify for this assignment.
consts.py
This module is filled with constants (global variables that should not ever change). It is used by app.py
, level.py
, lanes.py
and models.py
to ensure that these modules agree on certain important values. It also contains code for adjusting your default level. You should only modify this file if you are adding additional features to your program.
game2d
This is a package containing the classes you will use to design your game. These are classes that you will subclass, just like we demonstrated in the lesson videos. In particular, the class Froggit
is a subclass of GameApp from this
package. As part of this assignment, you are expected to read the online documentation which describes how to use the base classes.
Under no circumstances should you ever modify this package!
Sounds
This is a folder of sound effects that you will use in the final activity. You are also free to add more if you wish; just put them in this folder. All sounds must be WAV files. While we have gotten MP3 to work on Windows, Python support for MP3 on MacOS is unreliable.
Fonts
This is a folder of True Type Fonts, should you get tired of the default font for this assignment. You can put whatever font you want in this folder, provided it is a .ttf file. Other Font formats (such as .ttc, .otf, or .dfont) are not supported. Be very careful with fonts, however, as they are copyrighted in the same way images are. Do not assume that you can include any font that you find on your computer.
Images
This is a folder with image files for the frog and the obstacles. The GImage and GSprite classes allow you to include these in your game. You can put other images here if you wish.
JSON
This is a folder with JSON files that define a level. You will turn these into dictionaries and use them to place objects in your game. Understanding these files will be a major portion of this assignment. But you should look at them briefly to familiarize yourself with these files.
Assignment Scope
As we explained in class, your game is a subclass of GameApp. The parent class does a lot of work for you. You just need to implement three main methods. They are as follows:
Method | Description |
---|---|
start() | Method to initialize the game state attributes |
update(dt) | Method to update the models for the next animation frame |
draw() | Method to draw all models to the screen |
Your goal is to implement all of these methods according to their (provided) specification.
start()
This method should take the place of __init__
. Because of how Kivy works, initialization code should go here and not in the initializer (which is called before the window is sized properly).
update(dt)
This method should move the position of everything for just one animation step, and resolve any collisions (potentially deleting objects). The speed at which this method is called is determined by the (immutable) attribute fps
, which is set by the constructor. The parameter dt
is time in seconds since the last call to update
.
draw()
This method is called as soon as update
is complete. Implementing this method should be as simple as calling the method draw, inherited from GObject
, on each of the models.
These are the only three methods that you need to implement. But obviously you are not going to put all of your code in those three methods. The result would be an unreadable mess. An important part of this assignment is developing new (helper) methods whenever you need them so that each method is small and manageable. Your grade will depend partly on the design of your program. As one guideline, points will be deducted for methods that are more than 30 lines long (not including specifications or spacing).
You will also need to add methods and attributes to the class Level
in level.py
, the class Frog
in models.py
and the classes Lane
, Road
, Water
. and Hedge
in lanes.py
. All of these classes are completely empty, though we have given you a lot of hints in the class specification. You should read all these specifications.
As you write the assignment, you may find that you need additional attributes. All new instance attributes should be hidden. You should list these new attributes and their invariants as single-line comments after the class specification (as we have done this semester). For example, if the Level
class needs to access the exit locations in a Hedge
lane, then you are going to need a getter for these exits (and we give hints on how to do this).
While documentation and encapsulation is essential for this assignment, you do not need to enforce any preconditions or invariants. However, you may find that debugging is a lot simpler if you do, as it will help you localize errors. But we will not take off points either way, as long as the game runs correctly.
Assignment Organization
This assignment follows the model-view-controller pattern discussed in the lesson videos. The modules are clearly organized so that each holds models, the view, or a controller. The organization of these files is shown below. The arrows in this diagram mean “accesses”. So the Froggit
controller accesses the view and Level
subcontroller. The Level
controller accesses the lanes, the view, and the models. And Lane
and its subclasses access the view and the modesl.
This leads to an important separation of files. Froggit
is never permitted to access anything in models.py
and lanes.py
. Level
is never permitted to access anything in app.py
. The classes in lanes.py
may not access anythign in app.py
or level.py
. And finally, models.py
cannot access anything other than the view. This is an important rule that we will enforce while grading. All of this is shown in the diagram below
The Import Relationships
In addition to the four main modules, there is another module with no class or function definitions. It only has constants, which are global variables that do not change. This is imported by the models
module and the various controllers. It is a way to help them share information.
When approaching this assignment, you should always be thinking about “what code goes where?” If you do not know what file to put things in, please ask on Piazza (but do not post code). Here are some rough guidelines.
Froggit
This controller does very little. All it does is keep track of the game state (e.g. whether or not the game is paused). Most of the time it just calls the methods in Level
, and lets Level
do all the work. However, if you need anything between lives, like a paused message or the final result, this goes here. This is similar to the class MainApp
from the demo subcontroller.py
Level
This class does all the hard work. In addition to the initializer (which is a proper __init__
, not start
), it needs its own update
and draw
methods. This is a subcontroller, and you should use the demo subcontroller.py
(in the provided sample code) as a template.
The most complex method will be the update
and you will certainly violate the 30-line rule if you do not break it up into helpers. For the basic game, this method will need to do the following:
- Animate the frog according to player input
- Move all the obstacles in each lane
- Detect any collisions between the frog and an obstacle
- Remove the frog after a death or a successful exit
In our code, each one of these is a separate helper (though the second one will actually be managed by the Lane
class below). You should think about doing this in your code as well.
The Lanes
There are four types of lanes in the game: road, water, grass, and the hedge. The hedge is where the exits are placed. The road has cars (and trucks). The water has logs to jump on. The grass is a safe zone that gives you a rest.
Because each lane acts differently, you want to make a separate class for each one. However, they do have a lot in common, so they will all be subclasses of the class Lane
. These classes already appear in lanes.py
with their specifications.
Each lane is essentially a mini-level. The frog needs to get succesfully past it. That is why each lane will be its own subcontroller, controlling its own lane. As a subcontroller, each lane should have its own __init__
, update
, and draw
methods. However, most of these will go in Lane
and the other classes will safely inherit them. When the Level
class needs to update the lanes, all it will do is loop through the lanes and call the update
method for each one.
The Models
The models just keep track of data. Most of the time, models just have attributes, with getters and setters. Think Image
from the previous assignment. However, sometimes models have additional methods that perform computation on the data, like swapPixel
.
The models in this assignment are the game objects on screen: the frog and any obstacles. Most of the time, you do not need a new class for a module, as the class GImage does everything that you needs for the cars, logs, and exits. However, the frog is very special and so the frog will need a custom class. We have written the specification of this for you.
If you want to add any additional features to your game, you may need to add some new models here. For example, turtles that will occasionally dive under water have to be animated in the same way that the frog does. So they will need their own classes for exactly the same reason. You should only add new classes for the additional features.
Suggested Micro-Deadlines
You should start on this assignment soon as possible. If you wait until the week before this assignment is due, you will have a hard time completing it. If you work on a little bit of it every day or every other day, then you will enjoy it and get it done on time.
You should implement the application in stages, as described in these instructions. Do not try to get everything working all at once. Make sure that each stage is working before moving on to the next stage.
Set up a schedule. We have suggested some milestones, but make up your own schedule. This schedule assumes that you are working on the assignment one or two hours every day. An average student should be able to complete this in one hour a day, though two hours will give you more time for hunting down bugs. But more importantly, you need to leave time for learning things and asking questions. Above all, do not try to add extensions until you at least finish the basic game. If you add extensions too early, debugging may get very difficult.
Overview of Froggit
The layout of a Froggit game depends on the level file you are using. We have included many different level files in the JSON
directory. The files easy1.json
and easy2.json
are easy games to help you test your game, while roadsonly.json
and complete.json
are a little more challenging. The level that you use is defined by the variable DEFAULT_LEVEL
in const.py
. You can also change the level at any time by specifying it when you run the game. For example, if you type
it will play the game with the level roadsonly.json
as the DEFAULT_LEVEL
. Below is the set-up for the complete.json
to test out your game in the end.
The Starting Position
All of the levels are arranged as a grid, where each grid square is GRID_SIZE
pixels on each side. The lanes are one grid unit in height, and several grid units in width. Each exit is one grid unit wide. Cars or logs can take up multiple grid squares, depending on the specific obstacle. This grid is not explicitly visible, but it becomes obvious once you play the game for a bit.
The Invisible Grid
Once the game begins, the cars and logs move across the screen. They go in different directions, specified by the 'speed'
key in the JSON dictionary. Once they go off the screen, they wrap back around to the other side. How fast they wrap around is defined by the 'offscreen'
key in the JSON dictionary.
The frog has to safely make it to (i.e. touch) one of the exits, which is a lily pad at the final hedge. When that happens, the game puts a bluish frog on the lily pad and restarts another frog at the starting point, as show below. Exits may not be reused. The second frog cannot use the exit that currently has a frog on it.
At the First Exit
If a frog dies, the game pauses and deducts a life from the player. These lives are shown by the frog heads in the top right corner. In the picture below, the game is paused after losing the first life.
Down One Life
There are two ways for the frog to die. The frog dies if it is hit by (or even touches) a car. The frog also dies if touches the water. Why this is a frog that cannot swim is a mystery from the early days of video games. To survive on the water, the frog must hop on a log. But the logs carry the frog with it when they move, making the game a little more challenging.
Once you understand those rules, the game is simple. You win by filling up every lily pad at the hedge. You lose if you run out of lives before this happens. In the video below, we show both outcomes on the level easy2.json
.
|
Game State
One of the challenges with making an application like this is keeping track of the game state. In the description above, we can identity several distinct phases of the game:
- Before the game starts, but no level has been selected
- When the level has been loaded, but the frog and obstacles are not moving
- While the game is ongoing, and the obstacles are moving onscreen
- While the game is paused (e.g. to show a message)
- While the game is creating a new frog to replace the old one
- After the game is over
Keeping these phases straight is an important part of implementing the game. You need
this information to implement update
in Froggit
correctly. For example, whenever
the game is ongoing, the method update
should instruct the Level
object to move the
frog. However, if the game has just started, there is no Level
object yet, and the
method update
should create one.
For your convenience, we have provided you with constants for six states:
STATE_INACTIVE
, before a level has startedSTATE_LOADING
, when it is time to load the level fileSTATE_ACTIVE
, when the game is ongoing and the obstacles are movingSTATE_PAUSED
, when the game is paused to display a messageSTATE_CONTINUE
, when the player is waiting for a new frogSTATE_COMPLETE
, when the game is over
All of these constants are available in consts.py
. The current application state should be stored in a hidden attribute _state
inside Froggit
. You are free to add more states if you add extensions. However, your basic game should stick to these six states.
The rules for changing between these six states are outlined in the specification of method update
in Froggit
. You should read that in its entirety. However, we will cover these rules in the instructions below as well.
Level JSONs
All of the information about a game level is stored in a JSON file. You should remember what a JSON string is from the very first assignment. A JSON file is just a file that stores a very large JSON string. These JSON files are a lightweight way to store information as nested dictionaries, which is the standard way of sending complex data across the Internet.
The tricky part about JSONs is coverting the string (or file) into the Python dictionary. Fortunately, we have provided a tool that makes this part easy. The GameApp class includes a method called load_json
which your Froggit
class will inherit. Simply specify the name of the JSON file. As long as that file is stored in the JSON
directory, this method will load this file and convert to ta Python dictionary for you.
This means that working with level files is really all about working with a complex nested dictionary. To understand how these dictionaries work, look at the file easy2.json
. This includes all of the features that you will need to implement for the basic game. The top-level dictionary has five keys:
'version'
: A float representing the level version.
This version is only relevant if you define your own levels. It is discussed in the section on additional features.
'size'
: A list of two integers for the width and height.
The size represents the size of your game in grid squares, not pixels. You multiply these by GRID_SIZE
to get the size of the game.
'start'
: A list of two integers for an x and y position.
The start value is the starting grid square for the frog (grid squares start at 0 like any list). You multiply its two integers by GRID_SIZE
to get the starting pixel position for the frog.
'offscreen'
: An integer to support “movement wrap”.
Objects need to wrap back to the beginning once they go off screen. But you do not want to do that immediately, as the images will snap and flicker. You want them to go completely offscreen before you wrap them. This value is how far (in grid squares) that any image must be offscreen before it is time to wrap it back around. This is discussed in the activity to move the obstacles.
'lanes'
: A list of all the lanes in the level.
The list of lanes should have the same number of elements as the (grid) height of the level. The are ordered bottom up. The first lane in the list is the bottom row, and the last lane in the list is the top one.
The real nesting happens in these lanes. Each lane is its own dictionary, with up to three keys. These are as follows:
'type'
: The lane type.
The lane type is a string, and is one of 'grass
’, 'road'
, 'water'
and 'hedge'
. Unless you add new types of lanes to the game, there are no other possibilities.
'speed'
: A float indicating how fast obstacles move in this lane.
All obstacles in a lane move at the same pace. This is the number of pixels they move per second. Movement is left-to-right. If the speed is negative, then the movement is right-to-left.
'objects'
: The list of obstacles in this lane.
The exact obstacles vary by lane type. For a road, these are the cars. For a water lane, these are the logs. For the hedge, these are the exits (which technically count as obstacles).
Not all lanes have all three keys. Grass has no obstacles. The hedge has exits, but they do not move and so they have no speed. However, every lane is guaranteed to have a 'type'
, and it is guaranteed to be one of those four values.
Finally, there are the obstacles in the list for 'objects'
. All obstacles have two keys:
'type'
: A string representing the obstacle type.
For this assignment, it is the image file (minus the '.png
’ suffix) for this obstacle. So type 'log2'
corresponds to the image file 'log2.png'
'position'
: The float for the obstacle position.
Technically, the position represents a grid square, so you multiply it by GRID_SIZE
to get the position of the obstacle. However, obstacles are different from the frog in the fact that they can actually be between two grid squares (which is why this is a float).
If you understand all of these features, then you will have no problem completing this assignment.
JSON Assumptions
One of the preconditions for this assignment is that level files are properly formatted. That is, they are proper JSONs and they do not have any important keys missing. As we said above, it is okay for some keys to be missing, like 'speed'
or 'objects'
. But other keys like 'size'
and 'start'
absolutely have to be there.
It is not your responsibility to enforce that the JSON files are in the correct format. That is the responsibility of the level designer, who is typically a different person on the team than a programmer. However if you add any extensions to the game, then you will likely be designing your own level files. And if you make mistakes in your level files, you may cause your program to crash. Again, this is a problem with the level file and not the game itself.
Task 1: Setup
We have divided these instructions into three parts. Each part corresponds to a letter grade. Finishing a task completely puts you at the boundary of that letter grade and the next. For example, finishing this first task will put you at the C+/B- border.
This task involves setting up the game. That includes loading a level file and using it to draw objects on the screen. While you will be able to move the frog when you are done, nothing else will work. No obstacles will move and you cannot win or lose the game.
Because this is the part of the assignment to earn a C grade, we have made all of the instructions in this section very explicit. The hope is that everyone in the class can make it to the C+ border. As we move on to later tasks, the instructions will become a little more vague.
To give yourself enough time on this assignment, you should finish this part in the first week of the assignment, as soon as the assignment instructions are posted.
Review the Constants
The very first thing that you should do is read the file consts.py
. If you ever need a value like the size of the grid, the initial size of the game window, the frog movement speed, or so on, this is where you go. When writing code, you should always use the constants, not raw numbers (or “magic numbers,” as we call them). Magic numbers make your code hard to debug, and if you make a change (e.g. to make the grid size larger), you have no idea about all of the locations in your code that need to be changed.
With that said, you are welcome to change any of these numbers if you wish. You are also encouraged to add more constants if you think of other numeric values that you need. Anytime that you find yourself putting a number in your code, ask yourself whether or not it would make sense as a constant.
Create a Welcome Screen
We start with a simple warm-up to get you used to defining state and drawing graphics elements. When the player starts the application, they should be greeted by a welcome screen. Your initial welcome screen should start with two lines of text.
Because the welcome message is before any game has started, it belongs in the
Froggit
class, not the Level
class. You are already seeing how we separate
what goes where.
The welcomes will look something like the one above. It should tell the player the name of the game, and tell the player to “Press ‘S’ to Start”. You can change the wording here if you want. It could say something else, as long as it is clear that the user should press a key on the keyboard to continue. However, we recommend against allowing the user to press any key, since in later steps that will make it easy for the user to accidentally miss an important message (or jump immediately in front of a truck).
To create a text message, you need to create a GLabel and store in it an attribute. If you read the class invariant for Froggit
, you will see two attributes named _title
and _text
. The title attribute is the logo of the game. The text attribute is for any messages to display to the player. The text is typically smaller,
like a footnote. While we will never show the logo after the initial state STATE_INACTIVE
, we will use the text attribute for messages throughout the game.
Since the welcome message should appear as soon as you start the game, it should be created in the method start
, the first important method of the class Froggit
. When creating your message, you will want to set things like the font size and position of the text. If you are unsure of how to do this, look at the class MainApp
from the demo subcontroller.py
in the provided sample code.
As you can see from the documentation for GLabel and GObject, graphics objects have a lot of attributes to specify things such as position, size, color, font style, and so on. You should experiment with these attributes to get the welcome screen that you want.
The first thing to understand is the positioning. In Kivy, the screen origin (0,0) is at the bottom-left corner, and not the center like it was for the Turtle assignment. If you want to find the center of the window (our version centers these on the screen), the
Froggit
object has attributes width
and height
that store the size of the window. In addition, for the label object, the attributes x
and y
store the center of the label. So you can center the label horizontally by assigning x
to width/2
.
When placing label objects, you do not always want to center them. Sometimes you would like the label to be flush againt the edge of the window. For that reason we have attributes like left
, right
, top
, and bottom
. These are alternate attributes for x
and y
. They move the label in much the same way, but give you a little more
control over the positioning.
One you understand how to position the label, it is time to think about your font choice and style. If you want to look exactlty like the picture above, we have some constants to help you in const.py
. The font is ALLOY_FONT
, which is a reference to AlloyInk). The title has size ALLOY_LARGE
while the message has ALLOY_MEDIUM
. However, you are not constrained to these choices. You may chose a different font or font size if you wish, so long as it fits on the screen.
Drawing the Welcome Message
Simply adding this code to start
is not enough. If you were to run the application right now, all you would see is a blank white window. You have to tell Python what to draw. To do this, simply add the lines
to the method draw
in Froggit
. The (non-hidden) attribute view
is a reference to the window (much like the Window
object in Assignment 4).Hence this method call instructs Python to draw this text label in the window. Now run the application and check if you see your welcome message appears.
Initializing the Game State
The other thing that you have to do in the beginning is initialize the game state. The attribute _state
(included in the class specification) should start out as STATE_INACTIVE
. That way we know that the game is not ongoing, and the program should (not yet) be attempting to animate anything on the screen. In addition, the other attributes listed (particularly _level
) should be None
, since we have not done anything yet.
The _state
attribute is an important part of many of the invariants in this game. In particular, we want your new attribute for the welcome message to have the following invariant:
- If the state is
STATE_INACTIVE
, then there is a welcome message with title and text. - If the state is not
STATE_INACTIVE
, the_title
attribute is None. - If the state is
STATE_ACTIVE
, the_text
attribute is None.
Does your start()
method satisfy this invariant? Note the difference between the last two invariants. That will become important later.
Dismissing the Welcome Screen
The welcome screen should not show up forever. The player should be able to dismiss the welcome screen (and start a new game) when he or she presses the S key. To respond to keyboard events, you will need the attribute input
, which is an instance of GInput.This class has several methods for identifying what keys are currently pressed.
When using the attribute input
, remember the issues that we discussed in class. The method update(dt)
is called every 16 milliseconds. If you hold a key down, then you see a lot of key presses. You just want the first press! That means you need some way to determine whether or not the key was pressed this animation frame and not in the previous one. See the state.py
demo from the sample code for some ideas on how to do this. This may require you to add a new attribute to Froggit
.
If you detect a key press, then you should change the state STATE_INACTIVE
to STATE_LOADING
. This will load a level file and start a new game. You are not ready to actually write the code to start the game, but switching states is an important first activity.
Invariants must be satisfied at the end of every method, so you need to assign None
to both _title
and _text
now. This will require a simple change to method draw()
to keep it from crashing (you cannot draw None
). Once you have done that, run the application. Does the message disappear when you press a key?
Documenting your New Attributes
When working on the steps above, you may have needed to add new attributes beyond the ones that we have provided. Whenever you a new attribute, you must add it and its corresponding invariant as a comment after the class specification (these comments are the class invariant). Add it just after the comment stating ADD MORE ATTRIBUTES
, to make it easier for the graders (and you) to find them. We will deduct style points for instance attributes that are not listed in the class invariant
Pacing Yourself
This first part of the assignment looks relatively straightforward, but it gets you used to having to deal with controller state. Try to finish this part by Wednesday, December 2, which is right after you have had your first lab. This will give you time to familiarize yourself with the online documentation, and make sure that you understand how everything fits together.
Load the Level File
The next activity is a little more complicated than the welcome screen, but once you complete it, you can be confident that you have a good idea how everything fits together. The state STATE_LOADING
is only supposed to last one animation frame. During that frame you should do the following:
- Load the
DEFAULT_LEVEL
into a dictionary - Resize the window to match the level
- Create a new
Level
object - Assign that
Level
object to the attribute_level
- Display the contents of the
Level
object - Switch the state to
STATE_ACTIVE
You do not need to worry about the details of STATE_ACTIVE
for now. However it is
very important that you only create a new Level
object if the state is STATE_LOADING
. If you continue to create a Level
object in STATE_ACTIVE
, that will cause many problems down the line (which you will not notice until much later).
Loading a JSON File
Loading a JSON file is easier than you think. There is a method (technically it is a class method) in GameApp
called load_json
, and Froggit
inherits this method. Simply call this method on the constant DEFAULT_LEVEL
. The method will return a dictionary and
you are good to go. So from this point on, you will treat the level information like a dictionary. Use the key 'size'
to get the width and height (in grid squares) for the
level.
By default, the starting level is easy1.json
. You can go into consts.py
to change this if you wish. Alternatively, you can test other levels by typing
So you can load the 'complete.json'
level simply by typing
Resizing the Window
Different level files are different sizes. When you load a level, you want to resize
the window to match the level. The window width should be the level width times GRID_SIZE
. The window height should be one grid size higher than the level height. That is because we need an extra grid square to display the remaining lives.
To resize the window, remember that the Froggit
object has mutable attributes width
and height
. Just assign the value to those. If you do it right, the levels 'easy1.json'
and 'easy2.json'
should make the window slightly smaller, while 'complete.json'
will not resize the window at all.
Creating a Level Object
We have started the definition of the Level
class for you in levels.py
. However, it
does not do much, because we have not defined the initializer. Furthermore, this means
that the constructor does not take any arguments. However, you want the Level
constructor to take a single argument: the JSON dictionary containing all of the information about the level.
Inside of your initializer, your are going to take the 'lanes'
list from this dictionary and create the lane objects. For right now, each lane is going to be a single GTile object. A GTile
is an image that can be repeated. To draw grass, road, or water, we take a single image and repeat several times. For example,
the image below shows the difference between the image 'hedge.png'
and 2x3 tiling of that same image.
Normal Image vs 2x3 Tiling
To define a tiled image, you use the attributes x
, y
, width
, height
, and source
to specify how it looks on screen. The first four attributes are just like GLabel, while source
specifies an image file in the Images folder. As with the label, you can either assign the attributes after the object is created or assign them in the constructor using keywords. Keyword arguments work like default arguments in that you write param = value. See the online documentation for an example of how to approach this.
The source for a lane tile is just the type plus the suffix '.png'
, So the 'grass'
type uses the image 'grass.png'
, the 'road'
type uses the image 'road.png'
and so on. The height should be GRID_SIZE
, while the width should be the width of the window.
The only hard part is positioning the tiles.
The lanes are placed starting at the bottom of the screen and work towards the top. There will be one blank line at the top, because you set the height of the window to be GRID_SIZE
higher than all of the lanes. To place the lanes you might find it easer to use the attributes left
and bottom
. These are alternate attributes to x
and y
when you do not necessarily want to work with the center of an object. For example, the first lanes has left
and bottom
both 0, while the second lane has its bottom at
GRID_SIZE
.
This suggests that you can create all of the lanes using a simple loop in the initializer,
adding them to the attribute _lanes
as you go.
Drawing the Level Object
Once again, creating an GTile
object is not enough to draw it on the screen. But drawing the lanes is a bit more complicated than drawing the welcome message. The lanes are (hidden) attributes in Level
. While the code
works (and you should try it out), it is not allowed. We will take off major style points if a class ever accesses the hidden attributes of an object of another class.
This is the purpose of adding a draw
method to class Level
. The draw
method in
Froggit
should call the draw
method in Level
, and this in turn should call the
the draw
method for each lane (defined in GObject).
However, only Froggit
has access to the attribute view
, which is necessary for
drawing. The class Level
cannot directly access any attributes in Froggit
.
If a method in Level
needs an attribute from Froggit
, then Froggit
must provide
that attribute as an argument in the method call. This means that the draw
method
in Level
needs to have view
as a parameter, and the code in Froggit
should look
like this.
Notice this is very similar to how we draw GObject objects.
Testing Your Code
While the concept of level files is a lot to wrap your head around, the advantage is that they make it really easy to test your code. By running your game on several different levels, you can see if you did them correctly. Remember, to try out a different level, type
Here are what the backgrounds should look like for some of the provided levels.
Background for easy1.json
Background for easy2.json
Background for roadsonly.json
Background for complete.json
WARNING: These files are designed assuming that you have a 1080p (1920x1080 pixel) monitor for your computer or laptop. If you have a 720p monitor (1280x720 pixels), you will be able to play the first two levels, but not the last two. In addition, we have found that the default settings on the 13 inch MacBook are 1440x900 pixels making
complete.png
unplayable (though the others are fine). However, you can solve this problem by going into Settings > Display and choosing More Space.
Pacing Yourself
This is a longer activity, and we have budgeted up to two days to work on this. That means you should try to finish this part by Thursday, December 3,. However, if you take both of the days, then you should complete the next activity on the same day.
Completing this activity successfully guarantees you will pass (C-) this assignment.
Create the Frog
Next you need to create the frog. Again, this is to be stored in an attribute of class Level
. That means that you must create it in the __init__
method of Level
and modify your drawing code so that it appears. The frog should be an object of class Frog
, which is included in models.py
.
You will notice that Frog
is a subclass of GImage. Like GTile
, a
GImage
object is used to draw an image to a screen. The difference is that, if you
specify the width
and height
, the GImage
will resize the image to fit entirely in
that size; it will not tile the image. But otherwise, creating and drawing a GImage
is exactly the same as creating and drawing a GTile
object.
Initializing the Frog
Technically, we do not have to define an initializer for the frog. We inherit the one
from GImage
already. But we do not want to use that initializer. The image file
for the frog will never change; it is defined by the constant FROG_IMAGE
in const.py
.
And the width and height will be the default values. The only thing we want to specify
is the frog position on the grid.
So that means we want to define a custom initializer for the Frog
class. This initializer should have parameters for the start position (both x
and y
) and that is it. This initializer then calls super()
to use the initializer for GImage
, passing FROG_IMAGE
as the source file.
When positioning the frog, the center of the frog (defined by the attributes x
and y
should be the center of the grid square. Remember to multiply the grid positions by GRID_SIZE
before passing them to the initializer for GImage
. The GImage
class works with pixels, not grid squares. But also remember that a grid square (col,row)
is not
centered at (col*GRID_SIZE,row*GRID_SIZE)
. That would be the bottom left corner of the grid square.
There is one more thing you need to do when you create the frog. By default, the frog image is facing to the south. You need to set the angle
attribute of the frog to FROG_NORTH
to make sure the frog is facing in the correct direction.
One you have created the custom initializer for Frog
, the initializer in Level
just needs to call the constructor for Frog
and assign it to the _frog
attribute.
Drawing the Frog
Remember to draw the frog object in the method draw
of class Level
. You draw the frog in the same way you drew the lane tiles. You do not need to define a draw
method in class Frog
as it is inherited from GImage
. However it is important that you draw the frog last. Objects are draw bottom to top and you want the frog to be on top of everything.
Testing Your Code
Again, you can test your code by running each of the provided level files. For all of the provided levels, the frog should be centered in the bottom lane. For example, the level easy1.json
should look like this:
Frog for easy1.json
That is because 'easy1.json'
has a start position of [5,0]
. If instead the start position where [0,5]
, it would look like this:
Frog for Swapped easy1.json
Pacing Yourself
This is super short activity. You should be able to complete this soon after you finish drawing the lanes, which is by Thursday, December 3. Finishing this on time will give you more time for the next activity.
Completing this activity successfully will guarantee you a low C on this assignment.
Add the Obstacles
Now we come to the first tricky point of the assignment. We want you to create all of the obstacles in the level. This includes cars, trucks, logs, and even exits. But do this you are going to need to replace some of the code that you have already written. This is something that we are going to be doing throughout the assignment. You will start with simple code first to make sure everything is working. But then you will go back and replace it with more complex code.
The code you need to replace are the GTile
objects you created in the initializer for Level
. That is because we are going to replace those objects with a composite object. If you do not know what composite objects are, these are discussed in lab 22, soon after you get back. You may go over this lab with your lab instructor if you need help.
For those who wish to skip the lab, composite object is when we combine one or more GImage
objects (or in this case a GTile
object and some GImage
objects). The composite object is constructed just like a subcontroller. It has an initializer that creates all of the objects inside of the composite, and its own draw
method to draw the individual objects as well.
Initializing a Lane
The Lane
object will serve as our composite object. You will notice that there are classes for Grass
, Road
, Water
and Hedge
. However, you can safely ignore all of those for now. We will only need those classes later when we work on the basic game. Right now, the parent class Lane
will do all of our work for us.
Each Lane
object will replace a single GTile
. That means that each Lane
object should have an attribute _tile
for the GTile
that it contains. Ideally, you
can just copy the code from Level
into the initializer for Lane
. However, that means
you need to add some new parameters to the initializer for the Lane
. In addition to having access to the JSON dictionary, the Lane
initializer needs to know which lane it is, so it can pick the right values out of the nested dictionary. If you created the tiles with a for-loop, you essentially need to pass the loop variable as a parameter to the lane object.
Inside of the Lane
initializer, you should also initialize all of the obstacles, adding them to the list attribute _objs
. You do this with a for-loop just like you did the tiles in a previous activity. However, this time all of the obstacles are represented by a GImage, not a GTile
. The source file for each obstacle is the same as the type plus the suffix '.png'
. So obstacle 'log2'
corresponds to the image file 'log2.png'
, and so on.
You do not need to specify the width
and height
of these images. The default value is fine. However, you do need to specify their position. If you look at the JSON files, you will notice that each obstacle has a 'position'
value. This is the grid square for the center of the obstacle. So the y
attribute of the obstacle should be the same as the y
attribute for the tile (the centers should match), but the x
attribute should be determined by the 'position'
.
When using the 'position'
, note that obstacles can fill up more than one grid square. For example, 'log2'
is two grid squares in width. So to put this obstacle in grid squares 1 and 2, its center will be at grid 1.5 (so at the border of grid squares 1 and 2). This is shown below.
Positioning an Obstacle
This means that you cannot get the x
pixel by multiplying the 'position'
by the GRID_SIZE
. But it is close, and you should be able to figure out the correct answer now.
Orienting the Obstacles
In addition to placing the obstacles, you need to orient them correctly. By default, all obstacles are facing the right edge of the window, because the assumption is that they will move left-to-right. However, that is not always true. Some objects will move right-to-left, and they will need to face in the opposite direction.
How do we know which is which? It depends on the 'speed'
value of the lane. If the speed is positive (or 0), then the obstacles face the normal direction. But if the speed is negative, the obstacles face the opposite direction.
Turning obstacles around is easy. We just rotate them 180 degrees by senting the angle
attribute in a GImage
object to 180.
Constructing a Lane
Once you have the initializer defined for the lane, now it is time to replace the code in Level
. In the initializer for level, you want to replace all of the GTile
objects with Lane
objects. We also recommend that you use the right subclass for each type. So a 'grass'
lane should use a Grass
object and so on. We know you did not define any methods in these classes, but they inherit the initializer from Lane
. And breaking your lanes into specific classes now will make parts of Task 2 much easier.
Drawing a Lane
Drawing a Lane
object is similar to drawing Level
object. You need to add a draw
method and it needs to take the view
as a parameter. You use this to draw the tile first (since it is at the bottom) and then all of the obstacles in order.
If you do this correctly, you should not even need to change the code in the draw
method for Level
. That code should still work. Instead of calling the draw
method of GTile
, it is calling the method for Lane
.
Testing Your Code
Once again, you should try out your code on several different level files to make sure that it is correct. Here are what the obstacles should look like (with the frog included) for some of the provided levels.
Obstacles for easy1.json
Obstacles for easy2.json
Obstacles for roadsonly.json
Obstacles for complete.json
Pacing Yourself
This is the hardest activity in the setup portion. Give yourself two days to work on this activity, finishing it by Saturday, December 5. Once you get this down, the rest of the setup will be easier.
Completing this activity successfully will guarantee you a C on this assignment.
Display the Lives
The challenge of Frogger is that you have a limited number of lives before the game is over. We want to display those lives to the player. That is the purpose of the blank lane at the top of the screen. When you are done with this activity, the top bar should looke like this:
The Lives Counter
The lives are represented by GImage
objects using the source file FROG_HEAD
. However, the FROG_HEAD
image is very large. You will need to scale it to GRID_SIZE
width and height to get it to fit. You can do that by setting the width
and height
attributes of the GImage
object.
You should keep these GImage
objects in a list. That makes drawing them the same as drawing the lanes. And when you want to lose a life, you will just remove one of the images from the list.
Finally, you should also include a GLabel indicating that these frog heads represent the lives. If you use ALLOY_FONT
, we recommend using ALLOY_SMALL
for
the font size. In addition, make the right edge of the label equal to the left edge of the first head.
Organizing Your Code
All of these attributes should be created in the initializer for Level
. However, you might notice that your initializer is starting to get a little long. Remember the 30 line rule. If any method gets longer than 30 lines, you might need to break it up into helpers. This may now be the time to do that.
If you make helpers, remember to write specifications for all of your helpers. You should also hide them. If another class does not need access to them, they should be hidden.
Finally, remember to update the draw
method to draw the label and the frog heads. If you think that your draw
method should be broken up into helpers, you should do that as well.
Pacing Yourself
This is a short activity, though we have give you a day on it. Try to finish this by Sunday, December 6. That should give you a breather if you got behind on the previous activity.
Completing this activity successfully will guarantee you a high C on this assignment.
Move the Frog
The last activity in the setup is to get the frog moving. We will not worry about the obstacles yet. We just want to concentrate on the frog. To move the frog, you will need to take into account the player’s key presses. The frog only moves when the player presses (or holds down) a key to make it move. By default, we assume that the player will use the arrow keys to move the frog. However, if you prefer WASD controls or some other control scheme, that is okay.
Updating the Frog
To see how to control the frog, you should look at the arrow.py
demo from the provided sample code. This example shows how to check if the arrow keys are pressed, and how to use that to animate a shape.
To perform the actual movement, you will need to add an update
method to Level
. This will complete the methods turning it into a proper subcontroller, like subcontroller.py
in the sample code. Note that moving the frog requires access to the input
attribute. This is an attribute of Froggit
, not Level
. The Level
class will need some way to access this.
The frog should move between grid squares. That means if the player is pressing the up or down arrow, the frog will move GRID_SIZE
up or down. If the player is pressing left or right, the frog will move GRID_SIZE
left or right. As a result it will look like the frog is slightly “teleporting” about the screen, as shown below:
|
Note that the frog also turns when it moves. Use the constants FROG_NORTH
, FROG_EAST
, FROG_SOUTH
and FROG_WEST
to point your frog in the correct direction.
If the player is holding down two keys at the same time, the frog should not move diagonally. One of the keys should win (or they should cancel out). Which one you do is up to you. We do not care.
Timing the Movements
If the player holds down an arrow key, we actually want the frog to keep moving. So in a sense, moving the frog will easier arrow.py
. We do not care whether the player did or did not press a key the previous frame. As long as the player holds down the arrow keys, the frog will move.
However, part of the challenge of the Frogger is that the frog moves slowly. If the frog moves a full GRID_SIZE
every animation frame, the frog will rocket off the screen immediately.
The solution to this it to introduce a cooldown. Once the frog moves, you must wait FROG_SPEED
seconds to move the frog again. How do you keep track of time? That is the point of the dt
attribute in the update
method of Froggit
. That measures how many seconds have passed. So, when the frog moves, set a cooldown attribute to FROG_SPEED
. Afterwards, subtract dt
from the cooldown. Once this reaches 0 or less, the frog can move again.
Restricting Movement
You should ensure that the frog stays completely on the board even if the player continues to hold down a key. If you do not do this, the frog is going to be completely lost once it goes off screen. How do you do this? Look at where the frog will end up before you move it. If it is going to go offscreen, then do not move it. However if that movement required the frog to turn, you should still turn the frog in place, even if it cannot move forward.
When we say “offscreen”, we mean the top bar with the lives counter in it as well. As you can see in the video above, our frog stops when it reaches the final hedge. However, the frog can still move freely about the hedge. That will not be the case in the game; the hedge will block the frog unless the frog is stepping on a lily pad. But we will not worry about that until a later activity.
Testing Your Code
Testing this part of the code is easy. Run the game, and try to move your frog. Does the frog move? Does it stay on the screen? Then you have succeeded.
This activity is actually pretty straightforward, provide you understand the
arrows.py
demo in the provided sample code. Most people
can complete this part of the assignment in less than two hours. However, some people will find that they cannot get the frog to move, even after properly adapting the code from arrows.py
.
One of the key things is to make sure that your cooldown is correct. Keep one of the arrow keys held down. The frog should not move smoothly, but instead should only move every FROG_SPEED
seconds. It is possible to adjust this speed from the command line if you wish. Try running
The last argument is a number which replaces FROG_SPEED
. In this case, the frog would move once a second, which is much slower than the default 4 times a second.
Debugging Your Code
If your frog refuses to move, add a watch statement to the draw
method of Level
.
Print out the id (the folder name) of the frog. Run the game and look for this print
statement. What you see will (hopefully) identify your bug.
The print statement never appears: In this case, you have forgot to call the
update method for Level
from Froggit
. Make sure Froggit
has this line of code
in its update
:
The frog id keeps changing: In this case, you are accidentally reseting the Frog
object each frame. Go back and look at the instructions for loading the level file to see how to stop this.
The frog id is constant: If this is true and the frog still will not move, we have no idea what your problem is. It is something unique. Please see a consultant immediately. They will not look at your code, but they will help you debug it better. In our experience, when students claim the frog id is not changing, they are wrong.
Pacing Yourself
We highly recommend that you complete this by Tuesday, December 8, ending the first week of work. This will get you to the first grade border.
Completing this activity successfully will guarantee you a C+ on this assignment.
Task 2: The Basic Game
When you are done with this task, you will have a complete and playable game. It will not be fancy, but it will be playable. That will be enough to put you at the B+/A- border.
Because we expect more of B students than we do of C students, these instructions will not be as detailed as they were for the previous task. At this point, we will have assumed that you have read all the documentation and are familiar with how the game2d objects work.
Detect the Exits
In the previous activity, the frog could walk all along the hedge. This is not what we want. The frog should be able to enter an exit (one of the lily pads). Any other part of the hedge will block the frog from moving, just as if it were going offscreen.
To do this, we need to be able to do two things
- Detect if the frog is trying to walk into a hedge
- Detect if the frog is trying to walk into an exit (or an opening)
To do this, we will leverage two different methods inherited from GObject: collides
and contains
. They are both very similar, but they do have some important differences.
Detecting Movement into a Hedge
The collides
method is used to determine when two game objects (like say a frog and a background tile) are overlapping. To determine if the frog is in the hedge, simply call the method collides
from either the frog or the hedge tile (it does not matter which). If the result is True
, then the movement is (potentially) blocked. Otherwise, the frog is not trying to move into the hedge and there is no problem.
Because the tile object is a (hidden) attribute of Lane
and the frog object is a (hidden) attribute of Level
, we need some way to compare these two together. This will require additional methods in the Lane
class. You either need a getter to access the tile, or a collision method that allows the Level
to pass the frog as a parameter. We do not care which one you chose.
When you write this method, be prepared for there to be multiple hedges. We have added an image 'open.png'
which represents an opening in the hedge that is not a exit. This allows the player to walk through the hedge without reaching an exit. This is for advanced levels like 'multihedge.json'
. While supporting this file is not necessary for this activity, we will be including it when we grade this task at the end.
Detecting Movement into a Exit
Movement into a hedge is only allowed if it is into an exit (or an opening). As the “obstacles” of a hedge are all either exits or openings, this means that we just have to check whether the frog collides with an obstacle in the hedge. If so, the frog can move into the hedge.
However, this time we do not want you to use the collides
method. For reasons that will become clear when you give the frog a log ride, we want to ensure that most of the frog overlaps the exit. So that is the purpose of the contains
method. The contains
method checks if a point (represented as a tuple (x,y)
) is located inside of a game object. We only want to allow movement into the hedge if the center of the frog will be contained in the exit.
To do this, you loop though the exits and opening (the obstacles) and see if the center of the frog is contained in one of them. If so, the movement is allowed. Adding this functionality will require more methods. But this time we do not want you to add these methods to Lane
. This is functionality that is specific to a hedge. So these methods (even if they are just getters to access all of the exits) belong in the Hedge
class. This is where we are going to start to use our subclasses, as each of the lanes will behave differently from each other.
ADDENDUM: It has been recently pointed out that in 'multihedge.json'
, you should not permit the frog to enter an exit from the north.
While that is not a requirement of this task, we are adding it to Task 3
Testing Your Code
Once again, test your code by trying out some of the levels. You should be blocked trying to walk into a hedge unless you are walking into an exit or opening.
You should also try out the level 'multihedge.json'
and see if you can squeeze through the first hedge to make it to the second hedge. It is okay if you walk through exits like they were an opening. You will solve that problem in a later activity.
Pacing Yourself
You should be able to finish this activity in a single day. If you are on schedule, this would be by Wednesday, December 9. This activity is not that much more difficult than the normal restrictions on frog movement. The only challenge is spreading out your code among the individual classes.
Completing this activity successfully will guarantee you a B- on this assignment.
Move the Obstacles
Up until now, the obstacles have just been sitting there. It is time to get them moving. Movement is determined by the 'speed'
value for the lane. This is the number of pixels
that each obstacle should move per second. All obstacles in a lane move at the same rate. If the speed is positive, they move left-to-right (so you add the speed to the position). If the speed is negative, they move right-to-left.
Implementing Basic Movement
To implement the basic movement, you will need to add an update method to Lane
, also turning it into a proper subcontroller. This update
method should move all of the obstacles in the lane, and it should be called from the update
method in Level
.
Technically, you should only move obstacles in road and water lanes. However, hedges never have a speed (the exits do not move unless you are making than an extension). And the grass has no obstacles (again, unless you are adding an extension like snakes). So that is why it is safe to add this code to the Lane
class. Keep in mind that this movement will require some additional attributes (such as the lane speed).
When you move the obstacles, remember that the speed is the number of pixels per second. To get the number of pixels to move any given animation frame, you should multiply that value by dt
, the time since the last animation frame.
Implementing Wrap Around
Eventually the obstacles are going to go offscreen. When that happens, we want to wrap them back around to the other side. So objects going offscreen to the left should come back around on the right, and objects going offscreen to the right should come back around to the left.
Naively, the way to do this is to look at the x
position of the obstacle. If it is less than 0, set it the width of the window. If it is greater than the width, set it to 0. However this solution causes a major problem: “snapping”. The x
attribute is the center of the object. So we will see have of the object immediately disappear on one side, and the other half teleport in on the other side. We want the movement to be smooth.
We could try to solve the snapping problem by using attributes left
and right
instead of x
. If the left
attribute is fully off the right of the screen, we set the right
attribute to 0. And similarly if the right
attribute is less than 0, we set the right
attribute to the width. This gets rid of snapping and the movement looks smooth. However, it completely screws up spacing. Different obstacles have different lengths (look at level 'easy2.py'
). Over time, this will alter the spaces in-between the obstacles changing the layout of the level. In some cases, it could cause the obstacles to start to overlap.
We want a solution that eliminates snapping but also preserves spacing. The solution is to have an offscreen buffer. This is a little bit of a distance that all objects are allowed to go offscreen. If the buffer is 4, then obstacles can have an x
as low as -4*GRID_SIZE
and as high as width+4*GRID_SIZE
. When the x
attribute crosses this threshold, then the object should wrap around to the other offscreen edge. Hence an object will traverse two offscreen buffers before it comes back on screen.
The buffer should be large enough to support the largest obstacle in the level. That is why it is defined inside of the level file. It is not your responsibility to ensure that the buffer is large enough. That is the responsibility of the level designer.
An Offscreen Buffer of Size 2
As the illustration above shows, the the object should not snap to -buffer*GRID_SIZE
or width+buffer*GRID_SIZE
when it wraps around. If you do this, you will still start to lose spacing over time because the obstacles do not align exactly with the grid squares. Instead, when the center of the obstacle (marked by the blue dot) crosses the buffer, we determine the distance d
that it went over. When we teleport the image to the other side, we shift it forward by the amount d
.
This all sounds harder than it is. You can accomplish all of this with a simple if-statement.
Testing Your Code
Once again, you can test your code on any level file. However, we highly recommend that you test it on the level 'complete.json'
. If it is working properly, then it should look like the video below.
|
Let the game run for a long time. If you are losing spacing, then you will start to see it in this level, because there are so many obstacles.
Pacing Yourself
The code is getting harder, but we still think you can finish it in a day. If you have made it this far then you understand more and more of how the code works, and can write code faster. Therefore, you should try to finish this activity by Thursday, December 10.
Completing this activity successfully will guarantee you a low B on this assignment.
Squash the Frog
Now we have everything moving, but the game is not very interesting. That is because nothing really interacts with anything. Cars do not squash the frog. Logs do not take the frog for a ride. Only hedges really do anything right now, but it does not feel like a game.
In this activity, you will introduce the first challenge in your game: cars. If a frog collides with a car, then the frog should die. Before we talk about how to kill the frog, we first need to think about how to detect a frog death. In many ways this is exactly the same problem as detecting the exits. We need to add special methods to the Road
class that allow us to determine if a frog has collided with a car. Look at how you solved that problem and come up with something similar here.
Killing the Frog
What does it mean to kill the frog? Two things. First of all, the frog should disappear from the screen. There are many ways to do that. One is simply to set the _frog
attribute to None, and make sure that the Level
does not draw the frog when it is None (just like the Froggit
class handles the welcome message). Another approach is to add a visible attribute to the frog. When this attribute is False
, the frog is not drawn. This would require you to override the draw
method in Frog
, instead of simply inheriting the one from GImage
. Choose whichever approach you are most comfortable with.
The second thing about killing the frog is that you should pause the game immediately. That is, the Froggit
object should switch its state to STATE_PAUSED
(remember states?)
and it should no longer update the Level
object until it is active again. That means you will need some way for the Froggit
app to know whether or not to pause the game. You could add a getter to Level
for Froggit
to get this information. Or you could turn the update
method in Level
from a procedure into a fruitful method, and use the return value to indicate whether or not to pause. Again, we do not care which approach you take.
Pausing the Game
Pausing the game is easy. Simply do not call update
in Level
. Again, you can see exactly this idea if you examine subcontroller.py
in the sample code.
However, there is one more thing to do. We want you to display a message that the game is paused to the player, just like we have done below.
Paused Game
Notice that we are displaying a message to the player. We can do this with _text
attribute in Froggit
, provided that we draw it last. There is not need to add a label to Level
for this, especially since Froggit
is in charge of pausing.
In the example above we made a few stylistic choices that you might want to adopt, but you are not required to do so. First of all, we made the message neatly fit in a center lane of the level (which is easy if you use ALLOY_SMALL
). Furthermore, we set the background of the GLabel to a solid cover, covering up the obstacles underneath. We did all of this to make the label more readable. In the end, this is all that matters. You can do whatever you want with your paused message so long as it is still readable.
Some of the more observant of you might realize that the message can get really tight as the levels get small. The level 'easy1.json'
is pretty narrow, but what if we have a level that is just three grid squares wide? Actually, this is a precondition that we are going to add to the game. No level will be less than 10 grid squares wide and less than 8 grid squares high. So as long as your message fits in that space, you are fine.
Resuming the Game
The player should be able to start playing again by pressing a key. Our solution uses the ‘C’ key, but it can be anything you want so long as the message you displayed makes it clear. Once again, we recommend that you do not allow the player to press any key, as a player holding down the up arrow will immediately move forward and step in front of a truck.
This step is almost the same as STATE_INACTIVE
. You detect a key press and then switch the state. But this time you do not want to switch to STATE_LOADING
; we do not want to load a new level. Instead, go to STATE_CONTINUE
.
The purpose of STATE_CONTINUE
is to reset the level. That means making the frog visible again and putting the frog back in its start location. If you forgot where the start location was, you might need an attribute somewhere to remember it. Like STATE_LOADING
, this is a state that lasts exactly one animation frame. When it is done, it should immediately switch to STATE_ACTIVE
and the game should continue as normal.
Testing Your Code
It is time to start killing frogs! Load up a level and step in front of cars. You do not lose any lives yet, so it is harmless. Make sure that you die when you touch a car and live when you avoid them.
Keep in mind that images have a little bit transparency around the edges, but the collision code in GObject
simply compares the size of the images (which are rectangles). So it is possible that your frog just barely escapes the car but the game still registers it as a touch. This is a problem you will solve when you tighten the hitboxes.
Pacing Yourself
You should try to finish this by Friday, December 11. A lot what you need to do here is similar to what you had to do for the exits. The only new and tricky part is pausing and unpausing the game.
Completing this activity successfully will guarantee you a low-to-mid B on this assignment.
Lose the Game
Now that you can kill your frog, you can lose the game. Every time that the frog dies, you should remove one of the frog heads from the top of the screen. Since this is just a list of GImage
objects, this should be easy. The problem is what happens when there are no lives left.
Normally when the frog dies, Froggit
is supposed to switch to STATE_PAUSED
. However if Froggit
detects that there are no lives left (this will probably require a new method in Level
) then it should instead switch to STATE_COMPLETE
and display a very different message to the player
Game Lost
For the basic game STATE_COMPLETE
is the final state. There is no other state to switch to. The player is expected to close the window and relaunch the game if they want to play again. If you want to allow the player to play again, that is an extension that you can add.
Testing Your Code
Once again, it is time to kill your frog. But when you run out of lives the game should end. Since you cannot win yet, the only thing you can do is die. This says something about the inevitability of our mortality.
Pacing Yourself
This activity should be really quick. If you know how to pause the game, tracking lives and marking a loss is not that much different. However, we budgeting a full day for this, up to Saturday, December 12. That is to allow you to catch up you got behind on previous activities.
Completing this activity successfully will guarantee you a mid B on this assignment.
Reach the Exits
You are already detecting the exits in the game. But that is not the same as reaching safety. When a frog touches an exit (as determined by the contains
method), you need to put the frog safely on the lily pad. That means putting the image FROG_SAFE
on top of the lily pad. You should also make the normal frog invisible and pause the game, just like when the frog got hit by a car. But this time, the player should not lose a life.
As when the frog dies, the player will continue once they press the correct key. The frog will be reset to the starting position to begin again.
Blocking the Exits
For the most part, this activity is not that much different that getting hit by car. It is just that the frog does not lose a life. However, there is one important additional detail. The frog can only use an exit once. Once a lily pad has been taken, it cannot be used again in the future. If a frog tries to step into an occupied lily pad, the frog should be blocked, just like the frog is blocked by a normal hedge square.
This is going to require a lot more attributes in the Hedge
class. In fact, you are going to want to add some attributes just do draw the FROG_SAFE
images on top of the lily pads. But you also might want something else (like a list or dictionary) to keep track of which exits are occupied and which are not.
More attributes means you need to define an initializer in the Hedge
class. It should add the new attributes, but use super()
to make sure that you inherit all of the original attributes from Lane
. You will also need to modify the methods you wrote when you first detected the exits to account for these changes. Of all the Lane
subclasses, Hedge
will prove to be one of the most complex.
Testing Your Code
You are almost ready to play a complete game. Try out the default game 'easy1.json'
. See if you can fill up all of the lily pads without losing too many lives. If you want more of a challenge, try ‘roadsonly.json'
instead.
Pacing Yourself
You should try to finish this by Sunday, December 13, which is one day ahead of the last activity. We starting to move very fast now. But students who reach this far should have been able to finish the other parts a little early and hopefully have some time to spare in their schedule.
Completing this activity successfully will guarantee you a mid-to-high B on this assignment.
Win the Game
Winning the game is lot like losing the game. When you run out of exits, the game pauses,
Froggit
switches to STATE_COMPLETE
and displays a congratulatory message like the one shown below.
Game Won
The trick is determining whether the game is won or not. Class Hedge
should give you some method to determine if all exits are occupied. But remember that it is possible for there to be more than one hedge, so you need to check this against all hedges.
Testing Your Code
You are now able to successfully play any level that does not include water. Try out 'easy1.json
’ first, as that is very easy level to win. Try out 'roadsonly.json'
for more of a challenge.
Finally, try out 'multihedge.json'
to make sure that you work correctly when you have more than one hedge. Because occupied lily pads prevent the frog from going through the hedge, this level should work correctly so long as you did not think that the open square was an exit.
Pacing Yourself
This activity is very short and should be completed by Sunday, December 13, the same day as the last activity.
Completing this activity successfully will guarantee you a high B on this assignment.
Splash the Frog
The game is complete as far as roads are concerned. But the standard game of frogger also has water. To try out the water levels, we suggest that you switch DEFAULT_LEVEL
to easy2.json
. You will be able to win and lose this level, but the water portions will be boring. The frog will walk on water and not interact with the logs.
Taking a Log Ride
The frog should go for a ride if it mostly collides with a frog. You should use the same rule that you use for exits. That is, use the contains
method on a log to check if the center of the frog is inside the log. When that happens, we say that the frog is “on top of the log”.
As long as the frog is on top of the log, the log should push it. That means however much much the log changes its x
attribute each frame, the frog x
attribute should change the same amount. When the frog moves again, this will stop (unless jumps on top of a log).
Right now, movement is discrete. The frog jumps between lanes, but is always either fully inside of or fully outside of a lane. So that makes the code somewhat simple. Check which lane the frog is in. If it is a water lane, find the log underneath the frog (if any). Then move the frog by that amount.
Notice that this is going to screw up the grid movement. As a frog gets pushed, it becomes like an obstacle and is no longer guaranteed to be in a grid square. But because we are using the contains
method to reach the exit, everything will be fine.
Drowning the Frog
The Frogger frog cannot swim. Maybe its mother never taught it how. What this means is that if the frog enters a water lane but it is not on top of any log, it dies immediately. You should try it exactly as if the frog were hit by a car. The game pauses and the player loses a life. If this is the last life, the player loses the game.
In addition, the frog cannot go offscreen. While you previously wrote code to prevent this from happening, that was only for the arrow keys. There is nothing to keep a log from dragging a frog offscreen. If the center of the frog (defined by the x
and y
attributes) ever goes off screen, the frog dies as well.
Testing Your Code
You are now able to successfully play any level in the game, including 'complete.json'
. Try them all out. Feel free to make some new levels. Enjoy your accomplishment.
Pacing Yourself
If you want an A on this assignment, we suggest that you finish by Tuesday, December 15, just after the second week. This will give you enough time to complete the final task and add some extensions if you wish.
Make sure that all of Task 2 is complete and tested before moving on. If you have any bugs now, they can make the next part much harder.
Completing this activity successfully will guarantee you a B+ on this assignment.
Task 3: Animation and Polish
The game is now playable, but it feels very rough. The frog teleports across the screen rather than moving smoothly. And if the frog dies, it just disappears. Most of the time we have no idea even why we died.
To improve the game we need to add some juice, or what game academics call game feel. These are features that do not fundamentally alter how the game plays, but make the game feel more professional or satisfying.
That is also the point of the extensions. If you have reached this far in the assignment, you are allowed to start adding extensions to the game. We will ignore any extensions for any submission that does not complete the basic game. But from this point on you can implement extensions for minor extra credit, including compensating for occasional bugs in the first two tasks.
Tighten the Hitboxes
Before you can start working on animation, we need to talk about hitboxes. A hitbox is the part of an image that can collide with a game object. This is important because images often have transparencies around them, and we do not want the transparent parts to count as part of the collision.
This is particularly true when we start to animate the frog. Because the frog can stretch out, this will mean that there is a huge difference between the hitbox and the image size. Below we show the difference between the images 'frog1.png'
which you have used so far and 'frog2.png'
which you will use to animate the frog. The image for 'frog2.png'
has a huge amount of blank space because we need to give the frog space to stretch out.
Hitbox Comparisons for FROG_IMAGE and FROG_SPRITE
Handling hitboxes can be really hard. Fortunately, you do not have to worry about this. The game2d
package does this automatically for you. All you have to do is tell it what the hitboxes are, and the methods collides
and contains
do the rest.
Defining the Hitboxes
All hitbox information is in a file called 'objects.json'
. This is the lone file in the JSON directory that is not a level file. Look at this file and you will see that it has the image size and hitboxes for every single image file provided. These files are divided into 'images'
and 'sprites'
. You will work with sprites when you animate the frog.
You have to understand what the hitbox information means. Let us look at 'car1.png'
as an example. Its hitbox is [1,5,1,5]. You read these values as adjustments for the left, top, right, and bottom edges, in that order. So the hitbox starts 1 pixel to the right of the left edge, 5 pixels down from the top edge, 1 pixel left of the right edge, and 5 pixels above the bottom edge. This is shown in the image below.
The Hitbox for Image 'car1.png'
However, you do not need to understand any of this (unless you want to add your own images). All you need to do is assign it. And if you look at the documentation for GObject, you will notice that that all have an attribute hitbox
that has exactly this same format. And this attribute is inherited by GImage
.
Assigning the Hitboxes
So if all you have to do is assign the hitbox
attribute, what is the challenge? Well, you have to get this information from the file 'objects.json'
to the individual GImage
objects. That means you have to load this file at the same time you load the level file. And you have to change all of your initialzers to include an additional parameter for the hitbox dictionary.
But fortunately, once you do that, it is pretty straight forward. To get the hitbox for an image, just use the ‘type'
as a key.
ADDENDUM: The following requirement was added on December 4.
Fixing the Hedges
When you write code to Detect the Exits, it is possible that you
allowed a frog to enter a hedge edge from the north. For example, in ‘multihedge.json
’,
you squeeze through the opening in the middle, and then try to move back down through
the hedge into an exit.
We do not want this behavior. So while you are fixing collisions, you should also fix your code for detecting the exits to prevent this possibility.
Testing Your Code
If you look at the JSON directory, you will notice a file called 'bigones.json'
. This level includes the obstacles 'biglog'
and ‘bigcar'
. If you do not implement the hitboxes correctly, then the second grey car will kill your frog even if the frog is sitting safely on the grass. Furthermore, the frog will be able to ride along on the second log even if it is not on top of it.
Fortunately, these two obstacles are included in 'objects.json'
. If you can play this level normally, then your hitboxes are working correctly.
Pacing Yourself
This is another straight-forward activity. You just have to modify all your initializers. Again, you should be able to do this in a day, finishing by Wednesday, December 16. But make sure you make a back-up of your code before making big changes like this. We would hate for you mess up your progress and not know how to get it back.
Completing this activity successfully will guarantee you an A- on this assignment.
Animate the Frog
This is the big one. If you can get past this part, the last two parts of the assignment are easy. It is time to animate the frog so that it moves smoothly between lanes. This is where you are going to use the coroutines that we talked about in Lesson 29. You will also get some experience with these on lab 23 to aid you on this step.
Up until now, you have not done that much with the Frog
class. Maybe you added a visible
attribute. Maybe you added some attributes to the frog to remember the start position. But now you are going to see why we created the Frog
class, and did not just leave it a GImage
object like we did for the other obstacles.
Creating the Coroutine
The first thing we want to do is to slide the frog between lanes. The way we are going to do this very close to what we do in the coroutine _animate_slide
in the file coroutine1.py
in the provided sample code. You should use FROG_SPEED
to define how long the coroutine runs. You will find that you no longer need the cooldown attribute when you do this. You detect player input if the frog is not animating and ignore it if it is.
The difference between the frog and coroutine1.py
is that you need to be prepared to move in different directions. While the frog slides smoothly, it turns immediately as soon as the coroutine starts. When you are done with this step, your frog movement should look like this.
|
We recommend that you make the coroutine a method in the Frog
class. That way the coroutine has access to all of the frog attributes and you do not have to pass them as parameters when the coroutine starts.
There is one other important thing to keep in mind, and that is how this sliding movement affects collisions. You do not have to do anything different above cars. But logs are going to be a problem. You die if you touch water without being on a log. But if the frog is sliding, there is going to be a period in the jump when it is over the water but not yet on the log.
You need to change the collision code for the Water
class. The frog only dies if it is in the water and animation has stopped. If the animation is still active, you can assume that the frog is safely in mid-air. Similarly, the log should only push the frog if the animation has stopped. If the frog is still in the air, the log has no affect.
Finally, if the frog dies in the middle of an animation, you need to clear the animator (set it to None). Otherwise, the frog will resume that animation (probably half way across the board) when the player continues.
Switching to a Sprite
The frog now moves smoothly. But we want to see it actually jump. We are not going to be able to do that with a still image. We are going to need a sprite. A sprite is a collection of images that you flip through to create an illusion of movement. You will get experience with one of these in lab 23.
Read the documentation for GSprite to see how it works. The sprite image is defined by the constant FROG_SPRITE
(though it is missing the '.png'
). If you use this as a key in the 'objects.json'
file, you will get the image file and the correct format (the number of rows and columns).
You will also notice that GSprite
objects have a hitboxes
attribute. This is different from the hitbox
attribute (which they have as well). This attribute defines the hitbox for every frame. If you look at the frog sprite, you will see that they are wildly different. Once again, this is all handled for you automatically. If you set the frame, then the game will set the correct hitbox. You just need to assign the hitboxes
attribute at the beginning.
Animating the Sprite
While you are smoothly sliding the frog, you want to flip through all of the sprite images as well. You change the sprite image by setting the frame
attribute. The frog is at rest at frame 0. You stretch out the frog by increasing the frames until you get to frame 4 (this final frame). Then you contract by decreasing frames in the reverse order until you get to frame 0.
If you look at coroutine2.py
in the provided sample code you will notice that we do almost exactly the same thing when we turn the ship. If you can figure out how that code works, then you can animate the frog here.
Testing Your Code
As always, you should play your game. When you are done, your movement should look something like this
|
Pay close attention to how your frog works with the water. You do not want your frog dying in mid animation for no reason. Also pay close attention to what happens when you continue after a death or a successful exit. Is the frog back in its normal resting position in the correct location? If not, you did not reset the animator correctly.
Pacing Yourself
We are budgeting two-days for this part, with a suggested finish date of Friday, December 18. Honestly, we are not expecting a lot of students to get this one. If you get it, congratulations. And if a lot of people in the class gets it, even better. Everyone who gets this far deserves an A (an exam-level A).
Completing this activity successfully will guarantee you a low A on this assignment.
Animate the Death
It is still the case that if your frog dies it just disappears. That is not satisfactory. We want to see what killed our frog. That is going to require a dying animation. For this, you will need another sprite, given by DEATH_SPRITE
. We recommend that you manage this sprite in the Frog
class (so now the Frog
class because a composite object). But you are free to manage it with a second class in models.py
if you wish.
Either way, you should manage the death animation as a coroutine. There can only be one animator at a time. Either the frog is moving or it is dying. So this will be a lot like the two animators in coroutine1.py
in the provided sample code.
Creating the Death Sprite
The format and file for the death sprite is defined in 'objects.json'
using the DEATH_SPRITE
key. You will notice that it has no hitboxes. That is because the death sprite cannot collide with anything (it is already dead). Thus it is safe for both the hitbox
and hitboxes
attribute to be None.
While you can create the death sprite each time the frog dies, it is better to create it once in the beginning, at the same time that you create the frog. That is why we suggest turning the Frog
class into a composite. It displays either the frog or the death sprite, though both will be invisible if the frog makes it to the exit. In addition, the death sprite should be located exactly where the frog was when it died. This is exactly the thing that you learned how to do with composite objects in lab 22.
Once again, however, we will not be checking this aspect of your code. If the death sprite works, then it works. We do not care how you do it.
Animating the Death Sprite
The coroutine for the death sprite is even easier than the one for the frog. It does not have to move. It only has to go from the first frame to the last frame. The amount of time to animate DEATH_SPEED
. If you could animate the frog, this is simple.
The important feature, however, is that you should not pause the game (and deduct the life) until the death animation is finished. If you pause to early, the player will see the skull-and-crossbones, but the death will not be animated.
Testing Your Code
When you are done, your game should look like the video at the start of the instructions (minus the sound). You are almost done.
|
Pacing Yourself
This is much easier than the frog movement, so you completed that task you should be able to finish this in a day. We suggest finishing this by Saturday, December 19. This will give you one day for the last task and then another day to check over your work.
Completing this activity successfully will guarantee you a solid A on this assignment.
Play Sounds
If you watch the video above you will notice that we have sound effects. The frog croaks when it jumps (CROAK_SOUND
), splats when it dies (SPLAT_SOUND
), and trills when it reaches an exit (TRILL_SOUND
).
To load an audio file, you simply create a Sound object as follows:
Once it is loaded, you can play it whenever you want (such as when the frog jumps)
by calling jumpSound.play()
.
We are not going to tell you anything more than that. If you made it this far, you can read the online specification to see how to use Sound
objects. You cannot replay a sound until the current version of the sound stops. So if you want to play the same sound multiple times simultaneously (which is only likely to happen if you add extensions, you will need two different Sound
objects for the same sound file. Proper game audio can get really complicated and this is one of the professor’s active areas of research.
Important: Loading sounds can take a while. We recommend that you load all sounds you plan to use at either the start of the game or the start of a level.
Pacing Yourself
You will want one last day to check all your work and go over the finishing touches. Therefore, we recommend that you finish this activity by Sunday, December 20.
Completing this activity successfully will guarantee you a high A (but not an A+) on this assignment.
Additional Features
If you are ahead of schedule, then you are welcome to do whatever you want to extend the game and make it more fun. Doing so will require that you not only add more code, but also that you make your own level files. For example, you might want to add turtles to the game. You will notice that we have provided a sprite sheet for a submerging turtle in the IMAGES
directory. We also have provided a tasty fly for the frog to eat as a pickup. And what would Frogger be without a score documenting how fast or how far you made it in the game?
You are allowed to change anything you want so long the program runs normally on the level files that were provided. If you want to demonstrate anything new you have to define your own level file to show it off. We recommend that you give this level file a version number other than 1.0. In fact, you might want to veersion them in the order that you want us to look at them.
If you have additional features in your game, we may award extra credit for them. Indeed, additional features are the only way to make an A+ on this assignment. However, there are some important rules here.
First of all, we will not look at additional features unless you have (mostly) completed Task 2. There is no point in adding features if you cannot play the game. It is okay if you have bugs in Task 2, and the extra credit may compensate against some of those bugs. But do not work on additional features until the frog can successfully ride the logs.
Second, we will not answer any questions about how much extra credit is worth. Extra credit is extra credit. You do it because you want to, not because you are hoping it will save your grade. We will decide how much your effort is worth when we see it, after it is turned. Not before. This is a time for your imagination to shine, not for you to check off boxes.
We will say this much, however. No matter how many additional features you add, extra credit will never raise you grade by more than half a letter. So it can take you from a B+ to an A-, an A- to an A, and so on. But that is it. Do not get too carried away.
We highly recommend that you back up your solution before working on any additional features. Many times we have seen students completely ruin their perfectly good code adding the new features. Do not do that.
If you do add any extensions, we ask that you add a README text file to your submission. Document any extensions that you have added and tell us what level files we should run in order to experience them.
Finishing Touches
Before submitting anything, test your program to see that it works. Play for a while and make sure that as many parts of it as you can check are working. Cycle between multiple level files.
When you are done, reread the specifications of all your methods and functions (including those we stubbed in for you), and be sure that your specifications are clear and that your functions follow their specifications. If you implemented extensions, make sure your documentation makes it very clear what your extensions are.
As part of this assignment, we expect you to follow our style guidelines:
- You have indented with spaces, not tabs (Atom Editor handles this automatically).
- Classes are separated from each other by two blank lines
- Methods are separated from each other by a single blank line
- Class contents are ordered as follows: getters/setters, initializer, non-hidden methods, hidden methods
- Lines are short enough that horizontal scrolling is not necessary (about 80 chars)
- The specifications for all of the methods and classes are complete
- Specifications are immediately after the method header and indented
- No method is more than 30 lines long, not including the specification
We are serious about the last one. This is a potential 10 point deduction.
Turning it In
You are potentially modifying a lot of files in this assignment. At a bare minimum, you are modifying app.py
, level.py
, lanes.py
, and models.py
. You might be modifying consts.py
. You might have extra art and sound files.
To simplify the submission process, we are not asking you upload each individual file. Instead, put all your files in a zip file called froggit.zip
and submit this instead. We need to be able to play your game, and if anything is missing, we cannot play it. However, make sure that your file is less than 100 MB. CMS cannot take anything more than that and sound files can get very large.
If you did add any extensions, then your submission must include a README file documenting all of the extensions you added. You should also tell us what level files we should run in order to experience them. We cannot give you credit for extensions if we do not know about this. If there is no README file, we will assume the game has no extensions.
Completing the Survey
One last time, we need you to do a survey. As always, the survey will ask about things such as how long you spent on the assignment and your impression of the difficulty. Please try to complete the survey within a day of turning in this assignment. Remember that participation in surveys comprises 1% of your final grade.
This is designed to be a very hard assignment. We realized that is what we were going to have to do if we replaced the final exam with an assignment. We had to create an assignment that possibly half the class could not finish (though if everyone can finish it, I will happily give out a lot of As this semester). We are interested in whether or not that was the case, and how much time you actually spent on the assignment.