A7: Froggit

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

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 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:

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.

mvc
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 on
  • Hedge: This terrain will block the frog, but not kill it
  • Exit: 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.json

it 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.

easy 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.

easy level
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
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.

first exit
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.

first death
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 started
  • STATE_LOADING, when it is time to load the level file
  • STATE_ACTIVE, when the game is ongoing and the terrain is 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 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.

welcome message
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 _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 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

  1. Create an attribute for the title and assign it a GLabel
  2. Create an attribute for the message and assign it a GLabel
  3. Create an atttribute for the state and assign it to STATE_INACTIVE

In the method update you should

  1. Check if the start key was pressed
  2. Change the state to STATE_LOADING if the key was pressed
  3. Ensure the application invariants are satisfied

Finally, in the method draw you should

  1. Draw the title if it is not None
  2. 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 levelname

So you can load the large.json level simply by typing

  python froggit large.json

Resizing the Window

Different level files are different sizes. When you load a level, you want to resize the window to match the level. The window width should be the level width times GRID_SIZE. The window height should be one grid size higher than the level height. That is because we need an extra grid square to display the remaining lives.

To resize the window, remember that the Froggit object has mutable attributes width and height. Just assign the value to those. If you do it right, the 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.

stretched hedge tiled hedge
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.

water
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

  1. Get the JSON dictionary from Froggit as a parameter
  2. Extract the "size" from this dictionary
  3. Create an attribute _water for the Water (GTile)
  4. Assign the water source attribute as WATER_IMAGE
  5. Size the water to match the "size" value
  6. 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

  1. Check if the state is STATE_LOADING (if not, skip the steps below)
  2. Load the DEFAULT_LEVEL as a dictionary using the method load_json
  3. Create a Level object, passing the dictionary as an argument
  4. 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

  1. Creating an initializer in the class Frog
  2. Constructing a frog object in class Level
  3. 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

  1. Creating an initializer in the classes Pad, Hedge, and Exit
  2. Constructing a 2-dimensional list _lanes in class Level
  3. Drawing the contents of _lanes to 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.

easy terrain
Terrain for easy.json
hedge terrain
Terrain for hedges.json
large terrain
Terrain for large.json
medium terrain
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:

lives counter
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.

interpolation
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.

  1. Add attributes for the ground state, takeoff, landing, and time in the air
  2. Add a jump method to start a jump (but not move the frog)
  3. 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

  1. Check if the frog is grounded
  2. Start a jump if the frog is grounded and a key is pressed
  3. Move the frog by dt if 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

  1. Is the jump offscreen?
  2. 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
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

  1. Check if the frog is in the air
  2. Check if there is a Pad or Exit object 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.

  1. Check if the frog has died
  2. Switch to STATE_PAUSED and display a message if so
  3. Switch back to STATE_CONTINUE when the player presses a key
  4. Reset the game in STATE_CONTINUE and switch back to STATE_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
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:

  1. Switch to STATE_COMPLETE if no lives are left
  2. Display a special message if you are in STATE_COMPLETE
  3. 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:

  1. Add an attribute (with getters/setters) to mark an exit as occupied
  2. Modify the draw method to add a blue frog if an exit is occupied
  3. 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

  1. Mark that exit occupied
  2. Remove the frog, signaling to Froggit to 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
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.

offset level
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.

hit box
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.

collisions
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.

  1. Modify the hedge blocking code to include the hitbox
  2. Modify the exit detection code to include the hitbox
  3. 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
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
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

  1. Get the current values from the level JSON.
  2. Compute the number of pixels to move each lane using the value dt.
  3. Move each object in the terrain by that amount.
  4. Wrap-around any objects that go offscreen.
  5. Move the frog by the current if it is grounded in a lane.
  6. 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.

corrupted level
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.