Organization and Scope
While there are no test cases this time, you should be able to figure
out if everything is working simply by playing the game. There are
no tricky “restore everything to how it was” like with Turtles; no
nasty surprises lurking in the specifications. Just get the game
working.
Assignment Source Code
To work on this assignment, you will need all of the following:
The last item is a link that you should refer to through the
assignment, while the other two are files to download. Only the first
is a must download, as it contains the all of the source code
necessary to complete the assignment.
The second file is a collection of demo code from the lesson
on GUI programming, as well as the lesson on coroutines. This
sample code contains a lot of hints on how to approach some of
the harder parts of this assignment, and we reference these
samples throughout the instructions.
As with the imager application, this assignment is organized as a
package with several files. To run the application, change the
directory in your command shell to just outside of the
folder froggit and type
File Description
froggit.zip The application package, with all the source code
samples.zip Several programs that give hints on this assignment
game2d API The documentation for how to use the game2d classes
python froggit
In this case, Python will run the entire folder. What this really means
is that it runs the script in __main__.py. This script imports each of the
other modules in this folder to create a complex application. To
work properly, the froggit folder should contain the following:
For the most part, you only need to understand the first four files -
app.py, level.py, lanes.py and models.py - as well as the JSON directory.
The other files and folders can be ignored. However, if you decide
to add a few extensions, it helps to understand how all of these fit
together.
app.py
This module contains the controller class Froggit. This is the
controller that launches the application, and is one of four modules
that you must modify for this assignment. While it is the primary
controller class, you will note that it has no script code. That is
File Description
app.py The primary controller class for the application
level.py A secondary controller for a single playable application
lanes.py A collection of mini-controllers for the individual lanes
models.py Model classes for the game (i.e. Frog)
consts.py All module with all of the constant (global variable) values
game2d A package with classes that can display graphics on the screen
Sounds A folder of sound effects approved for your use
Fonts A folder of True Type fonts approved for your use
Images A folder of images for the frog and various obstacles
JSON A folder with different levels you can play
contained in the module __main__.py (which you should not
modify).
level.py
This module contains the secondary controller class Level. This
class manages a single level of the game. It works as a
subcontroller, just like the example subcontroller.py in the provided
sample code. It is another of the four modules that you must
modify for this assignment, and is the one that will require the most
original code.
lanes.py
Levels in Frogger consist of horizontal lanes that the frog has to
traverse. These could be roads, water, or even just a strip of grass.
And each of these lanes is very different from one another. The frog
does not die from touching a road, but will die if it is hit by a car on
the road. On the other hand, the frog dies in the water (it cannot
swim) unless it is touching a log.
This will make the code quite complicated. So to make things
much easier, you will make a class for each type of lane: road,
water, grass and (exit) hedge. Technically these classes will act as
subcontrollers to Level just as Level is a subcontroller for Froggit. In
complex applications there are many layers of controllers.
models.py
This module contains the model class Frog. This class is similar to
those found in the pyro.py demo in the provided sample code. If you
want to add other model classes (e.g. turtles or pick-ups), then you
should add those here as well. This is the last of the four files you
must modify for this assignment.
consts.py
This module is filled with constants (global variables that should
not ever change). It is used
by app.py, level.py, lanes.py and models.py to ensure that these
modules agree on certain important values. It also contains code
for adjusting your default level. You should only modify this file if
you are adding additional features to your program.
game2d
This is a package containing the classes you will use to design
your game. These are classes that you will subclass, just like we
demonstrated in the lesson videos. In particular, the class Froggit is
a subclass of GameApp from this package. As part of this
assignment, you are expected to read the online
documentation which describes how to use the base classes.
Under no circumstances should you ever modify this package!
Sounds
This is a folder of sound effects that you will use in the final
activity. You are also free to add more if you wish; just put them in
this folder. All sounds must be WAV files. While we have gotten
MP3 to work on Windows, Python support for MP3 on MacOS is
unreliable.
Fonts
This is a folder of True Type Fonts, should you get tired of the
default font for this assignment. You can put whatever font you
want in this folder, provided it is a .ttf file. Other Font formats (such
as .ttc, .otf, or .dfont) are not supported. Be very careful with fonts,
however, as they are copyrighted in the same way images are. Do
not assume that you can include any font that you find on your
computer.
Images
This is a folder with image files for the frog and the obstacles.
The GImage and GSprite classes allow you to include these in
your game. You can put other images here if you wish.
JSON
This is a folder with JSON files that define a level. You will turn
these into dictionaries and use them to place objects in your game.
Understanding these files will be a major portion of this
assignment. But you should look at them briefly to familiarize
yourself with these files.
Assignment Scope
As we explained in class, your game is a subclass of GameApp.
The parent class does a lot of work for you. You just need to
implement three main methods. They are as follows:
Your goal is to implement all of these methods according to their
(provided) specification.
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
start()
This method should take the place of __init__. Because of how Kivy
works, initialization code should go here and not in the initializer
(which is called before the window is sized properly).
update(dt)
This method should move the position of everything for just one
animation step, and resolve any collisions (potentially deleting
objects). The speed at which this method is called is determined by
the (immutable) attribute fps, which is set by the constructor. The
parameter dt is time in seconds since the last call to update.
draw()
This method is called as soon as update is complete. Implementing
this method should be as simple as calling the method draw,
inherited from GObject, on each of the models.
These are the only three methods that you need to implement. But
obviously you are not going to put all of your code in those three
methods. The result would be an unreadable mess. An important
part of this assignment is developing new (helper) methods
whenever you need them so that each method is small and
manageable. Your grade will depend partly on the design of your
program. As one guideline, points will be deducted for methods
that are more than 30 lines long (not including specifications or
spacing).
You will also need to add methods and attributes to the
class Level in level.py, the class Frog in models.py and the
classes Lane, Road, Water. and Hedge in lanes.py. All of these classes are
completely empty, though we have given you a lot of hints in the
class specification. You should read all these specifications.
As you write the assignment, you may find that you need additional
attributes. All new instance attributes should be hidden. You should
list these new attributes and their invariants as single-line
comments after the class specification (as we have done this
semester). For example, if the Level class needs to access the exit
locations in a Hedge lane, then you are going to need a getter for
these exits (and we give hints on how to do this).
While documentation and encapsulation is essential for this
assignment, you do not need to enforce any preconditions or
invariants. However, you may find that debugging is a lot simpler if
you do, as it will help you localize errors. But we will not take off
points either way, as long as the game runs correctly.
Assignment Organization
This assignment follows the model-view-controller pattern
discussed in the lesson videos. The modules are clearly organized
so that each holds models, the view, or a controller. The
organization of these files is shown below. The arrows in this
diagram mean “accesses”. So the Froggit controller accesses the
view and Level subcontroller. The Level controller accesses the
lanes, the view, and the models. And Lane and its subclasses
access the view and the modesl.
This leads to an important separation of files. Froggit is never
permitted to access anything in models.py and lanes.py. Level is never
permitted to access anything in app.py. The classes in lanes.py may
not access anythign in app.py or level.py. And finally, models.py cannot
access anything other than the view. This is an important rule that
we will enforce while grading. All of this is shown in the diagram
below
The Import Relationships
In addition to the four main modules, there is another module with
no class or function definitions. It only has constants, which are
global variables that do not change. This is imported by
the models module and the various controllers. It is a way to help
them share information.
When approaching this assignment, you should always be thinking
about “what code goes where?” If you do not know what file to put
things in, please ask on Piazza (but do not post code). Here are
some rough guidelines.
Froggit
This controller does very little. All it does is keep track of the game
state (e.g. whether or not the game is paused). Most of the time it
just calls the methods in Level, and lets Level do all the work.
However, if you need anything between lives, like a paused
message or the final result, this goes here. This is similar to the
class MainApp from the demo subcontroller.py
Level
This class does all the hard work. In addition to the initializer (which
is a proper __init__, not start), it needs its
own update and draw methods. This is a subcontroller, and you
should use the demo subcontroller.py (in the provided sample code)
as a template.
The most complex method will be the update and you will certainly
violate the 30-line rule if you do not break it up into helpers. For the
basic game, this method will need to do the following:
• Animate the frog according to player input
• Move all the obstacles in each lane
• Detect any collisions between the frog and an obstacle
• Remove the frog after a death or a successful exit
In our code, each one of these is a separate helper (though the
second one will actually be managed by the Lane class below). You
should think about doing this in your code as well.
The Lanes
There are four types of lanes in the game: road, water, grass, and
the hedge. The hedge is where the exits are placed. The road has
cars (and trucks). The water has logs to jump on. The grass is a
safe zone that gives you a rest.
Because each lane acts differently, you want to make a separate
class for each one. However, they do have a lot in common, so
they will all be subclasses of the class Lane. These classes already
appear in lanes.py with their specifications.
Each lane is essentially a mini-level. The frog needs to get
succesfully past it. That is why each lane will be its own
subcontroller, controlling its own lane. As a subcontroller, each lane
should have its own __init__, update, and draw methods. However,
most of these will go in Lane and the other classes will safely inherit
them. When the Level class needs to update the lanes, all it will do
is loop through the lanes and call the update method for each one.
The Models
The models just keep track of data. Most of the time, models just
have attributes, with getters and setters. Think Image from the
previous assignment. However, sometimes models have additional
methods that perform computation on the data, like swapPixel.
The models in this assignment are the game objects on screen: the
frog and any obstacles. Most of the time, you do not need a new
class for a module, as the class GImage does everything that you
needs for the cars, logs, and exits. However, the frog is very special
and so the frog will need a custom class. We have written the
specification of this for you.
If you want to add any additional features to your game, you may
need to add some new models here. For example, turtles that will
occasionally dive under water have to be animated in the same
way that the frog does. So they will need their own classes for
exactly the same reason. You should only add new classes for the
additional features.
Suggested Micro-Deadlines
You should 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.
Overview of Froggit
The layout of a Froggit game depends on the level file you are
using. We have included many different level files in
the JSON directory. The files easy1.json and easy2.json are easy games
to help you test your game, while roadsonly.json and complete.json are
a little more challenging. The level that you use is defined by the
variable DEFAULT_LEVEL in const.py. You can also change the level at
any time by specifying it when you run the game. For example, if
you type
python froggit roadsonly.json
it will play the game with the level roadsonly.json as the DEFAULT_LEVEL.
Below is the set-up for the complete.json to test out your game in the
end.
The Starting Position
All of the levels are arranged as a grid, where each grid square
is GRID_SIZE pixels on each side. The lanes are one grid unit in
height, and several grid units in width. Each exit is one grid unit
wide. Cars or logs can take up multiple grid squares, depending on
the specific obstacle. This grid is not explicitly visible, but it
becomes obvious once you play the game for a bit.
The Invisible Grid
Once the game begins, the cars and logs move across the screen.
They go in different directions, specified by the 'speed' key in the
JSON dictionary. Once they go off the screen, they wrap back
around to the other side. How fast they wrap around is defined by
the 'offscreen' key in the JSON dictionary.
The frog has to safely make it to (i.e. touch) one of the exits, which
is a lily pad at the final hedge. When that happens, the game puts a
bluish frog on the lily pad and restarts another frog at the starting
point, as show below. Exits may not be reused. The second frog
cannot use the exit that currently has a frog on it.
At the First Exit
If a frog dies, the game pauses and deducts a life from the player.
These lives are shown by the frog heads in the top right corner. In
the picture below, the game is paused after losing the first life.
Down One Life
There are two ways for the frog to die. The frog dies if it is hit by (or
even touches) a car. The frog also dies if touches the water. Why
this is a frog that cannot swim is a mystery from the early days of
video games. To survive on the water, the frog must hop on a log.
But the logs carry the frog with it when they move, making the
game a little more challenging.
Once you understand those rules, the game is simple. You win by
filling up every lily pad at the hedge. You lose if you run out of lives
before this happens. In the video below, we show both outcomes
on the level easy2.json.
Game State
One of the challenges with making an application like this is
keeping track of the game state. In the description above, we can
identity several distinct phases of the game:
• Before the game starts, but no level has been selected
• When the level has been loaded, but the frog and obstacles
are not moving
• While the game is ongoing, and the obstacles are moving
onscreen
• While the game is paused (e.g. to show a message)
• While the game is creating a new frog to replace the old one
• After the game is over
Keeping these phases straight is an important part of implementing
the game. You need this information to
implement update in Froggit correctly. For example, whenever the
game is ongoing, the method update should instruct the Level object
to move the frog. However, if the game has just started, there is
no Level object yet, and the method update should create one.
For your convenience, we have provided you with constants for six
states:
• STATE_INACTIVE, before a level has started
• STATE_LOADING, when it is time to load the level file
• STATE_ACTIVE, when the game is ongoing and the obstacles are
moving
• STATE_PAUSED, when the game is paused to display a message
• STATE_CONTINUE, when the player is waiting for a new frog
• STATE_COMPLETE, when the game is over
All of these constants are available in consts.py. The current
application state should be stored in a hidden
attribute _state inside Froggit. You are free to add more states if you
add extensions. However, your basic game should stick to these
six states.
The rules for changing between these six states are outlined in the
specification of method update in Froggit. You should read that in its
entirety. However, we will cover these rules in the instructions
below as well.
Level JSONs
All of the information about a game level is stored in a JSON file.
You should remember what a JSON string is from the very first
assignment. A JSON file is just a file that stores a very large JSON
string. These JSON files are a lightweight way to store information
as nested dictionaries, which is the standard way of sending
complex data across the Internet.
The tricky part about JSONs is coverting the string (or file) into the
Python dictionary. Fortunately, we have provided a tool that makes
this part easy. The GameApp class includes a method
called load_json which your Froggit class will inherit. Simply specify
the name of the JSON file. As long as that file is stored in
the JSON directory, this method will load this file and convert to ta
Python dictionary for you.
This means that working with level files is really all about working
with a complex nested dictionary. To understand how these
dictionaries work, look at the file easy2.json. This includes all of the
features that you will need to implement for the basic game. The
top-level dictionary has five keys:
• 'version' : A float representing the level version.
This version is only relevant if you define your own levels. It is
discussed in the section on additional features.
• 'size' : A list of two integers for the width and height.
The size represents the size of your game in grid squares, not
pixels. You multiply these by GRID_SIZE to get the size of the game.
• 'start' : A list of two integers for an x and y position.
The start value is the starting grid square for the frog (grid squares
start at 0 like any list). You multiply its two integers by GRID_SIZE to
get the starting pixel position for the frog.
• 'offscreen' : An integer to support “movement wrap”.
Objects need to wrap back to the beginning once they go off
screen. But you do not want to do that immediately, as the images
will snap and flicker. You want them to go completely offscreen
before you wrap them. This value is how far (in grid squares) that
any image must be offscreen before it is time to wrap it back
around. This is discussed in the activity to move the obstacles.
• 'lanes' : A list of all the lanes in the level.
The list of lanes should have the same number of elements as the
(grid) height of the level. The are ordered bottom up. The first lane
in the list is the bottom row, and the last lane in the list is the top
one.
The real nesting happens in these lanes. Each lane is its own
dictionary, with up to three keys. These are as follows:
• 'type' : The lane type.
The lane type is a string, and is one
of 'grass’, 'road', 'water' and 'hedge'. Unless you add new types of
lanes to the game, there are no other possibilities.
• 'speed' : A float indicating how fast obstacles move in this
lane.
All obstacles in a lane move at the same pace. This is the number
of pixels they move per second. Movement is left-to-right. If the
speed is negative, then the movement is right-to-left.
• 'objects' : The list of obstacles in this lane.
The exact obstacles vary by lane type. For a road, these are the
cars. For a water lane, these are the logs. For the hedge, these are
the exits (which technically count as obstacles).
Not all lanes have all three keys. Grass has no obstacles. The
hedge has exits, but they do not move and so they have no speed.
However, every lane is guaranteed to have a 'type', and it is
guaranteed to be one of those four values.
Finally, there are the obstacles in the list for 'objects'. All obstacles
have two keys:
• 'type' : A string representing the obstacle type.
For this assignment, it is the image file (minus the '.png’ suffix) for
this obstacle. So type 'log2' corresponds to the image file 'log2.png'
• 'position' : The float for the obstacle position.
Technically, the position represents a grid square, so you multiply it
by GRID_SIZE to get the position of the obstacle. However, obstacles
are different from the frog in the fact that they can actually be
between two grid squares (which is why this is a float).
If you understand all of these features, then you will have no
problem completing this assignment.
JSON Assumptions
One of the preconditions for this assignment is that level files are
properly formatted. That is, they are proper JSONs and they do not
have any important keys missing. As we said above, it is okay for
some keys to be missing, like 'speed' or 'objects'. But other keys
like 'size' and 'start' absolutely have to be there.
It is not your responsibility to enforce that the JSON files are in
the correct format. That is the responsibility of the level designer,
who is typically a different person on the team than a programmer.
However if you add any extensions to the game, then you will
likely be designing your own level files. And if you make mistakes in
your level files, you may cause your program to crash. Again, this is
a problem with the level file and not the game itself.
Task 1: Setup
We have divided these instructions into three parts. This task
involves setting up the game. That includes loading a level file and
using it to draw objects on the screen. While you will be able to
move the frog when you are done, nothing else will work. No
obstacles will move and you cannot win or lose the game.
We have made all of the instructions in this section very explicit. As
we move on to later tasks, the instructions will become a little more
vague.
Review the Constants
The very first thing that you should do is read the file consts.py. If
you ever need a value like the size of the grid, the initial size of the
game window, the frog movement speed, or so on, this is where
you go. When writing code, you should always use the constants,
not raw numbers (or “magic numbers,” as we call them). Magic
numbers make your code hard to debug, and if you make a change
(e.g. to make the grid size larger), you have no idea about all of the
locations in your code that need to be changed.
With that said, you are welcome to change any of these numbers if
you wish. You are also encouraged to add more constants if you
think of other numeric values that you need. Anytime that you find
yourself putting a number in your code, ask yourself whether or not
it would make sense as a constant.
Create a Welcome Screen
We start with a simple warm-up to get you used to defining state
and drawing graphics elements. When the player starts the
application, they should be greeted by a welcome screen. Your
initial welcome screen should start with two lines of text.
Because the welcome message is before any game has started, it
belongs in the Froggit class, not the Level class. You are already
seeing how we separate what goes where.
The welcomes will look something like the one above. It should tell
the player the name of the game, and tell the player to “Press ‘S’ to
Start”. You can change the wording here if you want. It could say
something else, as long as it is clear that the user should press a
key on the keyboard to continue. However, we recommend against
allowing the user to press any key, since in later steps that will
make it easy for the user to accidentally miss an important
message (or jump immediately in front of a truck).
To create a text message, you need to create a GLabel and store in
it an attribute. If you read the class invariant for Froggit, you will see
two attributes named _title and _text. The title attribute is the logo
of the game. The text attribute is for any messages to display to
the player. The text is typically smaller, like a footnote. While we will
never show the logo after the initial state STATE_INACTIVE, we will use
the text attribute for messages throughout the game.
Since the welcome message should appear as soon as you start
the game, it should be created in the method start, the first
important method of the class Froggit. When creating your
message, you will want to set things like the font size and position
of the text. If you are unsure of how to do this, look at the
class MainApp from the demo subcontroller.py in the provided sample
code.
As you can see from the documentation for GLabel and GObject,
graphics objects have a lot of attributes to specify things such as
position, size, color, font style, and so on. You should experiment
with these attributes to get the welcome screen that you want.
The first thing to understand is the positioning. In Kivy, the screen
origin (0,0) is at the bottom-left corner, and not the center like it
was for the Turtle assignment. If you want to find the center of the
window (our version centers these on the screen), the Froggit object
has attributes width and height that store the size of the window. In
addition, for the label object, the attributes x and y store
the center of the label. So you can center the label horizontally by
assigning x to width/2.
When placing label objects, you do not always want to center
them. Sometimes you would like the label to be flush againt the
edge of the window. For that reason we have attributes
like left, right, top, and bottom. These are alternate attributes
for x and y. They move the label in much the same way, but give
you a little more control over the positioning.
One you understand how to position the label, it is time to think
about your font choice and style. If you want to look exactlty like
the picture above, we have some constants to help you in const.py.
The font is ALLOY_FONT, which is a reference to AlloyInk). The title has
size ALLOY_LARGE while the message has ALLOY_MEDIUM. However, you are
not constrained to these choices. You may chose a different font or
font size if you wish, so long as it fits on the screen.
Drawing the Welcome Message
Simply adding this code to start is not enough. If you were to run
the application right now, all you would see is a blank white
window. You have to tell Python what to draw. To do this, simply
add the lines
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 we have not done
anything yet.
The _state attribute is an important part of many of the invariants in
this game. In particular, we want your new attribute for the
welcome message to have the following invariant:
• If the state is STATE_INACTIVE, then there is a welcome message
with title and text.
• If the state is not STATE_INACTIVE, the _title attribute is None.
• If the state is STATE_ACTIVE, the _text attribute is None.
Does your start() method satisfy this invariant? Note the difference
between the last two invariants. That will become important later.
Dismissing the Welcome Screen
The welcome screen should not show up forever. The player should
be able to dismiss the welcome screen (and start a new game)
when he or she presses the S key. To respond to keyboard events,
you will need the attribute input, which is an instance
of GInput.This class has several methods for identifying what keys
are currently pressed.
When using the attribute input, remember the issues that we
discussed in class. The method update(dt) is called every 16
milliseconds. If you hold a key down, then you see a lot of key
presses. You just want the first press! That means you need some
way to determine whether or not the key was pressed this
animation frame and not in the previous one. See the state.py demo
from the sample code for some ideas on how to do this. This may
require you to add a new attribute to Froggit.
If you detect a key press, then you should change the
state STATE_INACTIVE to STATE_LOADING. This will load a level file and start
a new game. You are not ready to actually write the code to start
the game, but switching states is an important first activity.
Invariants must be satisfied at the end of every method, so you
need to assign None to both _title and _text now. This will require a
simple change to method draw() to keep it from crashing (you
cannot draw None). Once you have done that, run the application.
Does the message disappear when you press a key?
Documenting your New Attributes
When working on the steps above, you may have needed to add
new attributes beyond the ones that we have provided. Whenever
you a new attribute, you must add it and its corresponding
inva