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 assignment will be a Frogger clone.
|
|
If you have never played Frogger before (or its modern equivalent Crossy Road), there are a few versions online. You can try HappyHopper on the web or Classic Frogger on Android. Either will give you a good feel of the game.
Our assignment shown above is a simplified version of the game. In particular, you will notice that we only have the water segment. We do not include the portion of the game with the moving cars. That is because those two parts of the game – the road segment and the water segment – play very differently, and supporting both of them can double the length of the assignment. In fact, a previous version of this assignment contained both parts and developed a reputation as the hardest Assignment 7 ever created. We are confident that restricting this assignment to the water will make it much more reasonable and fair.
One of the main challenges with this assignment is that its scope is completely up to you. There is a bare minimum of functionality that you must implement. In fact, the video shown above is way more than we expect you to do. You can see a better representation of what we expect in the assignment overview. Once you have finished that, you are free (and encouraged) to add more interesting features to your game. The video above shows our solution, which has several extra features such as sound, scoring, animation, and submersible turtles. You are permitted to do anything that you want, provided that the basic functionality is there.
Despite any difficulties you might have had with previous assignments, we know that this assignment is easily within your grasp. You just have to start early, break the problem up into manageable pieces, and program/test incrementally. Below, we discuss stages of implementation and give suggestions for staying on top of the project. If you follow our advice and test each piece thoroughly before proceeding to the next, you should be successful.
Our bare minimum solution is about 1030 lines of code (including specifications
and comments). This is ~270 lines in app.py (including the ~160 lines already
there), ~385 lines in level.py (including the ~60 lines already there), and
~385 lines in models.py (including the ~160 lines already there). So you
should expect to write approximately 660 lines of code for the bare minimum
solution. While this may seem intimidating, the majority of that consists of
headers and specifications. If we remove the comments, specifications, and
blank lines, our minimum solution only has 300 lines of code. This code is
is spread across ~30 methods in six different classes, not including getters
and setters.
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 collisions and animation
- 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
- The Basic Game
- Additional Features
- Finishing Touches
Academic Integrity
Because of the complexity of this assignment, this is the one where we most worry about improper collaboration. For that reason, we will be running Moss very heavily on this assignment. Because it is so open-ended it is completely impossible for your code to look like someone else’s, unless you looked at the code and then used it to design your own. We are 100% guaranteed to catch you if you do this. No matter how different you think your code will be, it will not be different.
This also likely to be true if you use generative AI, as we find that it suggests similar solutions to different students. Also, we suspect generative AI is probably trained on an older, harder version of this assignment. We have made so many changes to this assignment (in an attempt to make it easier) that it is effectively unrecognizable. This means that any references to the old assignment will be suspicious.
Because of the time that it takes to grade this assignment, any academic integrity hearings will have to be delayed until Spring semester (meaning that such students will receive an Incomplete for the course). Do not put yourself in this position. We also ask that you do not enable violations of academic policy. Do not post your code to Pastebin, GitHub, or any other publicly accessible site.
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.
Collaboration Policy
You may do this assignment with one other person. If you are going to work together, form your group on CMS as soon as possible. If you do this assignment with another person, you must work together. It is against the rules for one person to do some programming on this assignment without the other person sitting nearby and helping.
With the exception of your CMS-registered partner, we ask that you do not look at anyone else’s code or show your code to anyone else (except a CS1110 staff member) in any form whatsoever. This includes posting your code on Ed Discussions to ask for help. It is okay to post error messages on online, but not code. If we need to see your code, we will ask for 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 was 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 sound effects from FreeSound 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 Ed Discussions.
Extension Policy
This assignment is technically due Monday, December 8, which is the last day of class. That is the last day we are allowed to make this due. However, we realize that ALL professors like to load you up with work the last week of class. Because of this fact, minor extensions are possible.
We are allowing no-questions-asked extensions up to Wednesday, December 10. To get an extension, you must contact your lab instructor (do not e-mail the professor). Your instructor will assign you an extension in CMS. If you do not ask for an extension, you will be penalized for being late.
Assignment Help
While the code in this assignment is only a little bit more complicated than Assignment 6, the directions are more open-ended. We have tried to give you as much guidance in this document as we can. At the end of each task we have section called Reviewing the Task where we break down what we expect from you. But even then there is a lot to read in this document. We suggest that you start this assignment early, and that you go to office hours page on the first sign of difficulty.
In addition, you should always check Ed Discussions for student questions as the assignment progresses. We may also periodically post announcements regarding this assignment on Ed and on the course website.
Organization and Scope
This assignment is even longer than Assignment 6. Fortunately, we have (technically) given you over three weeks for this assignment! But that time includes both Thanksgiving Break and the exam, and we want to give you time for both of these. Therefore, it is very important that you pace yourself. This assignment can be finished by the end of classes, if you work a little bit every day, excluding the Thanksgiving break and two days before the exam.
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. There are 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 |
Only the first is a must download, as it contains the all of the source code necessary to complete the assignment. The second is a collection of demo code from the lecture on GUI programming. 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
python froggitIn 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 game of Froggit |
| models.py | All model classes for the game (Frog, Exit, etc.) |
| consts.py | A 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 |
| Data | A folder with different levels you can play |
For the basic game, you only need to worry about the first
three files: app.py, level.py, and models.py. The other files and folders
can be left alone. However, if you decide to add a
new features, 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 three 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
three modules that you must modify for this assignment, and is the one that will
require the most original code.
models.py
This module contains the model class Frog, Pad, Hedge, and Exit.
These classes are 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 three 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, 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 you game.
These are classes that you will subclass, just like we demonstrated in
lecture. 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 may wish to use as part of your new feature. 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 MacOS is unreliable.
Fonts
This is a folder of True Type Fonts, should you get tired of the default Kivy font. 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.
Data
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 right away 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 pausing the game). 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, and the classes Frog, Pad, Hedge, and Exit in models.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 know whether an Exit object is occupied, then you are going
to need a getter for this attribute.
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 class. 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 view and the models.
This leads to an important separation of files. Froggit is never permitted to
access anything in models.py, and Level is never permitted to access
anything in app.py. This is an important rule that we will enforce while
grading.
|
| The Import Relationships |
|---|
In addition to the three 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 Ed Discussions (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:
- Convert any input into a valid move
- Animate the frog according to player input
- Move all the terrain 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. You should think about doing this in your code as well.
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 terrain. You will notice that all of our model classes are subclasses of GImage. That is because they are represented by images. They inherit methods and attributes from this class that allow us to move them about the screen, and to draw them.
The reason why we need all these clases (instead of using GImage directly) is
because they all have different behavior. New behavior means additional methods
or attributes. In general, the purpose of these classes as follows.
Frog: This class represents the player as they move about the screen.Pad: This is any piece of terrain that the frog can safely step onHedge: This terrain will block the frog, but not kill itExit: The is the terrain the the frog needs to reach to complete the game
As you can see each class is distinguished by its behavior, not its looks.
A Pad object can either be a lily pad or a log; while they may look different,
they are the same as far as the frog is concerned. But Pad objects have to be
different from Hedge objects because they do not block the frog. Exit
objects are interesting in that they behave like a Pad when unoccupied, but
behave like a Hedge when there is a frog resting there.
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, which means that they need their own class. You should only add new classes for the additional features. If you are in doubt about whether or not you need a new class, ask us on Ed Discussions.
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. We have also tried not to include both your Thanksgiving Break and the second exam in this assignment.
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, but do not focus only on writing code. Leave time for learning things and asking questions.
We have provided you with a schedule of suggested milestones. This schedule ensures you will only work a little each day. However, to fit in the time alloted, this schedule does not leave any time for extra features. This is okay. You can still get a perfect score with no addition features. But if you want to add features you will either need to work faster than the recommended schedule, or ask for an extension. But whatever the case, do not add any new features until you get the basic game working. If you add extensions too early, debugging may get very difficult.
Overview of Froggit
As we mentioned above, this game is based on the water segment of Frogger. The game is based on the (ludicrous) idea that frogs cannot swim. If the frog hits the water, it drowns and dies. The frog starts at one side of the screen and has to use the terrain (lily pads or logs) to cross to an exit, which is marked by a yellow lily pad. Once a frog reaches an exist, it is safe and you start with a new frog. Once all exits are filled, you win the game.
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 file easy.json
is an easy level to help you test your game, while variable-currents.json and
medium.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
python froggit variable-currents.jsonit will play the game with the level variable-currents.json as the
DEFAULT_LEVEL.
The level file easy.json is designed to help you with Tasks 1 and 2. You will
not really need any of the other level files until the end. Below is the set-up
for this level.
|
| The Easy Level |
|---|
All of the levels are arranged as a grid, where each grid square is GRID_SIZE
pixels on each side. The level consists of a water image in the back, with
several terrain objects drawn on top. These terrain objects can be lily pads,
logs, hedges, or exits. They are all organized in lanes. Each lane is one
grid unit in height, and several grid units in width. Each terrain object is
one grid unit wide. 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 terrain may start to move across the screen. They
go in different directions, specified by the "currents" entry in the JSON
dictionary. Once they go off the screen, they wrap back around to the other
side.
The frog moves by jumping GRID_SIZE in any direction. This jump is blocked
if there is a hedge GRID_SIZE away in that direction. Otherwise, it is free
to jump where ever it wants, even if there is no terrain in that direction.
|
| Legal Moves |
|---|
The frog has to safely reach (e.g. touch) one of the exits, which is a yellow lily pad. Exits are usually in the last row, but this is not required. When the frog reaches the exit, 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. Note that the skull marks the place the frog died, but is not actually required for this assignment.
|
| Down One Life |
|---|
The frog dies if it lands in the water (e.g. after jumping GRID_SIZE pixels).
Why this frog cannot swim is a mystery from the early days of video games.
To survive on the water, the frog must hop on to a lily pad or a log (e.g.
a Pad object). But the lily pads and 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
exit. You lose if you run out of lives before this happens. In the video below
we show what we are expecting from you in the basic game on the level
variable-current.json.
|
|
| The Minimal Solution |
|---|
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 terrain 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 terrain is 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 easy.json. This includes all of the features that you will need to
implement 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.
"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 also a list, representing
the terrain objects in that list. Each terrain object, in turn, is represented
by a dictionary with the following keys: "type", "image", and "column".
The "column" is its grid location in the lane, while the "image" is the
image to display for this object. The "type" value is one of "pad",
"hedge", or "exit", indicating the class to use for this terrain.
"currents": A list of the current values for each lane.
The current list should have the same length as the lane list. For each lane,
it is number representing the speed and direction to move the terrain. If the
value is 0, then the terrain in that lane does not move (so easy.json is a
level with no movement). Otherwise, all terrain in a lane move at the same
pace.
This current number for each lane is the number of pixels to move the terrain per second. Movement is left-to-right. If the speed is negative, then the movement is right-to-left.
If you understand all of these features, then you will have no problem completing this assignment.
The Basic Game
We have divided these instructions into two parts. This first part covers the basic things that you must implement just to get the game running. Afterwards, we talk about additional features you can put into your game. These extra features can earn you extra credit on this assignment.
You should focus most of your effort on the basic game. This is the bulk of your grade. If you do everything correct on the basic game, you can still earn a 100 on this assignment. The value of extra credit is typically limited to no more than 8 points. Furthermore, unless your features are incredibly innovative, we will not give you more than a 100, no matter how many things you have added.
Near the end of each task we have a section called Reviewing the Task where we summarize everything that we told you to do. Make sure you read this before writing any code.
1. 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.
|
| The Welcome Message |
|---|
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 text message will look something like the one above. It does not need to say “Press ‘S’ to play”. 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 that you be careful about allowing the user to press any key, as the user could press the arrow key and immediately drown their frog.
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 you will never
show the logo after the initial state STATE_INACTIVE, you 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 exactly 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
self._title.draw(self.view)
self._text.draw(self.view)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 you 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_titleattribute is None. - If the state is
STATE_ACTIVE, the_textattribute 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 a 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. Checking if the
key is down will return a lot of matches. You want to detect a key press
which is the first time the key is held down. Remember the method
(is_key_pressed) to do this.
When 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 _text and _title 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?
Reviewing the Task
We have covered a lot in these instructions. It helps to have some a reminder
of what you should doing. To finish this part, you are adding code to each
of the start, update, and draw methods of Froggit. In the
method start, you should
- Create an attribute for the title and assign it a
GLabel - Create an attribute for the message and assign it a
GLabel - Create an atttribute for the state and assign it to
STATE_INACTIVE
In the method update you should
- Check if the start key was pressed
- Change the state to STATE_LOADING if the key was pressed
- Ensure the application invariants are satisfied
Finally, in the method draw you should
- Draw the title if it is not None
- Draw the message if it is not None
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 Sunday, November 16, which is the Sunday after the assignment is posted. You will spend most of your time reading the online documentation (the actual code is not that long), but this will give you a solid understanding of how this application works. You will also have a lab the previous Thursday/Friday helping you with these topics.
2. Load the Level File
The state STATE_LOADING is going to last just one animation frame. During that
time you are going to load the level file and create a Level object. So for this
activity you are going to write code in two different classes: Froggit and
Level.
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 with the argument 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.
By default, the starting level is easy.json. You can go into consts.py to
change this if you wish. Alternatively, you can test other levels by typing
python froggit levelnameSo you can load the large.json level simply by typing
python froggit large.jsonResizing 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 level easy.json should make the window slightly smaller, while
large.json will actually make it a littler larger.
Creating a Level Object
Once you have loaded the JSON file, you want to create a Level object and
assign it to the attribute _level in Froggit. We have started the definition
of the Level class for you in level.py. However, it does not do much,
because we have not defined the initializer.
Without an initializer, the constructor will not take any arguments. However,
we want the Level constructor to take a single argument: the JSON dictionary
containing all of the information about the level. For now do not worry about
creating the attributes. You will do that in the next task. You can leave
pass in your initializer for now.
After you have created the Level object, it is also time to change the state
in Froggitto STATE_ACTIVE. As we said, STATE_LOADING only lasts on
frame. You load the file and immediately switch, regardless of what keys the
player presses.
Creating the Water
Your Level class is going to display several things on the screen. You will
need an attribute for each object that you display. The first thing that you
want to display is the water. The water is the background image for the entire
game.
You will notice that we have provided you with an image called water.png.
However, this is a tiny image. It is not meant to fill the screen. Instead, we
intend for you to use GTile object. A GTile is an image that
can be repeated. To draw water several times. For example, the image below shows
the difference between the image hedge-middle.png and 2x3 tiling of that
same image.
|
|
| Normal Image | 2x3 Tiled Image |
|---|
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.
Create this tile and assign it to an attribute _water. It should be positioned
so that the bottom left corner is (0,0). The size of this tile is defined by
the "size" entry in the JSON dictionary. Remember to multiply those by the
GRID_SIZE, as the tile needs its width and height in pixels. You do not need
to tell it how many times to repeat. That happens automatically (and is
determined by the original image size).
Drawing the Water
Once again, creating a GTile object is not enough to draw it on the screen.
But drawing the water is a bit more complicated than drawing the welcome
message. The water is a (hidden) attribute in Level. While the Froggit
code
self._level_._water.draw(self.view)works (and you should try it out), it is not allowed. That is because the
attribute _level is hidden inside of the class Level, and Froggit is not
permitted to access anything hidden in another class. We will take off style
points if a class of one module ever accesses the hidden attributes of an object
of a class in a different module.
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 draw method for the water (defined in GTile).
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.
self._level.draw(self.view)Now the only access of something hidden is _level, which is okay since it is
inside of Froggit (Froggit can access its own hidden attributes).
Notice this is very similar to how we draw GObject objects.
When you are done, you should be able to see the water on the screen. Remember
that the top GRID_SIZE pixels of the window are reserved for the life bar.
Therefore, when you load easy.json, it should look something like this.
|
| The Water in easy.json |
|---|
Reviewing the Task
In this task, you are writing code in four different methods. You are adding
code to the start and draw methods in Froggit. You are also adding an
initializer and a draw method to Level.
Let’s start with the methods in Level, as you need them first. In __init__
you should
- Get the JSON dictionary from
Froggitas a parameter - Extract the
"size"from this dictionary - Create an attribute
_waterfor the Water (GTile) - Assign the water source attribute as
WATER_IMAGE - Size the water to match the
"size"value - Position the water so that it is flushe with the bottom of the window
In the draw method for Level you should just draw the water to the window.
Now that your have Level working, let’s move to Froggit. In the start
method for Froggit, add an attribute for the level. It will start out as
None.
In the update method for Froggit you should
- Check if the state is STATE_LOADING (if not, skip the steps below)
- Load the DEFAULT_LEVEL as a dictionary using the method
load_json - Create a
Levelobject, passing the dictionary as an argument - Change the state to STATE_ACTIVE
Finally, in the draw method for Froggit, you should draw the Level
object if it is not None.
Pacing Yourself
There are a lot of classes working together now, but the code is still pretty straight-forward. The challenging part is understanding how everything fits together. This should only take you a day to figure it out, so try to finish this part by Monday, November 17.
3. Create the Frog
Right now the level is looking pretty empty. You do not even have a frog on
the screen. 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, you do not have to define an initializer for the frog. You inherit
the one from GImage already. But you 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 you want to specify is the frog position on the grid.
So that means you 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. So you should adjust x and y accordingly.
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 water tile. 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.
Reviewing the Task
This task is very straight-forward. You are
- Creating an initializer in the class
Frog - Constructing a frog object in class
Level - Drawing that frog object to the screen
To test that it works, load one of the level files. We recommend that you try
it on various level files, as the frog does not always start in the same
position. For the file easy.json, the frog will be positioned at the bottom
of the window. However, for the file large.json, the frog will be positioned
somewhere near the center, as shown below.
With that said, you are not going to show pictures here, because it is hard to determine if it is completely correct until you place the terrain, which is the next step. As long as the frog looks mostly correct, you can move on for now.
Pacing Yourself
This is super short activity. You should be able to complete this on the same day you finish the water, which is by Monday, November 17.
4. Create the Terrain
The level is still looking pretty empty, you need to create the terrain. If you
remember from looking at the JSON file, the terrain is organized in lanes, each
lane having GRID_SIZE height. Lanes are defined in the JSON file from bottom
to top.
You will notice that "lanes" is a 2-dimensional list in the JSON. The contents
of these list are small dictionaries describing the type, image, and position
of each object.
You want to make an attribute _lanes in Level that matches this entry. That
is, _lanes should be a 2-dimensional list containing terrain objects. The
terrain objects will all be one of three classes: Pad, Hedge or Exit.
Initializing the Terrain
These three classes are in the models.py file. Once again, all of them are
subclasses of GImage. However, you do want to add a custom initializer for
each one. For each initializer, you should pass the following information:
- the object lane
- the object column
- the source file
The last two are defined in the dictionary provided for the object, so you could
just pass that as a single argument. However, the lane is determined by the
position of the entry in the 2-dimensional list, so it needs to be a separate
argument. Use these three values to compute the right information to send to
super().__init__ for GImage.
Now that you have defined the initializers, it is time to create the objects.
Build your attribute _lanes as a 2-dimensional list. Use a nested for-loop to
process the "lanes" value in the JSON file. The elements you access in this
for-loop will be three-element dictionaries. Use the "type" value in the
dictionary to pick the right class to use. Create an object of that class and
add it to your lane accumulator.
Drawing the Terrain
Once again, you need to draw these objects in the method draw of class Level.
Simply create a nested-loop to handle your 2-dimensional list _lanes and
call the draw method of each element in the list. You do not need to define
a draw method in any of the classes, as it is inherited from GImage.
As a reminder, you should not draw the terrain last. It is alway important to draw the frog last. Objects are draw bottom to top and you want the frog to be on top of everything.
Reviewing the Task
This task is very similar to the previous one, just with more steps. You are
- Creating an initializer in the classes
Pad,Hedge, andExit - Constructing a 2-dimensional list
_lanesin classLevel - Drawing the contents of
_lanesto the screen.
Once that you have this done, you will be able to see all of the contents of a level file. So we recommend that you try out your code on several different level files to make sure that it is correct. Here is what the terrain should look like (with the frog included) for some of the provided levels.
|
Terrain for easy.json
|
|---|
![]() |
Terrain for hedges.json
|
|---|
|
Terrain for large.json
|
|---|
![]() |
Terrain for medium.json
|
|---|
Pacing Yourself
The hardest part of this task is just wrapping your head around the JSON structure. However, once you do, you will find that it is just as easy as the previous step. So we believe you can finish this in another day. Try to get this done by Tuesday, November 18.
5. Display the Lives
The challenge of Frogger is that you have a limited number of lives before the game is over. You 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 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.
Reviewing the Task
This is a fairly straight-forward task, so there is not much to say here.
However, we do recommend that you try it on different sized levels, such as
easy.json and large.json. That way you can be sure the lives bar it
being placed correctly. The other key challenge here is to make sure that you
pay attention tothe 30 line rule.
Pacing Yourself
This is a short activity, though we have given you a day on it. Try to finish this by Wednesday, November 19. Finishing this on time will put you in a good place for the next task, which is where things really start to pick up.
6. Move the Frog
You are done with the set-up of the game. It is time to allow the player to do something. You want to move the frog. But the way the frog moves is very special. Therefore, this is going to be the first hard step in the assignment.
The important thing to understand is that the frog moves by “jumping”. When the
player presses a key (we prefer arrow keys, but some people prefer WASD), the
frog turns in that direction and moves GRID_SIZE pixels. But we do not want
the frog to teleport those pixels. Instead, we want the frog to smoothly slide,
as show in the video above.
Think of it as follows. The frog has two modes: in the air and grounded.
The frog starts off as grounded. If you press an arrow key, the frog is now in
the air. It starts sliding in that direction a few pixels every animation frame.
It will spend FROG_SPEED seconds in the air, and at the end of that time it
will have moved GRID_SIZE pixels in total. Even if you let go of the arrow
key before FROG_SPEED seconds are up, the frog will keep sliding until it is
in position. At that point it is grounded.
Any keys that you press while the frog is in the air will be ignored. Only keys pressed while the frog is grounded will cause it to jump. With that said, this does create an illusion of constant movement because, if you hold a key down, that key will keep moving the frog again every time it becomes grounded.
This might sound complicated, but we are going to take you through the important steps. Right now, we just want you to have a basic intuition for what is going on. If you do not understand the frog movement, please talk to a staff member.
Adding the Movement Attributes
For this to work you are going to need to add several more attributes to the
Frog class. You will need
- An attribute to tell if the frog is grounded or in the air
- An attribute telling us where the frog began its jump
- An attribute telling us where the frog is jumping to
- An attribute indicating how long the frog has been in the air
We leave it up to you to determine the type of all these attributes (there is more than one way to do this). You should also decide what the values of the last three should be when the frog is grounded. That will determine your invariant for these attributes.
When you have figured this out, add these attributes in the initializer for your
Frog class. You will also need to add comments describing the class invariant
for these attributes in class frog.
Adding the Movement Methods
The Frog class is going to need two movement methods. The first method
starts a jump. It will take a position (x,y) to jump to and assign values to
the attributes listed above. In particular, it will chose the current frog
position as the beginning of the jump, and the position (x,y) as the end of the
jump. It will also mark that the frog is now in the air. But with all this
said, this method does not move the frog. It only sets up the frog for the
jump.
The second method will move the frog. This method takes a single value: dt.
This value is the number of seconds that have passed since the last animation
frame. This value is added to the attribute for listed above for recording how
long the frog is in the air. If this total time exceeds FROG_SPEED, the
frog is placed in the landing position and marked as grounded. Otherwise, it
will interpolate the frog’s position between its take-off and landing.
To understand interpolation, suppose we have points \(\boldsymbol{P}\) and \(\boldsymbol{Q}\) and a number \(t\) in the range 0 to 1. The \(t\)-interpolation of these points is defined by the formula
\[\begin{equation*} \boldsymbol{R} = (1-t)\boldsymbol{P} + t \boldsymbol{Q} \end{equation*}\]Use this formula to compute the current location of the frog. In this equation,
\(\boldsymbol{P}\) is the takeoff position, \(\boldsymbol{Q}\) is the landing
position, and \(t\) is the total time in the air divided by FROG_SPEED. The
result will be something like what is shown below.
|
| Frog at \(t = \)time/FROG_SPEED |
|---|
Starting the Jump
To force the frog to jump, you will need to take into account the player’s key presses. The frog only jumps when the player presses (or holds down) a key to make it move. By default, we assume that the player will use the arrows keys to move the frog. However, if you prefer WASD controls or some other control scheme, that is okay (though you will need to tell us about it in the README).
To see how to move 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.
Note that this is actually easier than dismissing the welcome message. We do
not care if a key press is the first one. The frog will continue to jump so
long as you hold down a key (and the frog is grounded).
The frog movement takes place every animation frame. That is why you want to
put it in an update method for Level. Remember that this method must be
called within the update method of Froggit, or nothing will happen. Again,
see the subcontroller.py demo from the sample code
to understand what we are asking for.
However, to check the keyboard, the method update in Level will need to
access the input attribute Froggit, which is an instance of
GInput. Again, since Level is not allowed to access any of
the attributes of Froggit, that means you need to pass input as an argument
in this method call.
As a reminder, you should only process a jump if the frog is grounded. If the
frog is not grounded, you ignore all (movement) inputs. Using the appropriate
arrow or WASD key, compute the jump location. Then use the method in the Frog
class to start this jump. Finally, you should turn the frog to face this jump
(though you are free to add this code to the jump method in Frog if you wish).
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.
Updating the Frog
The update method in Level should ignore player input if the frog is in
the air (not grounded). However, if the frog is in the area, you will need to
do something else. In this case you should call the move method in the class
Frog, to move the frog to the correct position.
When you call this method, you should provide it the dt value that is the
parameter to update in Froggit. That means you will need to add dt as
a second, additional argument to update in Level.
Reviewing the Task
This task requires you to put code in three different classes: Froggit,
Level and Frog. Once again, pay attention to the 30 line rule when you
are trying to figure out your code.
We recommend you start with Frog class first.
- Add attributes for the ground state, takeoff, landing, and time in the air
- Add a jump method to start a jump (but not move the frog)
- Add a move method to guide the frog from take-off to landing using interpolation
In the update method of Froggit you simply call the update method in
Level, remembering to pass it any input (e.g. what keys are pressed and the
current value dt).
In the update method of Level, you should
- Check if the frog is grounded
- Start a jump if the frog is grounded and a key is pressed
- Move the frog by
dtif the frog is not grounded
Once you have done all this, you should be able to move the frog. Load up a level and press the arrow keys. Move your frog about the map. For now, you should be able to go anywhere, including in the water or off the screen. However, make sure that you are always aligned with the grid. Use the terrain to help visualize this if need be.
Debugging Your Code
This is generally the first place the students encounter real difficulty. It
is possible to do everything perfectly up until now only for your frog not
to move. 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 (something
like) this line of code in its update:
self._level.update(self.input,dt)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 constant but the frog is not moving, there is actually a problem with their debugging statements.
Pacing Yourself
We highly recommend that you complete this by Friday, November 21, ending the first real week of work. This is often a stumbling block for students, and this will get you over the hump before you leave for Thanksgiving break.
7. Restrict the Frog
Right now, the frog can go anywhere, including off the screen. We want to put some restrictions on the frog so that they stay on screen. When we say “on screen”, we mean the part of the window that includes the water tiles. We do not want to include the top bar with the lives counter in it. As you can see in the video above, our frog stops when it reaches the final row.
How do you do this? Look at where the frog will end up before you move it. You
already do this. To move the frog, you compute a jump location before you tell
the frog to jump there. Before you call the method in Frog, but after you
compute the jump location, check if it is offscreen (e.g. less than 0 or greater
than the width). If its is offscreen, do not tell the frog to jump. However
if that movement requires the frog to turn, you should still turn the frog in
place, even if it cannot move forward.
Detecting Movement into a Hedge
Your frog should now stop at the top lane of easy.json. However, this lane is
completely filled with hedges and you can freely move about them. We do not
want that. An grid location in a hedge location should block the frog. As with
offscreen movement, this means that the frog can turn towards the hedge, but
will not move forward into it.
As a reminder, the Hedge class is a subclass of GImage, which is itself a
a subclass of GObject. This means that all Hedge objects
have a method contains that tests if a point is inside of the hedge. So loop
over all of the terrain, and see if the jump location is ever inside of a hedge.
If so, do not permit the jump to happen.
As an aside, there is some chance for optimization here. When you are looking for hedges, you maybe tempted to loop over all the terrain the game. This is not necessary. It is very easy to figure out the lane of a jump location (we will let you figure out how on your own). So you only need to loop over the terrain in that lane. While this may not seem like a big deal, if you add a lot of features to your game, any little bit matters.
Reviewing the Task
This task requires you to check two things before you tell a frog to jump
- Is the jump offscreen?
- Is the jump inside a hedge?
If the answer to either of these is yes, do not allow the jump.
To test out your code, you might want to try the level hedge.json. This
level is very similar to easy.json, except that it has a lot of hedges. You
can use these hedges to make sure that your frog is not going where it
should not.
Pacing Yourself
This task is much easier than the previous one. You should be able to get back to completing one task a day. So for that reason, we recommend that you complete this by Saturday, November 22. We are trying to get you to complete as much as possible before Thanksgiving break.
8. Kill the Frog
You have gotten the frog to move, but the game is not very interesting. Lily pad, logs, and exits serve no purpose. 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: water.
The Frogger frog cannot swim. Maybe its mother never taught it how. What this
means is that if the frog lands in the water and it is not on top of a Pad
or Exit object, it dies.
Before we talk about how to kill the frog, we first need to think about how to detect a frog death. First of all, the frog can only die if it is grounded. No frog in the middle of a jump can die.
If it is grounded, you need to look at all of the terrain in the game (and as
an optimization, it is fine to only look at the terrain in the same lane as the
frog). All the terrain – Pad, Hedge, and Exit objects – are subclasses
of GImage, and so they all inherit the contains method. Check to see if
there is any Pad or Exit object containing the frog. If so, the frog is
alive. Otherwise, it is dead.
It is important to understand that you must check for frog death every frame. This is different from jumping, as that only happens when the player presses an arrow key. If you put this code in the same method as movement, then the frog might sit in the water, cheating death until you press an arrow key.
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 a
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, just like we have done
below.
|
| Game Paused |
|---|
Notice that you are displaying a message to the player. You can do this with
_text attribute in Froggit, provided that you draw it last. There is no 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
color, 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 'easy.json' is pretty narrow, but
what if you 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. And you are welcome to drop the font
size from what we have set it at, should this be necessary.
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 display 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 might immediately move forward and land back into the water.
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;
you 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.
Reviewing the Task
This task should not require you to modify any of the model classes. Just use
the inherited contains method. The only places you need to add code are in
Level and Froggit.
In Level, you need to do the following each frame
- Check if the frog is in the air
- Check if there is a
PadorExitobject containing the frog position
If either of these is true, the frog is alive. Otherwise, the frog is dead.
In Froggit, you need to do the following.
- Check if the frog has died
- Switch to
STATE_PAUSEDand display a message if so - Switch back to
STATE_CONTINUEwhen the player presses a key - Reset the game in
STATE_CONTINUEand switch back toSTATE_ACTIVE
Once you have done all this, it is time to start killing frogs! Load up a level and step into the water. You do not lose any lives yet, so it is harmless. Make sure that you die when you land in the water, and live when you land on a pad or a log.
Pacing Yourself
You should try to finish this by Sunday, November 23. While we realize this is just one day after the last task, a lot what you need to do here is similar to what you had to do for the hedges. The only new and tricky part is pausing and unpausing the game. And this is a good push for before Thanksgiving break starts.
9. Lose the Game
While the frog can die now, it is still not that punishing. You are able to respawn indefinitely, so it is impossible to lose the game. It is time to change that. Only through loss can you truly appreciate a win.
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 – just slice the list. 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. However, to help with testing, we recommend that
you make one more addition to the game. Before, you allowed the player to
press ‘C’ to continue. We like to allow the user to press ‘R’ to restart the
game.
Restarting the game is very easy. If you are currently in STATE_CONTINUE,
check if the player presses the restart key. If so, simply switch the state
to STATE_LOADING. The code you have already written will handle the rest,
reloading the game level and starting over.
While this is an optional feature, it does not technically count as an extension). You cannot earn extra credit for this feature.
Reviewing the Task
This task requires that you modify the lives bar in Level every time a frog
is killed. This is pretty simple, and you can put that code in the same place
that you “removed” the frog.
Assuming that you added a new method to check the current number of lives in
Level, you will need to do the following in Froggit:
- Switch to
STATE_COMPLETEif no lives are left - Display a special message if you are in
STATE_COMPLETE - Allow the user to restart the game (OPTIONAL)
Once again, testing is simple. Kill the frog until you lose. To fully test out
your code, you might want to play around with the FROG_LIVES value in
consts.py. Give your frog more or less lives and see what happens.
Pacing Yourself
This is the last task that we ask of you before Thanksgiving break. Please finish this before you leave on Monday, November 24. If you make it this far, you can enjoy your Thanksgiving break guilt-free and not worry about CS 1110 all weekend.
10. Reach the Exit
Welcome back from break. When you left, you only knew failure. Now it is time to taste victory. But to do that, you need to get your frog to the exit.
You already have code that determines if your frog is on a Pad object or an
Exit object. You had to do that when you writing the code to kill your frog.
But now we want you to go back to that code and think about what happens when
you reach an exit.
Occupying an Exit
If the terrain object that contains the frog is an Exit object. You want to
do several things. The first is to remove the frog, just like you would do when
you lose a life. In addition, you should mark the Exit object as occupied.
That will require an additional attribute in Exit with getters and setters.
Remember to document this attribute and its invariants when you add it to the
initializer.
An occupied exit has several new features to it. First of all, you want to draw
a blue frog on top of it – the one for the image FROG_SAFE. There are several
ways to do this, but we recommend adding a new attribute to Exit and modifying
its draw method. The lab that you worked on way back when you first started this
assignment will be helpful here.
In addition, an occupied exit is now effectively a hedge. For the rest of the game, when you block the player’s movement with hedges, you should also block movement with occupied exits.
Pausing the Game
When you lost a life, you paused the game and displayed a message to the player. This message told the player to press a key to continue. You are essentially going to do the same thing here. The player has guided one frog to safety, and you need to give them a new frog (after a brief pause). The only difference between this task and losing a life is that you do not modify the lives bar. Even the message to continue the game should be the same!
Reviewing the Task
Most of the code in this task actually happens in Exit. You need to do the
following:
- Add an attribute (with getters/setters) to mark an exit as occupied
- Modify the
drawmethod to add a blue frog if an exit is occupied - Update the initializer and class invariant for the new attribute(s)
In Level you need to add code that checks if the grounded frog is in an
unoccupied exit. If so, you
- Mark that exit occupied
- Remove the frog, signaling to
Froggitto pause the game.
Test our your code in the obvious way. Try to reach each of the exits in
easy.json. Make sure that future frogs cannot use an occupied exit. We also
recommend that you now try your code on large.json. That level has exits in
some unorthodox places – not just the top row. If your code works on that
file, you can be fairly certain it is correct.
Pacing Yourself
While you are back from break, the exam is round the corner. Fortunately, this is a short task that you can work on while you study. Try to finish this on Monday, December 1, the day you come back from break. If you finish this and the next task, you can stop and study for the exam.
11. Win the Game
The moment has come. It is now time to turn this into a proper game. You can already lose. Now you want to win.
Winning is simple. You lose when your lives bar is empty. You win when all of
the exits are occupied. So you will need some way to detect if there are any
unoccupied exits. But otherwise the idea is that same as losing the game.
Have Froggit check for a win condition, and switch to STATE_COMPLETE when
the game is done. The only difference is that you should display a
congratulations message, like the one below.
|
| Game Won |
|---|
Once again, you might want to add the option to restart the game so that you can play the level one more time.
Reviewing the Task
There is not much to say here. You should know what to do. We recommend that
you test out both easy.json and large.json so that you can experiment with
different numbers of exits and exits in unusual places.
We also recommend that you test out how losing and winning interact with each other. Can you win with only one life left? Can you lose with only one exit left to go?
Pacing Yourself
This is a really quick task, which is why we are comfortable asking you to do it before the exam. Try to finish this one by Monday, December 1. If you can do that, you stop working on the assignment and focus on studying for the exam. You will finish the last two tasks over the weekend.
12. Shift the Terrain
Technically you have a complete game. You can win and you can lose. But it is not a particularly challenging game. To lose you have to land in the water, and that requires conscious effort.
As you can see from the video above, all of the terrain (particularly lily pads and logs) should be moving. They move due to currents. A current is a uniform movement to apply to all objects in a lane. If a lane has non-zero current, then every object in that lane will move at the same speed in the same direction. A positive current moves objects to the right, and a negative current moves objects to the left.
You will worry about currents in the next task. But now you have to deal with
a different problem. Currents mean that you can no longer assume that terrain
objects are actually in a single grid square. To see what we mean, look at the
level offset.json. That file has floats for the column values (which is
allowed!). If you load that level file, you will see that the lanes are slightly
shifted, as shown below.
|
| The Level offset.json |
|---|
Your frog need to be able to work on that level. It needs to be able to safely
traverse the Pad objects, and it needs to reach the exits. But it cannot.
That is because the method contains in GObject only
returns True if the frog’s position is on the inside of the object. If it is
on the outside edge of the object, as is the case for many of the objects here,
it will return False.
To solve this problem, you are going to need to go back through your code and
look for all of the places that you used the method contains. You are going
to have to make some simple changes. There is a different change for each of
the three types of terrain.
Visualizing the Hitbox
Objects in games have what is know as a hitbox. Collisions are recorded if that hitbox overlaps with another object in some way. In many instances, the hitbox is the same as the image, meaning that it is image rectangle. So if the two images overlap in any way, they collide with each other. Note that collisions can be bad (running into a spike) or good (touching a powerup).
The hitbox does not have to be the entire image. If image represents a character with arms and legs, we might not want to include the limbs. The idea is that the character can move these out of the way quickly and they should not count. In that case, the hitbox is smaller. And while we call it a hitbox, it does not have to be a rectangle. It could be a circle. This is what we are going to do with the frog.
Up until now we have been looking at the position of the frog. The frog’s
position corresponds to the center of the image. In consts.py there is a
value FROG_RADIUS. The idea is that the frog hitbox is a circle about its
center with size FROG_RADIUS as shown below.
|
| The Frog Hitbox (\(r\) = FROG_RADIUS) |
|---|
While the frog hitbox is a circle, the terrain objects are still going to be rectangles representing the entire image. Figuring out when a circle overlaps a rectangle can be tricky. Fortunately, our game has one special feature that makes it simple. While the horizontal position of a terrain object can shift, the lane will never change. Lanes still align properly with the grid.
In addition, we only worry about collisions when the frog is grounded. When the frog is grounded it is also properly aligned with a lane. So that means that, for any terrain that could possible collide with the frog, it will always be perfectly aligned in the same lane. For that reason, if you want to test if the frog collides with a terrain object rectangle, you only need to check the left and right sides of the circle. This is shown below.
|
| Frog Collision |
|---|
What this means is that instead of performing one contains check on the center
of the frog, you are now performing two contains checks on the left and right
sides of the frog. This is true even if the frog is rotated. However, you will
need to compute the left and right sides yourselves. While there are attributes
left and right in GImage, those are for the entire image, not the hitbox
(and they shift when the image is rotated).
Updating Hedges
Previously, hedges blocked the frog if a jump would put the frog inside of the hedge. Now, take that same jump location and compute the left and right sides of the hitbox. A hedge blocks the frog if either side is inside of a hedge.
Note that you should apply this same logic to occupied exits as well. However, unoccupied exits are a little different.
Updating Exits
Previously we marked an exit as occupied if a grounded frog was inside the exit. Now we are going to require that both sides of the hitbox of a grounded frog be inside the exit. If only one side is in the exit it does not count. In fact, because an exit often has an hedge on its sides, this means that the hedge would block the frog. But the exit itself will not block the frog. It simply refuses to count this as a successful exit and the game will continue without pause.
Updating Water
The previous two changes could be made to the appropriate model classes
(though you do not have to make the changes there). But this final change
must be made in the Level class. When you determine if the frog is still
alive, you search all of the lanes and make sure that there is a Pad or Exit
object that contains the grounded frog. Now you need to make sure that there
is a Pad or Exit object that contains the left and right sides of the
hitbox.
The important thing to keep in mind is that it does not have to be the same object for the two sides. In the picture below, the frog is safe because the left side is inside one lilypad and the right side is in another. However, if just one side is hanging out over the water, the frog looses its balance and dies, as shown below.
![]() |
![]() |
| Live Frog | Dead Frog |
|---|
Because you need to check each side separately, this must be done in class
Level. Once you have done that, you are done with the task.
Reviewing the Task
As a reminder, you need to make a change to your existing code in three different places.
- Modify the hedge blocking code to include the hitbox
- Modify the exit detection code to include the hitbox
- Modify the frog killing code to include the hitbox.
Once you do all this, you should be able to win the level offset.json.
Verify that this is the case. In addition, make sure that your frog dies
when it becomes unbalanced on the edge of a Pad object.
Pacing Yourself
This is not too hard, but we are going to give you a day to recover from the exam. Try to have this task complete by Saturday, December 6. This allows you the last two days to work on the final tasks (the last one of which is really short).
13. Apply the Currents
Now that you can handle shifted terrain, it is time to get things moving. The
currents for a level are in the "currents" entry at the bottom of the
level JSON. You can see that they are all 0 in easy.json. That means that
nothing moves in this level. We intended this level to be easy.
To test currents, you need to use another level. The two levels
steady-currents.json and variable-currents.json are just like easy.json
except that they have currents to move terrain. As the name suggests, all
lanes move at the same spead in steady-currents.json, while they move at
different speeds in variable-currents.json. As a rule, you should always
test your code on steady-currents.json first, as that level is easier to
debug.
Moving the Terrain
Like the value FROG_SPEED, the current is the number of pixels that each
terrain object should move move per second. All objects 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.
The way you move terrain is very similar to the way that you implemented the
move method for your frog. For each lane, take the current speed and multiply
it by dt, the number of seconds since the last animation frame. That is the
number of pixels to move each terrain object horizontally.
Implementing Wrap Around
Eventually the objects 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.
You only want to wrap an object when it goes completely offscreen. So you should wait until the left edge of an object is past the edge of the window for lanes moving the right, and the right edge of an object is past the edge of the window for lanes moving left.
When the object goes completely offscreen, wrapping is super easy. Just update
the position to “teleport” it to the other side of the window. So you add to
position if it goes off to the left, and subtract from the position if it goes
off to the right. You might be tempted to add or subtract the window width.
But if you do that, the object will visibly pop on the screen because the new
x position is inside the window. This can be seen below.
|
| Bad Wrap-Around |
|---|
Instead you want to shift by the width plus GRID_SIZE. This ensures there is
an extra padding of one half of a grid on each side of the window. This leads
to smooth movement as shown below.
|
| Good Wrap-Around |
|---|
Dragging the Frog
A grounded frog should be taken for a ride if it is sitting on a moving Pad
object. That means, for whatever value you added to the x position of the
Pad object, you should also apply that to the x position of the frog.
Because all objects in a lane move at the same speed, it is not a problem if
the frog straddles more than one object. You should also do this for a moving
Exit (that is possible!) if the frog is not entirely inside the exit.
With that said, there is one important caveat. If the terrain drags the frog offscreen, the frog immediately dies. In the frog’s case, we do not need the entire frog to go offscreen. It is enough for either the left or right side of the hitbox to go offscreen. Treat this the same as if the frog had jumped into the water.
Reviewing the Task
Because you made the important changes to the model classes in the last task,
most of this code can go inside of Level. You will need to
- Get the current values from the level JSON.
- Compute the number of pixels to move each lane using the value
dt. - Move each object in the terrain by that amount.
- Wrap-around any objects that go offscreen.
- Move the frog by the current if it is grounded in a lane.
- Kill the frog if it goes offscreen.
Once you complete this, you should be able to test on all of the levels that
we have provided, except malformed.json. Use steady-current.json to
make sure that the wrap-around is happening smoothly and evenly. Take the frog
for a ride and make sure it dies when it goes offscreen.
Pacing Yourself
This is the last tricky part of the assignment and it is why we have left it to the very end. We expect you to finish this (and the next task) on the last day, which is Monday, December 8. Note that this does not give you any time to work on additional features. If you want to do that, you will either need to finish this early or get an extension on the assignment.
14. Support New Levels
If you are going to work on additional features, that means you want to make some new level files. And even if you don’t, it would be cool to see some new levels with just the basic game features. We are not requiring you to make new levels for your game. But we do want you to add some code to make it possible.
The problem is that the your level loading code in Froggit is not particularly
robust. Is uses the json module built into Python. But the functions in that
module module crash if there any any problems with the JSON file. Missing a
comma? The game crashes. Added a comma in the wrong spot? The game crashes.
Used single quotes instead of double quotes for your strings? The game crashes.
We want you to add some error handling to your code. That means putting a
try-except in function you use to load the level. Your code should capture
the error into a variable e as shown in class. It then should use the Kivy
logger to display the error as follows:
Logger.info(type(e).__name__+': '+str(e))Note that Logger is a module imported at the top of app.py. The info
function works like print except that it integrates it into the normal Kivy
messages. That print statement will show the type of error that happened and
the associated error message.
In addition, when a level fails to load, we want to you to change the _text
attribute so that it tells the player that a level failed to load, as shown
below.
|
| A Malformed Level |
|---|
It is likely that you will need multiple lines to show this message if you
intended to display the name of the file. You do not need more than one
GLabel object to do this. You can get more than one line in your label by
putting a '\n' character in your string.
This is a very short task. When you have done all this, try your game on
malformed.json. You should see the image above and the Terminal should display
the error mesage.
[INFO ] [json.decoder.JSONDecodeError] Expecting value: line 323 column 5 (char 8944)
When you seen this, you are done with the basic game.
Additional Features
Our suggested timeline gives you just enough time to work on this assignment. But if you are able to finish early, then we will give you the opportunity to extend the game and try to make it more fun. While this is not required, this is an opportunity for extra credit. We will award up to 8 points for extra credit, according to how interesting your features are. However, you can only go above 100 if your features are truly extraordinary.
When you add new features, you might find yourself reorganizing a lot of the code above. You may add new methods or change any of the methods you have written. For example, you may want to add animations for the frog. You may even want to add new classes for turtles that occasionally dive underneath the water.
You are allowed to change anything you want so long the game 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. One of the nice features of JSON files is that we can add new features to them without breaking our code. The old features are still there and still work. However, for style purposes, we recommend that you change the version number if you start adding elements to a JSON file. This is a way of indicating that it is changed from the original format.
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.
Once again, addition features are simply for extra credit. You can earn full points on this assignment with just the basic game. The purpose of these new features to allow you to earn back any points that you might miss for implementing a feature incorrectly. However, you cannot get back any points lost for writing bad specifications or for violating the 30-line rule, no matter how good your extra features are. And again, your features must be truly exceptional to earn more than 100. You cannot exceed 100 just for implementing our suggestions below.
Possible Features
Here are some possible ways to extend the game, though you should not be constrained by any of them. Make the game you want to make. We will reward originality more than we will reward quantity. While this is a fairly simple game, the design space is wide open with possibilities.
Multiple Levels
The easiest feature to implement is multiple levels. If the player completes a
level without losing all their lives, it is time for a new level. This is
really easy, since all you have to do is make a new Level object. The state
STATE_CONTINUE indicates the transition from one level to another.
For this to count as a proper feature, you need some way to decide which file
to use for the new wave. One way to do this is to add a new entry to the JSON
file. For example, you could have a key "nextwave" which contains the name
of the next JSON file to use. But remember, your game still needs to support
the original JSON files which do not have this key.
In addition, lives should carry over. So if the player has lost the a life, then they should start the next level with one less life in the life bar. When the player runs out of lives, the game is truly over.
Player Score
Frogger is game designed for speed-running. To win the game, you need to reach all the exits, but your score is determined by how fast you get there. If you go this way, you should display the score at all times using a GLabel object. Where you display it is up to you, but you should minimize its interferance with the game itself. We find the left-hand side of the lives bar to give a good place.
As an important note, please do not make a new GLabel object each time the
score changes. This will slow down the program tremendously. Simply change
the text attribute in your GLabel object.
In classic Frogger, this score carries over between levels. If you choose
to implement multiple levels, then each level after the first should start with
the score from the previous level. That would require you to track the score in
Froggit, as that is the only object remains for the duration of the entire
game.
Sound Effects
Another easy feature is to add appropriate sounds for game events. We have provided several audio files with the source code. You will want to look at them, but you are not restricted to only those sounds. Remember that it is a violation of the Academic Integrity Policy to use unlicensed copyrighted material.
To load an audio file, you simply create a Sound object as follows:
croakSound = Sound('croak.wav')Once it is loaded, you can play it whenever you want (such as when the frog
jumps) by calling croakSound.play().
The sound might get monotonous after awhile, so make the sounds vary, and
figure out a way to let the user turn sound off (and on).
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 (such as if
you want other creatures croaking at the same time), 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. If a sound has not loaded properly, there will be noticeable lag the first time that you play it.
Animation
Right now your frog just slides across the screen. It would be nice if the frog visibly jumps like it does in the original video. If to support animations, you can use a sprite. A sprite is a collection of images that you flip through to create an illusion of movement. You got some experience with one of these in the GUI lab, assuming you did the optional activity.
The file frog2.png is a sprite sheet of the frog in different positions.
You will notice this cause the frog to get bigger as it stretches out. But
since the frog is the same size when it is grounded, this will not interfere
with the game at all.
To use this sprite sheet, you need to make a GSprite object as follows:
sprite = GSprite(source='frog2.png',format=(1,5),width=...,height=...)The (1, 5) indicate that the sprite has one rows of frames, broken up into five
columns. The GSprite class has an attribute called frame which tracks which
frame in the sprite sheet is currently displayed. At the start, the frame is
always 0, showing the frog in the grounded position. To show the frog jumping,
simply move forward and backwards between these images.
Submersible Turtles
We have alluded to this feature several times and you can see it in the original video. This is actually a challenge from the original game. Turtles work like logs and lilypads, except they occasionally go under water. If a frog is grounded on a turtle when it goes under, it dies. In addition, the (rectangular) hitbox of the turtle gets smaller when it starts diving. That means that the frog can die if it is not perfectly balanced atop a diving turtle.
Turtles work very differently than anything you have seen so far, and so they need their own class. This can require a quite of bit of extra code, making it much more challenging than the other features.
Your Imagination
What else have you always wanted a game like this to do? Maybe you are inspired by the more modern incarnation of the game, Crossy Road). Maybe you want to add collectibles to increase your score or even give your frog new powers. With that said, do not go too wild with power-ups or collectibles. We much prefer a few innovations that greatly improve the play as opposed to a screen filled with gizmos.
You also are not restricted to the art assets that we have provided. You can add or replace whatever art you want provided that you respect copyright. Please do not use Pokemon characters, inviting Nintendo to sue us.
Again, you can make any modifications to the game you want, but the core gameplay must be there. If we load the game on any of the level files that we have provided, it should play as normal with nothing more than cosmetic differences.
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, add a README file that explains your extensions very clearly. You should also add a README if you decide to use non-standard controls (anything other than the arrow keys).
As part of this assignment, we expect you to follow our style guidelines:
- You have indented with spaces, not tabs (Pulsar 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, and models.py. But in
addition, 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. This is the return of an assignment that we had abandoned because it was too hard. We have tried really hard to make it feasible this time. We are interested in your opinion.
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.



