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 assignemnt will be a Frogger clone. This is a brand new assignment which includes a lot of the material that was introduced this year for the first time.

If you have never played Frogger before (or its modern equivalent Crossy Road), there are a few versions online as Flash games (though Flash is going away soon). You can try out either Frogger Classic or HappyHopper. Either will give you a good feel of the game. Our assignment shown above is a simplified version of the game with no submerging turtles or pick-ups. However, you can add these as extensions at the end if you wish.

This assignment is long - much longer than anything that you have written before. Our final A-level solution to this assignment required us to write 915 lines of code and specifications. However, as always, specifications and class/method headers were the bulk of what we wrote and it was only 390 lines of code without them. The solution for the A/B border appears to be about 310 lines of code without specifications, and the B/C border is about 175 lines of code without specifications. This assignment is designed with the expectation that not everyone in this course will finish it. That is why we are comfortable replacing the final with this assignment (though you should pay close attention to the Academic Integrity policy on this assignment).

Because we do not expect everyone to finish this assignment, we have made the grade boundaries explicit in these instructions. Every single task has a letter grade attached to it. If you make it that far with no bugs (which is a big if), then that is the grade you will earn for your work. Unlike the prelims, we do not want this grade to be a surprise. With that said, we honestly believe that everyone in this class can earn at least a B on this assignment and the majority of you should be able to earn an A. The trick is to start early, break the problem up into manageable pieces, and program/test incrementally. From our experience on similar assignments, we are assuming that the average time to complete this is a little over 20 hours, with many students going to 30 hours. That means if you work on this assignment for either an hour or an hour-and-a-half every day, you should be able to finish it all.

In the past, we have made the game a lot more open-ended than this, with much less structure. While we need the structure to replace the final, there is an opportunity for you to let your creativity shine in the extensions. We will award a limited amount of extra credit (though never more than half a letter grade) for particularly interesting additional features.

Author: W. White

Learning Objectives

This final assignment has several important objectives:

  • It gives you practice reading official class documentation and APIs.
  • It gives you experience with a complex, interactive application.
  • It gives you experience designing helper functions to structure your code properly.
  • It gives you experience using constants to make your code more readable.
  • It gives you experience with programming animation using coroutines.
  • It gives you experience with an open-ended project that is not fully specified.

Table of Contents


Academic Integrity

Collaboration Policy

Because of COVID, we are using this project to replace our final exam. But this means that we have to treat this differently from normal assignments. The reason we have exams is so that we can assess your ability with material without getting help. And that is how this assignment has to be as well. You must work on this assignment alone. If that means you are not going to be able to finish the assignment, understand that this is one of the design decisions of replacing an exam with an assignment.

With that said, we know that people like to show off their work for this project. So you are allowed to show off videos of your game, or allow other people to play your game in a controlled setting. But you may not show anyone your code until after the submission deadline. Be very careful with your code, as someone may copy it.

Because of the huge number of lines of code in this assignment, it is trivially easy for us to discover when someone copies code from another person. Everyone’s code is going to be radically different, and so we will be able to prosecute violations (that hold up under appeal) on as little as 5 lines of code. We will catch at least 99% of all violations on this assignment (and we expect there to be a few).

Because this assignment is replacing the final, the penalties for an academic integrity violation will be severe. It will not be a question of whether or not you fail the assignment. Many violations will result in failure for the entire course. Do not copy code from other students or online resources. Do not hire people to complete your assignment for you. Do not post your code online or send it to your friends (because you will discover that your so-called-friends will copy it). We have seen it all and caught it all. Do not do it.

Copyrighted Material

There is another Academic Integrity issue with this assignment: copyrighted material. Gameplay cannot be copyrighted. You can make a game that plays the same as another. This established in the early days of gaming when Space Invaders lost its court case against Galaxian and Galaga. However, artwork in a game is copyrighted (and in the case of Space Invaders, even trademarked). So you should be careful about adding additional artwork to this game.

While there might be an argument for fair use – this is a class project – your instructor prefers that you avoid the copyright issue entirely. In general, you are only allowed to use copyrighted material if you have a license to do so. For example, many of the songs and sound effects in the NewGrounds library are available for you to use under an Attribution License. That means you are free to use it so long as you cite the source in your documentation (e.g. your header comments). This is okay. A license where you have to pay is not okay.

If you are in doubt as to whether you have a license to use something, ask us on Piazza.

Office Hours Policy

POLICY REVISION: The policy below now only applies to Tasks 2 and 3. You may get normal assignment help on Task 1.

This assignment is replacing an exam. That means we have to be fair about how we help students on this assignment. We cannot allow someone to get a grade that they do not deserve simply because they camped out in office hours and had a TA or consultant pull them through the assignment.

The rules for this assignment will be exactly the same as they were for the function pixellate on the last assignment. That is, we will be applying the no code rule. When we said that you may not show your code to anyone, that includes the instructors or the course staff. We mean it. We will gladly look at error message. And if you want to share a Zoom video of you playing your game, we will look at that. But you may not show us code unless we explicitly ask for it, and we will only do this in highly unusual circumstances (typically related to a bug in the game2d module).

This can be a little scary, because there is a lot of new material in this assignment, particularly with animation and graphical applications. Fortunately, all of the final labs in this course will be devoted to this assignment. And the labs have no academic integrity restrictions. You can talk about them and show your code to the instructors or even other students.

If you are really stuck, talk to a staff member, but do not show your code. We may be able to come up with a supportive lab activity (solving a different but similar problem) to help you.


Organization and Scope

This is assignment is even longer than Assignment 6. As with that assignment, the trick is to pacing yourself. This assignment has three full weeks, and this is more than enough time if you work on a little bit every day. In fact, we have designed this assignment so that the average person needs between an hour to an hour-and-a-half each day to get the highest score.

While there are no test cases this time, you should be able to figure out if everything is working simply by playing the game. There are no tricky “restore everything to how it was” like with Turtles; no nasty surprises lurking in the specifications. Just get the game working.

Assignment Source Code

To work on this assignment, you will need all of the following:

File Description
froggit.zip The application package, with all the source code
samples.zip Several programs that give hints on this assignment
game2d API The documentation for how to use the game2d classes

The last item is a link that you should refer to through the assignment, while the other two are files to download. Only the first is a must download, as it contains the all of the source code necessary to complete the assignment.

The second file is a collection of demo code from the lesson on GUI programming, as well as the lesson on coroutines. This sample code contains a lot of hints on how to approach some of the harder parts of this assignment, and we reference these samples throughout the instructions.

As with the imager application, this assignment is organized as a package with several files. To run the application, change the directory in your command shell to just outside of the folder froggit and type

  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 playable application
lanes.py A collection of mini-controllers for the individual lanes
models.py Model classes for the game (i.e. Frog)
consts.py All module with all of the constant (global variable) values
game2d A package with classes that can display graphics on the screen
Sounds A folder of sound effects approved for your use
Fonts A folder of True Type fonts approved for your use
Images A folder of images for the frog and various obstacles
JSON A folder with different levels you can play

For the most part, you only need to understand the first four files - app.py, level.py, lanes.py and models.py - as well as the JSON directory. The other files and folders can be ignored. However, if you decide to add a few extensions, it helps to understand how all of these fit together.

app.py

This module contains the controller class Froggit. This is the controller that launches the application, and is one of four modules that you must modify for this assignment. While it is the primary controller class, you will note that it has no script code. That is contained in the module __main__.py (which you should not modify).

level.py

This module contains the secondary controller class Level. This class manages a single level of the game. It works as a subcontroller, just like the example subcontroller.py in the provided sample code. It is another of the four modules that you must modify for this assignment, and is the one that will require the most original code.

lanes.py

Levels in Frogger consist of horizontal lanes that the frog has to traverse. These could be roads, water, or even just a strip of grass. And each of these lanes is very different from one another. The frog does not die from touching a road, but will die if it is hit by a car on the road. On the other hand, the frog dies in the water (it cannot swim) unless it is touching a log.

This will make the code quite complicated. So to make things much easier, you will make a class for each type of lane: road, water, grass and (exit) hedge. Technically these classes will act as subcontrollers to Level just as Level is a subcontroller for Froggit. In complex applications there are many layers of controllers.

models.py

This module contains the model class Frog. This class is similar to those found in the pyro.py demo in the provided sample code. If you want to add other model classes (e.g. turtles or pick-ups), then you should add those here as well. This is the last of the four files you must modify for this assignment.

consts.py

This module is filled with constants (global variables that should not ever change). It is used by app.py, level.py, lanes.py and models.py to ensure that these modules agree on certain important values. It also contains code for adjusting your default level. You should only modify this file if you are adding additional features to your program.

game2d

This is a package containing the classes you will use to design your game. These are classes that you will subclass, just like we demonstrated in the lesson videos. In particular, the class Froggit is a subclass of GameApp from this package. As part of this assignment, you are expected to read the online documentation which describes how to use the base classes.

Under no circumstances should you ever modify this package!

Sounds

This is a folder of sound effects that you will use in the final activity. You are also free to add more if you wish; just put them in this folder. All sounds must be WAV files. While we have gotten MP3 to work on Windows, Python support for MP3 on MacOS is unreliable.

Fonts

This is a folder of True Type Fonts, should you get tired of the default font for this assignment. You can put whatever font you want in this folder, provided it is a .ttf file. Other Font formats (such as .ttc, .otf, or .dfont) are not supported. Be very careful with fonts, however, as they are copyrighted in the same way images are. Do not assume that you can include any font that you find on your computer.

Images

This is a folder with image files for the frog and the obstacles. The GImage and GSprite classes allow you to include these in your game. You can put other images here if you wish.

JSON

This is a folder with JSON files that define a level. You will turn these into dictionaries and use them to place objects in your game. Understanding these files will be a major portion of this assignment. But you should look at them briefly to familiarize yourself with these files.

Assignment Scope

As we explained in class, your game is a subclass of GameApp. The parent class does a lot of work for you. You just need to implement three main methods. They are as follows:

Method Description
start() Method to initialize the game state attributes
update(dt) Method to update the models for the next animation frame
draw() Method to draw all models to the screen

Your goal is to implement all of these methods according to their (provided) specification.

start()

This method should take the place of __init__. Because of how Kivy works, initialization code should go here and not in the initializer (which is called before the window is sized properly).

update(dt)

This method should move the position of everything for just one animation step, and resolve any collisions (potentially deleting objects). The speed at which this method is called is determined by the (immutable) attribute fps, which is set by the constructor. The parameter dt is time in seconds since the last call to update.

draw()

This method is called as soon as update is complete. Implementing this method should be as simple as calling the method draw, inherited from GObject, on each of the models.


These are the only three methods that you need to implement. But obviously you are not going to put all of your code in those three methods. The result would be an unreadable mess. An important part of this assignment is developing new (helper) methods whenever you need them so that each method is small and manageable. Your grade will depend partly on the design of your program. As one guideline, points will be deducted for methods that are more than 30 lines long (not including specifications or spacing).

You will also need to add methods and attributes to the class Level in level.py, the class Frog in models.py and the classes Lane, Road, Water. and Hedge in lanes.py. All of these classes are completely empty, though we have given you a lot of hints in the class specification. You should read all these specifications.

As you write the assignment, you may find that you need additional attributes. All new instance attributes should be hidden. You should list these new attributes and their invariants as single-line comments after the class specification (as we have done this semester). For example, if the Level class needs to access the exit locations in a Hedge lane, then you are going to need a getter for these exits (and we give hints on how to do this).

While documentation and encapsulation is essential for this assignment, you do not need to enforce any preconditions or invariants. However, you may find that debugging is a lot simpler if you do, as it will help you localize errors. But we will not take off points either way, as long as the game runs correctly.

Assignment Organization

This assignment follows the model-view-controller pattern discussed in the lesson videos. The modules are clearly organized so that each holds models, the view, or a controller. The organization of these files is shown below. The arrows in this diagram mean “accesses”. So the Froggit controller accesses the view and Level subcontroller. The Level controller accesses the lanes, the view, and the models. And Lane and its subclasses access the view and the modesl.

This leads to an important separation of files. Froggit is never permitted to access anything in models.py and lanes.py. Level is never permitted to access anything in app.py. The classes in lanes.py may not access anythign in app.py or level.py. And finally, models.py cannot access anything other than the view. This is an important rule that we will enforce while grading. All of this is shown in the diagram below

mvc The Import Relationships

In addition to the four main modules, there is another module with no class or function definitions. It only has constants, which are global variables that do not change. This is imported by the models module and the various controllers. It is a way to help them share information.

When approaching this assignment, you should always be thinking about “what code goes where?” If you do not know what file to put things in, please ask on Piazza (but do not post code). Here are some rough guidelines.

Froggit

This controller does very little. All it does is keep track of the game state (e.g. whether or not the game is paused). Most of the time it just calls the methods in Level, and lets Level do all the work. However, if you need anything between lives, like a paused message or the final result, this goes here. This is similar to the class MainApp from the demo subcontroller.py

Level

This class does all the hard work. In addition to the initializer (which is a proper __init__, not start), it needs its own update and draw methods. This is a subcontroller, and you should use the demo subcontroller.py (in the provided sample code) as a template.

The most complex method will be the update and you will certainly violate the 30-line rule if you do not break it up into helpers. For the basic game, this method will need to do the following:

  • Animate the frog according to player input
  • Move all the obstacles in each lane
  • Detect any collisions between the frog and an obstacle
  • Remove the frog after a death or a successful exit

In our code, each one of these is a separate helper (though the second one will actually be managed by the Lane class below). You should think about doing this in your code as well.

The Lanes

There are four types of lanes in the game: road, water, grass, and the hedge. The hedge is where the exits are placed. The road has cars (and trucks). The water has logs to jump on. The grass is a safe zone that gives you a rest.

Because each lane acts differently, you want to make a separate class for each one. However, they do have a lot in common, so they will all be subclasses of the class Lane. These classes already appear in lanes.py with their specifications.

Each lane is essentially a mini-level. The frog needs to get succesfully past it. That is why each lane will be its own subcontroller, controlling its own lane. As a subcontroller, each lane should have its own __init__, update, and draw methods. However, most of these will go in Lane and the other classes will safely inherit them. When the Level class needs to update the lanes, all it will do is loop through the lanes and call the update method for each one.

The Models

The models just keep track of data. Most of the time, models just have attributes, with getters and setters. Think Image from the previous assignment. However, sometimes models have additional methods that perform computation on the data, like swapPixel.

The models in this assignment are the game objects on screen: the frog and any obstacles. Most of the time, you do not need a new class for a module, as the class GImage does everything that you needs for the cars, logs, and exits. However, the frog is very special and so the frog will need a custom class. We have written the specification of this for you.

If you want to add any additional features to your game, you may need to add some new models here. For example, turtles that will occasionally dive under water have to be animated in the same way that the frog does. So they will need their own classes for exactly the same reason. You should only add new classes for the additional features.

Suggested Micro-Deadlines

You should start on this assignment soon as possible. If you wait until the week before this assignment is due, you will have a hard time completing it. If you work on a little bit of it every day or every other day, then you will enjoy it and get it done on time.

You should implement the application in stages, as described in these instructions. Do not try to get everything working all at once. Make sure that each stage is working before moving on to the next stage.

Set up a schedule. We have suggested some milestones, but make up your own schedule. This schedule assumes that you are working on the assignment one or two hours every day. An average student should be able to complete this in one hour a day, though two hours will give you more time for hunting down bugs. But more importantly, you need to leave time for learning things and asking questions. Above all, do not try to add extensions until you at least finish the basic game. If you add extensions too early, debugging may get very difficult.


Overview of Froggit

The layout of a Froggit game depends on the level file you are using. We have included many different level files in the JSON directory. The files easy1.json and easy2.json are easy games to help you test your game, while roadsonly.json and complete.json are a little more challenging. The level that you use is defined by the variable DEFAULT_LEVEL in const.py. You can also change the level at any time by specifying it when you run the game. For example, if you type

  python froggit roadsonly.json

it will play the game with the level roadsonly.json as the DEFAULT_LEVEL. Below is the set-up for the complete.json to test out your game in the end.


The Starting Position

All of the levels are arranged as a grid, where each grid square is GRID_SIZE pixels on each side. The lanes are one grid unit in height, and several grid units in width. Each exit is one grid unit wide. Cars or logs can take up multiple grid squares, depending on the specific obstacle. This grid is not explicitly visible, but it becomes obvious once you play the game for a bit.


The Invisible Grid

Once the game begins, the cars and logs move across the screen. They go in different directions, specified by the 'speed' key in the JSON dictionary. Once they go off the screen, they wrap back around to the other side. How fast they wrap around is defined by the 'offscreen' key in the JSON dictionary.

The frog has to safely make it to (i.e. touch) one of the exits, which is a lily pad at the final hedge. When that happens, the game puts a bluish frog on the lily pad and restarts another frog at the starting point, as show below. Exits may not be reused. The second frog cannot use the exit that currently has a frog on it.


At the First Exit

If a frog dies, the game pauses and deducts a life from the player. These lives are shown by the frog heads in the top right corner. In the picture below, the game is paused after losing the first life.


Down One Life

There are two ways for the frog to die. The frog dies if it is hit by (or even touches) a car. The frog also dies if touches the water. Why this is a frog that cannot swim is a mystery from the early days of video games. To survive on the water, the frog must hop on a log. But the logs carry the frog with it when they move, making the game a little more challenging.

Once you understand those rules, the game is simple. You win by filling up every lily pad at the hedge. You lose if you run out of lives before this happens. In the video below, we show both outcomes on the level easy2.json.


Game State

One of the challenges with making an application like this is keeping track of the game state. In the description above, we can identity several distinct phases of the game:

  • Before the game starts, but no level has been selected
  • When the level has been loaded, but the frog and obstacles are not moving
  • While the game is ongoing, and the obstacles are moving onscreen
  • While the game is paused (e.g. to show a message)
  • While the game is creating a new frog to replace the old one
  • After the game is over

Keeping these phases straight is an important part of implementing the game. You need this information to implement update in Froggit correctly. For example, whenever the game is ongoing, the method update should instruct the Level object to move the frog. However, if the game has just started, there is no Level object yet, and the method update should create one.

For your convenience, we have provided you with constants for six states:

  • STATE_INACTIVE, before a level has started
  • STATE_LOADING, when it is time to load the level file
  • STATE_ACTIVE, when the game is ongoing and the obstacles are moving
  • STATE_PAUSED, when the game is paused to display a message
  • STATE_CONTINUE, when the player is waiting for a new frog
  • STATE_COMPLETE, when the game is over

All of these constants are available in consts.py. The current application state should be stored in a hidden attribute _state inside Froggit. You are free to add more states if you add extensions. However, your basic game should stick to these six states.

The rules for changing between these six states are outlined in the specification of method update in Froggit. You should read that in its entirety. However, we will cover these rules in the instructions below as well.

Level JSONs

All of the information about a game level is stored in a JSON file. You should remember what a JSON string is from the very first assignment. A JSON file is just a file that stores a very large JSON string. These JSON files are a lightweight way to store information as nested dictionaries, which is the standard way of sending complex data across the Internet.

The tricky part about JSONs is coverting the string (or file) into the Python dictionary. Fortunately, we have provided a tool that makes this part easy. The GameApp class includes a method called load_json which your Froggit class will inherit. Simply specify the name of the JSON file. As long as that file is stored in the JSON directory, this method will load this file and convert to ta Python dictionary for you.

This means that working with level files is really all about working with a complex nested dictionary. To understand how these dictionaries work, look at the file easy2.json. This includes all of the features that you will need to implement for the basic game. The top-level dictionary has five keys:

  • 'version' : A float representing the level version.

This version is only relevant if you define your own levels. It is discussed in the section on additional features.

  • 'size' : A list of two integers for the width and height.

The size represents the size of your game in grid squares, not pixels. You multiply these by GRID_SIZE to get the size of the game.

  • 'start' : A list of two integers for an x and y position.

The start value is the starting grid square for the frog (grid squares start at 0 like any list). You multiply its two integers by GRID_SIZE to get the starting pixel position for the frog.

  • 'offscreen' : An integer to support “movement wrap”.

Objects need to wrap back to the beginning once they go off screen. But you do not want to do that immediately, as the images will snap and flicker. You want them to go completely offscreen before you wrap them. This value is how far (in grid squares) that any image must be offscreen before it is time to wrap it back around. This is discussed in the activity to move the obstacles.

  • 'lanes' : A list of all the lanes in the level.

The list of lanes should have the same number of elements as the (grid) height of the level. The are ordered bottom up. The first lane in the list is the bottom row, and the last lane in the list is the top one.

The real nesting happens in these lanes. Each lane is its own dictionary, with up to three keys. These are as follows:

  • 'type' : The lane type.

The lane type is a string, and is one of 'grass’, 'road', 'water' and 'hedge'. Unless you add new types of lanes to the game, there are no other possibilities.

  • 'speed' : A float indicating how fast obstacles move in this lane.

All obstacles in a lane move at the same pace. This is the number of pixels they move per second. Movement is left-to-right. If the speed is negative, then the movement is right-to-left.

  • 'objects' : The list of obstacles in this lane.

The exact obstacles vary by lane type. For a road, these are the cars. For a water lane, these are the logs. For the hedge, these are the exits (which technically count as obstacles).

Not all lanes have all three keys. Grass has no obstacles. The hedge has exits, but they do not move and so they have no speed. However, every lane is guaranteed to have a 'type', and it is guaranteed to be one of those four values.

Finally, there are the obstacles in the list for 'objects'. All obstacles have two keys:

  • 'type' : A string representing the obstacle type.

For this assignment, it is the image file (minus the '.png’ suffix) for this obstacle. So type 'log2' corresponds to the image file 'log2.png'

  • 'position' : The float for the obstacle position.

Technically, the position represents a grid square, so you multiply it by GRID_SIZE to get the position of the obstacle. However, obstacles are different from the frog in the fact that they can actually be between two grid squares (which is why this is a float).

If you understand all of these features, then you will have no problem completing this assignment.

JSON Assumptions

One of the preconditions for this assignment is that level files are properly formatted. That is, they are proper JSONs and they do not have any important keys missing. As we said above, it is okay for some keys to be missing, like 'speed' or 'objects'. But other keys like 'size' and 'start' absolutely have to be there.

It is not your responsibility to enforce that the JSON files are in the correct format. That is the responsibility of the level designer, who is typically a different person on the team than a programmer. However if you add any extensions to the game, then you will likely be designing your own level files. And if you make mistakes in your level files, you may cause your program to crash. Again, this is a problem with the level file and not the game itself.


Task 1: Setup

We have divided these instructions into three parts. Each part corresponds to a letter grade. Finishing a task completely puts you at the boundary of that letter grade and the next. For example, finishing this first task will put you at the C+/B- border.

This task involves setting up the game. That includes loading a level file and using it to draw objects on the screen. While you will be able to move the frog when you are done, nothing else will work. No obstacles will move and you cannot win or lose the game.

Because this is the part of the assignment to earn a C grade, we have made all of the instructions in this section very explicit. The hope is that everyone in the class can make it to the C+ border. As we move on to later tasks, the instructions will become a little more vague.

To give yourself enough time on this assignment, you should finish this part in the first week of the assignment, as soon as the assignment instructions are posted.

Review the Constants

The very first thing that you should do is read the file consts.py. If you ever need a value like the size of the grid, the initial size of the game window, the frog movement speed, or so on, this is where you go. When writing code, you should always use the constants, not raw numbers (or “magic numbers,” as we call them). Magic numbers make your code hard to debug, and if you make a change (e.g. to make the grid size larger), you have no idea about all of the locations in your code that need to be changed.

With that said, you are welcome to change any of these numbers if you wish. You are also encouraged to add more constants if you think of other numeric values that you need. Anytime that you find yourself putting a number in your code, ask yourself whether or not it would make sense as a constant.

Create a Welcome Screen

We start with a simple warm-up to get you used to defining state and drawing graphics elements. When the player starts the application, they should be greeted by a welcome screen. Your initial welcome screen should start with two lines of text.

Because the welcome message is before any game has started, it belongs in the Froggit class, not the Level class. You are already seeing how we separate what goes where.

The welcomes will look something like the one above. It should tell the player the name of the game, and tell the player to “Press ‘S’ to Start”. You can change the wording here if you want. It could say something else, as long as it is clear that the user should press a key on the keyboard to continue. However, we recommend against allowing the user to press any key, since in later steps that will make it easy for the user to accidentally miss an important message (or jump immediately in front of a truck).

To create a text message, you need to create a GLabel and store in it an attribute. If you read the class invariant for Froggit, you will see two attributes named _title and _text. The title attribute is the logo of the game. The text attribute is for any messages to display to the player. The text is typically smaller, like a footnote. While we will never show the logo after the initial state STATE_INACTIVE, we will use the text attribute for messages throughout the game.

Since the welcome message should appear as soon as you start the game, it should be created in the method start, the first important method of the class Froggit. When creating your message, you will want to set things like the font size and position of the text. If you are unsure of how to do this, look at the class MainApp from the demo subcontroller.py in the provided sample code.

As you can see from the documentation for GLabel and GObject, graphics objects have a lot of attributes to specify things such as position, size, color, font style, and so on. You should experiment with these attributes to get the welcome screen that you want.

The first thing to understand is the positioning. In Kivy, the screen origin (0,0) is at the bottom-left corner, and not the center like it was for the Turtle assignment. If you want to find the center of the window (our version centers these on the screen), the Froggit object has attributes width and height that store the size of the window. In addition, for the label object, the attributes x and y store the center of the label. So you can center the label horizontally by assigning x to width/2.

When placing label objects, you do not always want to center them. Sometimes you would like the label to be flush againt the edge of the window. For that reason we have attributes like left, right, top, and bottom. These are alternate attributes for x and y. They move the label in much the same way, but give you a little more control over the positioning.

One you understand how to position the label, it is time to think about your font choice and style. If you want to look exactlty like the picture above, we have some constants to help you in const.py. The font is ALLOY_FONT, which is a reference to AlloyInk). The title has size ALLOY_LARGE while the message has ALLOY_MEDIUM. However, you are not constrained to these choices. You may chose a different font or font size if you wish, so long as it fits on the screen.

Drawing the Welcome Message

Simply adding this code to start is not enough. If you were to run the application right now, all you would see is a blank white window. You have to tell Python what to draw. To do this, simply add the lines

    self._title.draw(self.view)
    self._text.draw(self.view)

to the method draw in Froggit. The (non-hidden) attribute view is a reference to the window (much like the Window object in Assignment 4).Hence this method call instructs Python to draw this text label in the window. Now run the application and check if you see your welcome message appears.

Initializing the Game State

The other thing that you have to do in the beginning is initialize the game state. The attribute _state (included in the class specification) should start out as STATE_INACTIVE. That way we know that the game is not ongoing, and the program should (not yet) be attempting to animate anything on the screen. In addition, the other attributes listed (particularly _level) should be None, since we have not done anything yet.

The _state attribute is an important part of many of the invariants in this game. In particular, we want your new attribute for the welcome message to have the following invariant:

  • If the state is STATE_INACTIVE, then there is a welcome message with title and text.
  • If the state is not STATE_INACTIVE, the _title attribute is None.
  • If the state is STATE_ACTIVE, the _text attribute is None.

Does your start() method satisfy this invariant? Note the difference between the last two invariants. That will become important later.

Dismissing the Welcome Screen

The welcome screen should not show up forever. The player should be able to dismiss the welcome screen (and start a new game) when he or she presses the S key. To respond to keyboard events, you will need the attribute input, which is an instance of GInput.This class has several methods for identifying what keys are currently pressed.

When using the attribute input, remember the issues that we discussed in class. The method update(dt) is called every 16 milliseconds. If you hold a key down, then you see a lot of key presses. You just want the first press! That means you need some way to determine whether or not the key was pressed this animation frame and not in the previous one. See the state.py demo from the sample code for some ideas on how to do this. This may require you to add a new attribute to Froggit.

If you detect a key press, then you should change the state STATE_INACTIVE to STATE_LOADING. This will load a level file and start a new game. You are not ready to actually write the code to start the game, but switching states is an important first activity.

Invariants must be satisfied at the end of every method, so you need to assign None to both _title and _text now. This will require a simple change to method draw() to keep it from crashing (you cannot draw None). Once you have done that, run the application. Does the message disappear when you press a key?

Documenting your New Attributes

When working on the steps above, you may have needed to add new attributes beyond the ones that we have provided. Whenever you a new attribute, you must add it and its corresponding invariant as a comment after the class specification (these comments are the class invariant). Add it just after the comment stating ADD MORE ATTRIBUTES, to make it easier for the graders (and you) to find them. We will deduct style points for instance attributes that are not listed in the class invariant

Pacing Yourself

This first part of the assignment looks relatively straightforward, but it gets you used to having to deal with controller state. Try to finish this part by Wednesday, December 2, which is right after you have had your first lab. This will give you time to familiarize yourself with the online documentation, and make sure that you understand how everything fits together.

Load the Level File

The next activity is a little more complicated than the welcome screen, but once you complete it, you can be confident that you have a good idea how everything fits together. The state STATE_LOADING is only supposed to last one animation frame. During that frame you should do the following:

  • Load the DEFAULT_LEVEL into a dictionary
  • Resize the window to match the level
  • Create a new Level object
  • Assign that Level object to the attribute _level
  • Display the contents of the Level object
  • Switch the state to STATE_ACTIVE

You do not need to worry about the details of STATE_ACTIVE for now. However it is very important that you only create a new Level object if the state is STATE_LOADING. If you continue to create a Level object in STATE_ACTIVE, that will cause many problems down the line (which you will not notice until much later).

Loading a JSON File

Loading a JSON file is easier than you think. There is a method (technically it is a class method) in GameApp called load_json, and Froggit inherits this method. Simply call this method on the constant DEFAULT_LEVEL. The method will return a dictionary and you are good to go. So from this point on, you will treat the level information like a dictionary. Use the key 'size' to get the width and height (in grid squares) for the level.

By default, the starting level is easy1.json. You can go into consts.py to change this if you wish. Alternatively, you can test other levels by typing

  python froggit levelname

So you can load the 'complete.json' level simply by typing

  python froggit complete.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 levels 'easy1.json' and 'easy2.json' should make the window slightly smaller, while 'complete.json' will not resize the window at all.

Creating a Level Object

We have started the definition of the Level class for you in levels.py. However, it does not do much, because we have not defined the initializer. Furthermore, this means that the constructor does not take any arguments. However, you want the Level constructor to take a single argument: the JSON dictionary containing all of the information about the level.

Inside of your initializer, your are going to take the 'lanes' list from this dictionary and create the lane objects. For right now, each lane is going to be a single GTile object. A GTile is an image that can be repeated. To draw grass, road, or water, we take a single image and repeat several times. For example, the image below shows the difference between the image 'hedge.png' and 2x3 tiling of that same image.


Normal Image vs 2x3 Tiling

To define a tiled image, you use the attributes x, y, width, height, and source to specify how it looks on screen. The first four attributes are just like GLabel, while source specifies an image file in the Images folder. As with the label, you can either assign the attributes after the object is created or assign them in the constructor using keywords. Keyword arguments work like default arguments in that you write param = value. See the online documentation for an example of how to approach this.

The source for a lane tile is just the type plus the suffix '.png', So the 'grass' type uses the image 'grass.png', the 'road' type uses the image 'road.png' and so on. The height should be GRID_SIZE, while the width should be the width of the window. The only hard part is positioning the tiles.

The lanes are placed starting at the bottom of the screen and work towards the top. There will be one blank line at the top, because you set the height of the window to be GRID_SIZE higher than all of the lanes. To place the lanes you might find it easer to use the attributes left and bottom. These are alternate attributes to x and y when you do not necessarily want to work with the center of an object. For example, the first lanes has left and bottom both 0, while the second lane has its bottom at GRID_SIZE.

This suggests that you can create all of the lanes using a simple loop in the initializer, adding them to the attribute _lanes as you go.

Drawing the Level Object

Once again, creating an GTile object is not enough to draw it on the screen. But drawing the lanes is a bit more complicated than drawing the welcome message. The lanes are (hidden) attributes in Level. While the code

    for lane in self._level._lanes:
        lane.draw(self.view)

works (and you should try it out), it is not allowed. We will take off major style points if a class ever accesses the hidden attributes of an object of another class.

This is the purpose of adding a draw method to class Level. The draw method in Froggit should call the draw method in Level, and this in turn should call the the draw method for each lane (defined in GObject).

However, only Froggit has access to the attribute view, which is necessary for drawing. The class Level cannot directly access any attributes in Froggit. If a method in Level needs an attribute from Froggit, then Froggit must provide that attribute as an argument in the method call. This means that the draw method in Level needs to have view as a parameter, and the code in Froggit should look like this.

  self._level.draw(self.view)

Notice this is very similar to how we draw GObject objects.

Testing Your Code

While the concept of level files is a lot to wrap your head around, the advantage is that they make it really easy to test your code. By running your game on several different levels, you can see if you did them correctly. Remember, to try out a different level, type

  python froggit levelname

Here are what the backgrounds should look like for some of the provided levels.


Background for easy1.json


Background for easy2.json


Background for roadsonly.json


Background for complete.json

WARNING: These files are designed assuming that you have a 1080p (1920x1080 pixel) monitor for your computer or laptop. If you have a 720p monitor (1280x720 pixels), you will be able to play the first two levels, but not the last two. In addition, we have found that the default settings on the 13 inch MacBook are 1440x900 pixels making complete.png unplayable (though the others are fine). However, you can solve this problem by going into Settings > Display and choosing More Space.

Pacing Yourself

This is a longer activity, and we have budgeted up to two days to work on this. That means you should try to finish this part by Thursday, December 3,. However, if you take both of the days, then you should complete the next activity on the same day.

Completing this activity successfully guarantees you will pass (C-) this assignment.

Create the Frog

Next you need to create the frog. Again, this is to be stored in an attribute of class Level. That means that you must create it in the __init__ method of Level and modify your drawing code so that it appears. The frog should be an object of class Frog, which is included in models.py.

You will notice that Frog is a subclass of GImage. Like GTile, a GImage object is used to draw an image to a screen. The difference is that, if you specify the width and height, the GImage will resize the image to fit entirely in that size; it will not tile the image. But otherwise, creating and drawing a GImage is exactly the same as creating and drawing a GTile object.

Initializing the Frog

Technically, we do not have to define an initializer for the frog. We inherit the one from GImage already. But we do not want to use that initializer. The image file for the frog will never change; it is defined by the constant FROG_IMAGE in const.py. And the width and height will be the default values. The only thing we want to specify is the frog position on the grid.

So that means we want to define a custom initializer for the Frog class. This initializer should have parameters for the start position (both x and y) and that is it. This initializer then calls super() to use the initializer for GImage, passing FROG_IMAGE as the source file.

When positioning the frog, the center of the frog (defined by the attributes x and y should be the center of the grid square. Remember to multiply the grid positions by GRID_SIZE before passing them to the initializer for GImage. The GImage class works with pixels, not grid squares. But also remember that a grid square (col,row) is not centered at (col*GRID_SIZE,row*GRID_SIZE). That would be the bottom left corner of the grid square.

There is one more thing you need to do when you create the frog. By default, the frog image is facing to the south. You need to set the angle attribute of the frog to FROG_NORTH to make sure the frog is facing in the correct direction.

One you have created the custom initializer for Frog, the initializer in Level just needs to call the constructor for Frog and assign it to the _frog attribute.

Drawing the Frog

Remember to draw the frog object in the method draw of class Level. You draw the frog in the same way you drew the lane tiles. You do not need to define a draw method in class Frog as it is inherited from GImage. However it is important that you draw the frog last. Objects are draw bottom to top and you want the frog to be on top of everything.

Testing Your Code

Again, you can test your code by running each of the provided level files. For all of the provided levels, the frog should be centered in the bottom lane. For example, the level easy1.json should look like this:


Frog for easy1.json

That is because 'easy1.json' has a start position of [5,0]. If instead the start position where [0,5], it would look like this:


Frog for Swapped easy1.json

Pacing Yourself

This is super short activity. You should be able to complete this soon after you finish drawing the lanes, which is by Thursday, December 3. Finishing this on time will give you more time for the next activity.

Completing this activity successfully will guarantee you a low C on this assignment.

Add the Obstacles

Now we come to the first tricky point of the assignment. We want you to create all of the obstacles in the level. This includes cars, trucks, logs, and even exits. But do this you are going to need to replace some of the code that you have already written. This is something that we are going to be doing throughout the assignment. You will start with simple code first to make sure everything is working. But then you will go back and replace it with more complex code.

The code you need to replace are the GTile objects you created in the initializer for Level. That is because we are going to replace those objects with a composite object. If you do not know what composite objects are, these are discussed in lab 22, soon after you get back. You may go over this lab with your lab instructor if you need help.

For those who wish to skip the lab, composite object is when we combine one or more GImage objects (or in this case a GTile object and some GImage objects). The composite object is constructed just like a subcontroller. It has an initializer that creates all of the objects inside of the composite, and its own draw method to draw the individual objects as well.

Initializing a Lane

The Lane object will serve as our composite object. You will notice that there are classes for Grass, Road, Water and Hedge. However, you can safely ignore all of those for now. We will only need those classes later when we work on the basic game. Right now, the parent class Lane will do all of our work for us.

Each Lane object will replace a single GTile. That means that each Lane object should have an attribute _tile for the GTile that it contains. Ideally, you can just copy the code from Level into the initializer for Lane. However, that means you need to add some new parameters to the initializer for the Lane. In addition to having access to the JSON dictionary, the Lane initializer needs to know which lane it is, so it can pick the right values out of the nested dictionary. If you created the tiles with a for-loop, you essentially need to pass the loop variable as a parameter to the lane object.

Inside of the Lane initializer, you should also initialize all of the obstacles, adding them to the list attribute _objs. You do this with a for-loop just like you did the tiles in a previous activity. However, this time all of the obstacles are represented by a GImage, not a GTile. The source file for each obstacle is the same as the type plus the suffix '.png'. So obstacle 'log2' corresponds to the image file 'log2.png', and so on.

You do not need to specify the width and height of these images. The default value is fine. However, you do need to specify their position. If you look at the JSON files, you will notice that each obstacle has a 'position' value. This is the grid square for the center of the obstacle. So the y attribute of the obstacle should be the same as the y attribute for the tile (the centers should match), but the x attribute should be determined by the 'position'.

When using the 'position', note that obstacles can fill up more than one grid square. For example, 'log2' is two grid squares in width. So to put this obstacle in grid squares 1 and 2, its center will be at grid 1.5 (so at the border of grid squares 1 and 2). This is shown below.


Positioning an Obstacle

This means that you cannot get the x pixel by multiplying the 'position' by the GRID_SIZE. But it is close, and you should be able to figure out the correct answer now.

Orienting the Obstacles

In addition to placing the obstacles, you need to orient them correctly. By default, all obstacles are facing the right edge of the window, because the assumption is that they will move left-to-right. However, that is not always true. Some objects will move right-to-left, and they will need to face in the opposite direction.

How do we know which is which? It depends on the 'speed' value of the lane. If the speed is positive (or 0), then the obstacles face the normal direction. But if the speed is negative, the obstacles face the opposite direction.

Turning obstacles around is easy. We just rotate them 180 degrees by senting the angle attribute in a GImage object to 180.

Constructing a Lane

Once you have the initializer defined for the lane, now it is time to replace the code in Level. In the initializer for level, you want to replace all of the GTile objects with Lane objects. We also recommend that you use the right subclass for each type. So a 'grass' lane should use a Grass object and so on. We know you did not define any methods in these classes, but they inherit the initializer from Lane. And breaking your lanes into specific classes now will make parts of Task 2 much easier.

Drawing a Lane

Drawing a Lane object is similar to drawing Level object. You need to add a draw method and it needs to take the view as a parameter. You use this to draw the tile first (since it is at the bottom) and then all of the obstacles in order.

If you do this correctly, you should not even need to change the code in the draw method for Level. That code should still work. Instead of calling the draw method of GTile, it is calling the method for Lane.

Testing Your Code

Once again, you should try out your code on several different level files to make sure that it is correct. Here are what the obstacles should look like (with the frog included) for some of the provided levels.


Obstacles for easy1.json


Obstacles for easy2.json


Obstacles for roadsonly.json


Obstacles for complete.json

Pacing Yourself

This is the hardest activity in the setup portion. Give yourself two days to work on this activity, finishing it by Saturday, December 5. Once you get this down, the rest of the setup will be easier.

Completing this activity successfully will guarantee you a C on this assignment.

Display the Lives

The challenge of Frogger is that you have a limited number of lives before the game is over. We want to display those lives to the player. That is the purpose of the blank lane at the top of the screen. When you are done with this activity, the top bar should looke like this:


The Lives Counter

The lives are represented by GImage objects using the source file FROG_HEAD. However, the FROG_HEAD image is very large. You will need to scale it to GRID_SIZE width and height to get it to fit. You can do that by setting the width and height attributes of the GImage object.

You should keep these GImage objects in a list. That makes drawing them the same as drawing the lanes. And when you want to lose a life, you will just remove one of the images from the list.

Finally, you should also include a GLabel indicating that these frog heads represent the lives. If you use ALLOY_FONT, we recommend using ALLOY_SMALL for the font size. In addition, make the right edge of the label equal to the left edge of the first head.

Organizing Your Code

All of these attributes should be created in the initializer for Level. However, you might notice that your initializer is starting to get a little long. Remember the 30 line rule. If any method gets longer than 30 lines, you might need to break it up into helpers. This may now be the time to do that.

If you make helpers, remember to write specifications for all of your helpers. You should also hide them. If another class does not need access to them, they should be hidden.

Finally, remember to update the draw method to draw the label and the frog heads. If you think that your draw method should be broken up into helpers, you should do that as well.

Pacing Yourself

This is a short activity, though we have give you a day on it. Try to finish this by Sunday, December 6. That should give you a breather if you got behind on the previous activity.

Completing this activity successfully will guarantee you a high C on this assignment.

Move the Frog

The last activity in the setup is to get the frog moving. We will not worry about the obstacles yet. We just want to concentrate on the frog. To move the frog, you will need to take into account the player’s key presses. The frog only moves when the player presses (or holds down) a key to make it move. By default, we assume that the player will use the arrow keys to move the frog. However, if you prefer WASD controls or some other control scheme, that is okay.

Updating the Frog

To see how to control the frog, you should look at the arrow.py demo from the provided sample code. This example shows how to check if the arrow keys are pressed, and how to use that to animate a shape.

To perform the actual movement, you will need to add an update method to Level. This will complete the methods turning it into a proper subcontroller, like subcontroller.py in the sample code. Note that moving the frog requires access to the input attribute. This is an attribute of Froggit, not Level. The Level class will need some way to access this.

The frog should move between grid squares. That means if the player is pressing the up or down arrow, the frog will move GRID_SIZE up or down. If the player is pressing left or right, the frog will move GRID_SIZE left or right. As a result it will look like the frog is slightly “teleporting” about the screen, as shown below:

Note that the frog also turns when it moves. Use the constants FROG_NORTH, FROG_EAST, FROG_SOUTH and FROG_WEST to point your frog in the correct direction.

If the player is holding down two keys at the same time, the frog should not move diagonally. One of the keys should win (or they should cancel out). Which one you do is up to you. We do not care.

Timing the Movements

If the player holds down an arrow key, we actually want the frog to keep moving. So in a sense, moving the frog will easier arrow.py. We do not care whether the player did or did not press a key the previous frame. As long as the player holds down the arrow keys, the frog will move.

However, part of the challenge of the Frogger is that the frog moves slowly. If the frog moves a full GRID_SIZE every animation frame, the frog will rocket off the screen immediately.

The solution to this it to introduce a cooldown. Once the frog moves, you must wait FROG_SPEED seconds to move the frog again. How do you keep track of time? That is the point of the dt attribute in the update method of Froggit. That measures how many seconds have passed. So, when the frog moves, set a cooldown attribute to FROG_SPEED. Afterwards, subtract dt from the cooldown. Once this reaches 0 or less, the frog can move again.

Restricting Movement

You should ensure that the frog stays completely on the board even if the player continues to hold down a key. If you do not do this, the frog is going to be completely lost once it goes off screen. How do you do this? Look at where the frog will end up before you move it. If it is going to go offscreen, then do not move it. However if that movement required the frog to turn, you should still turn the frog in place, even if it cannot move forward.

When we say “offscreen”, we mean the top bar with the lives counter in it as well. As you can see in the video above, our frog stops when it reaches the final hedge. However, the frog can still move freely about the hedge. That will not be the case in the game; the hedge will block the frog unless the frog is stepping on a lily pad. But we will not worry about that until a later activity.

Testing Your Code

Testing this part of the code is easy. Run the game, and try to move your frog. Does the frog move? Does it stay on the screen? Then you have succeeded.

This activity is actually pretty straightforward, provide you understand the arrows.py demo in the provided sample code. Most people can complete this part of the assignment in less than two hours. However, some people will find that they cannot get the frog to move, even after properly adapting the code from arrows.py.

One of the key things is to make sure that your cooldown is correct. Keep one of the arrow keys held down. The frog should not move smoothly, but instead should only move every FROG_SPEED seconds. It is possible to adjust this speed from the command line if you wish. Try running

  python froggit easy1.json 1

The last argument is a number which replaces FROG_SPEED. In this case, the frog would move once a second, which is much slower than the default 4 times a second.

Debugging Your Code

If your frog refuses to move, add a watch statement to the draw method of Level. Print out the id (the folder name) of the frog. Run the game and look for this print statement. What you see will (hopefully) identify your bug.

The print statement never appears: In this case, you have forgot to call the update method for Level from Froggit. Make sure Froggit has this line of code in its update:

    self._level.update(self.input)

The frog id keeps changing: In this case, you are accidentally reseting the Frog object each frame. Go back and look at the instructions for loading the level file to see how to stop this.

The frog id is constant: If this is true and the frog still will not move, we have no idea what your problem is. It is something unique. Please see a consultant immediately. They will not look at your code, but they will help you debug it better. In our experience, when students claim the frog id is not changing, they are wrong.

Pacing Yourself

We highly recommend that you complete this by Tuesday, December 8, ending the first week of work. This will get you to the first grade border.

Completing this activity successfully will guarantee you a C+ on this assignment.


Task 2: The Basic Game

When you are done with this task, you will have a complete and playable game. It will not be fancy, but it will be playable. That will be enough to put you at the B+/A- border.

Because we expect more of B students than we do of C students, these instructions will not be as detailed as they were for the previous task. At this point, we will have assumed that you have read all the documentation and are familiar with how the game2d objects work.

Detect the Exits

In the previous activity, the frog could walk all along the hedge. This is not what we want. The frog should be able to enter an exit (one of the lily pads). Any other part of the hedge will block the frog from moving, just as if it were going offscreen.

To do this, we need to be able to do two things

  • Detect if the frog is trying to walk into a hedge
  • Detect if the frog is trying to walk into an exit (or an opening)

To do this, we will leverage two different methods inherited from GObject: collides and contains. They are both very similar, but they do have some important differences.

Detecting Movement into a Hedge

The collides method is used to determine when two game objects (like say a frog and a background tile) are overlapping. To determine if the frog is in the hedge, simply call the method collides from either the frog or the hedge tile (it does not matter which). If the result is True, then the movement is (potentially) blocked. Otherwise, the frog is not trying to move into the hedge and there is no problem.

Because the tile object is a (hidden) attribute of Lane and the frog object is a (hidden) attribute of Level, we need some way to compare these two together. This will require additional methods in the Lane class. You either need a getter to access the tile, or a collision method that allows the Level to pass the frog as a parameter. We do not care which one you chose.

When you write this method, be prepared for there to be multiple hedges. We have added an image 'open.png' which represents an opening in the hedge that is not a exit. This allows the player to walk through the hedge without reaching an exit. This is for advanced levels like 'multihedge.json'. While supporting this file is not necessary for this activity, we will be including it when we grade this task at the end.

Detecting Movement into a Exit

Movement into a hedge is only allowed if it is into an exit (or an opening). As the “obstacles” of a hedge are all either exits or openings, this means that we just have to check whether the frog collides with an obstacle in the hedge. If so, the frog can move into the hedge.

However, this time we do not want you to use the collides method. For reasons that will become clear when you give the frog a log ride, we want to ensure that most of the frog overlaps the exit. So that is the purpose of the contains method. The contains method checks if a point (represented as a tuple (x,y)) is located inside of a game object. We only want to allow movement into the hedge if the center of the frog will be contained in the exit.

To do this, you loop though the exits and opening (the obstacles) and see if the center of the frog is contained in one of them. If so, the movement is allowed. Adding this functionality will require more methods. But this time we do not want you to add these methods to Lane. This is functionality that is specific to a hedge. So these methods (even if they are just getters to access all of the exits) belong in the Hedge class. This is where we are going to start to use our subclasses, as each of the lanes will behave differently from each other.

ADDENDUM: It has been recently pointed out that in 'multihedge.json', you should not permit the frog to enter an exit from the north. While that is not a requirement of this task, we are adding it to Task 3

Testing Your Code

Once again, test your code by trying out some of the levels. You should be blocked trying to walk into a hedge unless you are walking into an exit or opening.

You should also try out the level 'multihedge.json' and see if you can squeeze through the first hedge to make it to the second hedge. It is okay if you walk through exits like they were an opening. You will solve that problem in a later activity.

Pacing Yourself

You should be able to finish this activity in a single day. If you are on schedule, this would be by Wednesday, December 9. This activity is not that much more difficult than the normal restrictions on frog movement. The only challenge is spreading out your code among the individual classes.

Completing this activity successfully will guarantee you a B- on this assignment.

Move the Obstacles

Up until now, the obstacles have just been sitting there. It is time to get them moving. Movement is determined by the 'speed' value for the lane. This is the number of pixels that each obstacle should move per second. All obstacles in a lane move at the same rate. If the speed is positive, they move left-to-right (so you add the speed to the position). If the speed is negative, they move right-to-left.

Implementing Basic Movement

To implement the basic movement, you will need to add an update method to Lane, also turning it into a proper subcontroller. This update method should move all of the obstacles in the lane, and it should be called from the update method in Level.

Technically, you should only move obstacles in road and water lanes. However, hedges never have a speed (the exits do not move unless you are making than an extension). And the grass has no obstacles (again, unless you are adding an extension like snakes). So that is why it is safe to add this code to the Lane class. Keep in mind that this movement will require some additional attributes (such as the lane speed).

When you move the obstacles, remember that the speed is the number of pixels per second. To get the number of pixels to move any given animation frame, you should multiply that value by dt, the time since the last animation frame.

Implementing Wrap Around

Eventually the obstacles are going to go offscreen. When that happens, we want to wrap them back around to the other side. So objects going offscreen to the left should come back around on the right, and objects going offscreen to the right should come back around to the left.

Naively, the way to do this is to look at the x position of the obstacle. If it is less than 0, set it the width of the window. If it is greater than the width, set it to 0. However this solution causes a major problem: “snapping”. The x attribute is the center of the object. So we will see have of the object immediately disappear on one side, and the other half teleport in on the other side. We want the movement to be smooth.

We could try to solve the snapping problem by using attributes left and right instead of x. If the left attribute is fully off the right of the screen, we set the right attribute to 0. And similarly if the right attribute is less than 0, we set the right attribute to the width. This gets rid of snapping and the movement looks smooth. However, it completely screws up spacing. Different obstacles have different lengths (look at level 'easy2.py'). Over time, this will alter the spaces in-between the obstacles changing the layout of the level. In some cases, it could cause the obstacles to start to overlap.

We want a solution that eliminates snapping but also preserves spacing. The solution is to have an offscreen buffer. This is a little bit of a distance that all objects are allowed to go offscreen. If the buffer is 4, then obstacles can have an x as low as -4*GRID_SIZE and as high as width+4*GRID_SIZE. When the x attribute crosses this threshold, then the object should wrap around to the other offscreen edge. Hence an object will traverse two offscreen buffers before it comes back on screen.

The buffer should be large enough to support the largest obstacle in the level. That is why it is defined inside of the level file. It is not your responsibility to ensure that the buffer is large enough. That is the responsibility of the level designer.


An Offscreen Buffer of Size 2

As the illustration above shows, the the object should not snap to -buffer*GRID_SIZE or width+buffer*GRID_SIZE when it wraps around. If you do this, you will still start to lose spacing over time because the obstacles do not align exactly with the grid squares. Instead, when the center of the obstacle (marked by the blue dot) crosses the buffer, we determine the distance d that it went over. When we teleport the image to the other side, we shift it forward by the amount d.

This all sounds harder than it is. You can accomplish all of this with a simple if-statement.

Testing Your Code

Once again, you can test your code on any level file. However, we highly recommend that you test it on the level 'complete.json'. If it is working properly, then it should look like the video below.

Let the game run for a long time. If you are losing spacing, then you will start to see it in this level, because there are so many obstacles.

Pacing Yourself

The code is getting harder, but we still think you can finish it in a day. If you have made it this far then you understand more and more of how the code works, and can write code faster. Therefore, you should try to finish this activity by Thursday, December 10.

Completing this activity successfully will guarantee you a low B on this assignment.

Squash the Frog

Now we have everything moving, but the game is not very interesting. That is because nothing really interacts with anything. Cars do not squash the frog. Logs do not take the frog for a ride. Only hedges really do anything right now, but it does not feel like a game.

In this activity, you will introduce the first challenge in your game: cars. If a frog collides with a car, then the frog should die. Before we talk about how to kill the frog, we first need to think about how to detect a frog death. In many ways this is exactly the same problem as detecting the exits. We need to add special methods to the Road class that allow us to determine if a frog has collided with a car. Look at how you solved that problem and come up with something similar here.

Killing the Frog

What does it mean to kill the frog? Two things. First of all, the frog should disappear from the screen. There are many ways to do that. One is simply to set the _frog attribute to None, and make sure that the Level does not draw the frog when it is None (just like the Froggit class handles the welcome message). Another approach is to add a visible attribute to the frog. When this attribute is False, the frog is not drawn. This would require you to override the draw method in Frog, instead of simply inheriting the one from GImage. Choose whichever approach you are most comfortable with.

The second thing about killing the frog is that you should pause the game immediately. That is, the Froggit object should switch its state to STATE_PAUSED (remember states?) and it should no longer update the Level object until it is active again. That means you will need some way for the Froggit app to know whether or not to pause the game. You could add a getter to Level for Froggit to get this information. Or you could turn the update method in Level from a procedure into a fruitful method, and use the return value to indicate whether or not to pause. Again, we do not care which approach you take.

Pausing the Game

Pausing the game is easy. Simply do not call update in Level. Again, you can see exactly this idea if you examine subcontroller.py in the sample code.

However, there is one more thing to do. We want you to display a message that the game is paused to the player, just like we have done below.


Paused Game

Notice that we are displaying a message to the player. We can do this with _text attribute in Froggit, provided that we draw it last. There is not need to add a label to Level for this, especially since Froggit is in charge of pausing.

In the example above we made a few stylistic choices that you might want to adopt, but you are not required to do so. First of all, we made the message neatly fit in a center lane of the level (which is easy if you use ALLOY_SMALL). Furthermore, we set the background of the GLabel to a solid cover, covering up the obstacles underneath. We did all of this to make the label more readable. In the end, this is all that matters. You can do whatever you want with your paused message so long as it is still readable.

Some of the more observant of you might realize that the message can get really tight as the levels get small. The level 'easy1.json' is pretty narrow, but what if we have a level that is just three grid squares wide? Actually, this is a precondition that we are going to add to the game. No level will be less than 10 grid squares wide and less than 8 grid squares high. So as long as your message fits in that space, you are fine.

Resuming the Game

The player should be able to start playing again by pressing a key. Our solution uses the ‘C’ key, but it can be anything you want so long as the message you displayed makes it clear. Once again, we recommend that you do not allow the player to press any key, as a player holding down the up arrow will immediately move forward and step in front of a truck.

This step is almost the same as STATE_INACTIVE. You detect a key press and then switch the state. But this time you do not want to switch to STATE_LOADING; we do not want to load a new level. Instead, go to STATE_CONTINUE.

The purpose of STATE_CONTINUE is to reset the level. That means making the frog visible again and putting the frog back in its start location. If you forgot where the start location was, you might need an attribute somewhere to remember it. Like STATE_LOADING, this is a state that lasts exactly one animation frame. When it is done, it should immediately switch to STATE_ACTIVE and the game should continue as normal.

Testing Your Code

It is time to start killing frogs! Load up a level and step in front of cars. You do not lose any lives yet, so it is harmless. Make sure that you die when you touch a car and live when you avoid them.

Keep in mind that images have a little bit transparency around the edges, but the collision code in GObject simply compares the size of the images (which are rectangles). So it is possible that your frog just barely escapes the car but the game still registers it as a touch. This is a problem you will solve when you tighten the hitboxes.

Pacing Yourself

You should try to finish this by Friday, December 11. A lot what you need to do here is similar to what you had to do for the exits. The only new and tricky part is pausing and unpausing the game.

Completing this activity successfully will guarantee you a low-to-mid B on this assignment.

Lose the Game

Now that you can kill your frog, you can lose the game. Every time that the frog dies, you should remove one of the frog heads from the top of the screen. Since this is just a list of GImage objects, this should be easy. The problem is what happens when there are no lives left.

Normally when the frog dies, Froggit is supposed to switch to STATE_PAUSED. However if Froggit detects that there are no lives left (this will probably require a new method in Level) then it should instead switch to STATE_COMPLETE and display a very different message to the player


Game Lost

For the basic game STATE_COMPLETE is the final state. There is no other state to switch to. The player is expected to close the window and relaunch the game if they want to play again. If you want to allow the player to play again, that is an extension that you can add.

Testing Your Code

Once again, it is time to kill your frog. But when you run out of lives the game should end. Since you cannot win yet, the only thing you can do is die. This says something about the inevitability of our mortality.

Pacing Yourself

This activity should be really quick. If you know how to pause the game, tracking lives and marking a loss is not that much different. However, we budgeting a full day for this, up to Saturday, December 12. That is to allow you to catch up you got behind on previous activities.

Completing this activity successfully will guarantee you a mid B on this assignment.

Reach the Exits

You are already detecting the exits in the game. But that is not the same as reaching safety. When a frog touches an exit (as determined by the contains method), you need to put the frog safely on the lily pad. That means putting the image FROG_SAFE on top of the lily pad. You should also make the normal frog invisible and pause the game, just like when the frog got hit by a car. But this time, the player should not lose a life.

As when the frog dies, the player will continue once they press the correct key. The frog will be reset to the starting position to begin again.

Blocking the Exits

For the most part, this activity is not that much different that getting hit by car. It is just that the frog does not lose a life. However, there is one important additional detail. The frog can only use an exit once. Once a lily pad has been taken, it cannot be used again in the future. If a frog tries to step into an occupied lily pad, the frog should be blocked, just like the frog is blocked by a normal hedge square.

This is going to require a lot more attributes in the Hedge class. In fact, you are going to want to add some attributes just do draw the FROG_SAFE images on top of the lily pads. But you also might want something else (like a list or dictionary) to keep track of which exits are occupied and which are not.

More attributes means you need to define an initializer in the Hedge class. It should add the new attributes, but use super() to make sure that you inherit all of the original attributes from Lane. You will also need to modify the methods you wrote when you first detected the exits to account for these changes. Of all the Lane subclasses, Hedge will prove to be one of the most complex.

Testing Your Code

You are almost ready to play a complete game. Try out the default game 'easy1.json'. See if you can fill up all of the lily pads without losing too many lives. If you want more of a challenge, try ‘roadsonly.json' instead.

Pacing Yourself

You should try to finish this by Sunday, December 13, which is one day ahead of the last activity. We starting to move very fast now. But students who reach this far should have been able to finish the other parts a little early and hopefully have some time to spare in their schedule.

Completing this activity successfully will guarantee you a mid-to-high B on this assignment.

Win the Game

Winning the game is lot like losing the game. When you run out of exits, the game pauses, Froggit switches to STATE_COMPLETE and displays a congratulatory message like the one shown below.


Game Won

The trick is determining whether the game is won or not. Class Hedge should give you some method to determine if all exits are occupied. But remember that it is possible for there to be more than one hedge, so you need to check this against all hedges.

Testing Your Code

You are now able to successfully play any level that does not include water. Try out 'easy1.json’ first, as that is very easy level to win. Try out 'roadsonly.json' for more of a challenge.

Finally, try out 'multihedge.json' to make sure that you work correctly when you have more than one hedge. Because occupied lily pads prevent the frog from going through the hedge, this level should work correctly so long as you did not think that the open square was an exit.

Pacing Yourself

This activity is very short and should be completed by Sunday, December 13, the same day as the last activity.

Completing this activity successfully will guarantee you a high B on this assignment.

Splash the Frog

The game is complete as far as roads are concerned. But the standard game of frogger also has water. To try out the water levels, we suggest that you switch DEFAULT_LEVEL to easy2.json. You will be able to win and lose this level, but the water portions will be boring. The frog will walk on water and not interact with the logs.

Taking a Log Ride

The frog should go for a ride if it mostly collides with a frog. You should use the same rule that you use for exits. That is, use the contains method on a log to check if the center of the frog is inside the log. When that happens, we say that the frog is “on top of the log”.

As long as the frog is on top of the log, the log should push it. That means however much much the log changes its x attribute each frame, the frog x attribute should change the same amount. When the frog moves again, this will stop (unless jumps on top of a log).

Right now, movement is discrete. The frog jumps between lanes, but is always either fully inside of or fully outside of a lane. So that makes the code somewhat simple. Check which lane the frog is in. If it is a water lane, find the log underneath the frog (if any). Then move the frog by that amount.

Notice that this is going to screw up the grid movement. As a frog gets pushed, it becomes like an obstacle and is no longer guaranteed to be in a grid square. But because we are using the contains method to reach the exit, everything will be fine.

Drowning the Frog

The Frogger frog cannot swim. Maybe its mother never taught it how. What this means is that if the frog enters a water lane but it is not on top of any log, it dies immediately. You should try it exactly as if the frog were hit by a car. The game pauses and the player loses a life. If this is the last life, the player loses the game.

In addition, the frog cannot go offscreen. While you previously wrote code to prevent this from happening, that was only for the arrow keys. There is nothing to keep a log from dragging a frog offscreen. If the center of the frog (defined by the x and y attributes) ever goes off screen, the frog dies as well.

Testing Your Code

You are now able to successfully play any level in the game, including 'complete.json'. Try them all out. Feel free to make some new levels. Enjoy your accomplishment.

Pacing Yourself

If you want an A on this assignment, we suggest that you finish by Tuesday, December 15, just after the second week. This will give you enough time to complete the final task and add some extensions if you wish.

Make sure that all of Task 2 is complete and tested before moving on. If you have any bugs now, they can make the next part much harder.

Completing this activity successfully will guarantee you a B+ on this assignment.


Task 3: Animation and Polish

The game is now playable, but it feels very rough. The frog teleports across the screen rather than moving smoothly. And if the frog dies, it just disappears. Most of the time we have no idea even why we died.

To improve the game we need to add some juice, or what game academics call game feel. These are features that do not fundamentally alter how the game plays, but make the game feel more professional or satisfying.

That is also the point of the extensions. If you have reached this far in the assignment, you are allowed to start adding extensions to the game. We will ignore any extensions for any submission that does not complete the basic game. But from this point on you can implement extensions for minor extra credit, including compensating for occasional bugs in the first two tasks.

Tighten the Hitboxes

Before you can start working on animation, we need to talk about hitboxes. A hitbox is the part of an image that can collide with a game object. This is important because images often have transparencies around them, and we do not want the transparent parts to count as part of the collision.

This is particularly true when we start to animate the frog. Because the frog can stretch out, this will mean that there is a huge difference between the hitbox and the image size. Below we show the difference between the images 'frog1.png' which you have used so far and 'frog2.png' which you will use to animate the frog. The image for 'frog2.png' has a huge amount of blank space because we need to give the frog space to stretch out.


Hitbox Comparisons for FROG_IMAGE and FROG_SPRITE

Handling hitboxes can be really hard. Fortunately, you do not have to worry about this. The game2d package does this automatically for you. All you have to do is tell it what the hitboxes are, and the methods collides and contains do the rest.

Defining the Hitboxes

All hitbox information is in a file called 'objects.json'. This is the lone file in the JSON directory that is not a level file. Look at this file and you will see that it has the image size and hitboxes for every single image file provided. These files are divided into 'images' and 'sprites'. You will work with sprites when you animate the frog.

You have to understand what the hitbox information means. Let us look at 'car1.png' as an example. Its hitbox is [1,5,1,5]. You read these values as adjustments for the left, top, right, and bottom edges, in that order. So the hitbox starts 1 pixel to the right of the left edge, 5 pixels down from the top edge, 1 pixel left of the right edge, and 5 pixels above the bottom edge. This is shown in the image below.


The Hitbox for Image 'car1.png'

However, you do not need to understand any of this (unless you want to add your own images). All you need to do is assign it. And if you look at the documentation for GObject, you will notice that that all have an attribute hitbox that has exactly this same format. And this attribute is inherited by GImage.

Assigning the Hitboxes

So if all you have to do is assign the hitbox attribute, what is the challenge? Well, you have to get this information from the file 'objects.json' to the individual GImage objects. That means you have to load this file at the same time you load the level file. And you have to change all of your initialzers to include an additional parameter for the hitbox dictionary.

But fortunately, once you do that, it is pretty straight forward. To get the hitbox for an image, just use the ‘type' as a key.

ADDENDUM: The following requirement was added on December 4.

Fixing the Hedges

When you write code to Detect the Exits, it is possible that you allowed a frog to enter a hedge edge from the north. For example, in ‘multihedge.json’, you squeeze through the opening in the middle, and then try to move back down through the hedge into an exit.

We do not want this behavior. So while you are fixing collisions, you should also fix your code for detecting the exits to prevent this possibility.

Testing Your Code

If you look at the JSON directory, you will notice a file called 'bigones.json'. This level includes the obstacles 'biglog' and ‘bigcar'. If you do not implement the hitboxes correctly, then the second grey car will kill your frog even if the frog is sitting safely on the grass. Furthermore, the frog will be able to ride along on the second log even if it is not on top of it.

Fortunately, these two obstacles are included in 'objects.json'. If you can play this level normally, then your hitboxes are working correctly.

Pacing Yourself

This is another straight-forward activity. You just have to modify all your initializers. Again, you should be able to do this in a day, finishing by Wednesday, December 16. But make sure you make a back-up of your code before making big changes like this. We would hate for you mess up your progress and not know how to get it back.

Completing this activity successfully will guarantee you an A- on this assignment.

Animate the Frog

This is the big one. If you can get past this part, the last two parts of the assignment are easy. It is time to animate the frog so that it moves smoothly between lanes. This is where you are going to use the coroutines that we talked about in Lesson 29. You will also get some experience with these on lab 23 to aid you on this step.

Up until now, you have not done that much with the Frog class. Maybe you added a visible attribute. Maybe you added some attributes to the frog to remember the start position. But now you are going to see why we created the Frog class, and did not just leave it a GImage object like we did for the other obstacles.

Creating the Coroutine

The first thing we want to do is to slide the frog between lanes. The way we are going to do this very close to what we do in the coroutine _animate_slide in the file coroutine1.py in the provided sample code. You should use FROG_SPEED to define how long the coroutine runs. You will find that you no longer need the cooldown attribute when you do this. You detect player input if the frog is not animating and ignore it if it is.

The difference between the frog and coroutine1.py is that you need to be prepared to move in different directions. While the frog slides smoothly, it turns immediately as soon as the coroutine starts. When you are done with this step, your frog movement should look like this.

We recommend that you make the coroutine a method in the Frog class. That way the coroutine has access to all of the frog attributes and you do not have to pass them as parameters when the coroutine starts.

There is one other important thing to keep in mind, and that is how this sliding movement affects collisions. You do not have to do anything different above cars. But logs are going to be a problem. You die if you touch water without being on a log. But if the frog is sliding, there is going to be a period in the jump when it is over the water but not yet on the log.

You need to change the collision code for the Water class. The frog only dies if it is in the water and animation has stopped. If the animation is still active, you can assume that the frog is safely in mid-air. Similarly, the log should only push the frog if the animation has stopped. If the frog is still in the air, the log has no affect.

Finally, if the frog dies in the middle of an animation, you need to clear the animator (set it to None). Otherwise, the frog will resume that animation (probably half way across the board) when the player continues.

Switching to a Sprite

The frog now moves smoothly. But we want to see it actually jump. We are not going to be able to do that with a still image. We are going to need a sprite. A sprite is a collection of images that you flip through to create an illusion of movement. You will get experience with one of these in lab 23.

Read the documentation for GSprite to see how it works. The sprite image is defined by the constant FROG_SPRITE (though it is missing the '.png'). If you use this as a key in the 'objects.json' file, you will get the image file and the correct format (the number of rows and columns).

You will also notice that GSprite objects have a hitboxes attribute. This is different from the hitbox attribute (which they have as well). This attribute defines the hitbox for every frame. If you look at the frog sprite, you will see that they are wildly different. Once again, this is all handled for you automatically. If you set the frame, then the game will set the correct hitbox. You just need to assign the hitboxes attribute at the beginning.

Animating the Sprite

While you are smoothly sliding the frog, you want to flip through all of the sprite images as well. You change the sprite image by setting the frame attribute. The frog is at rest at frame 0. You stretch out the frog by increasing the frames until you get to frame 4 (this final frame). Then you contract by decreasing frames in the reverse order until you get to frame 0.

If you look at coroutine2.py in the provided sample code you will notice that we do almost exactly the same thing when we turn the ship. If you can figure out how that code works, then you can animate the frog here.

Testing Your Code

As always, you should play your game. When you are done, your movement should look something like this

Pay close attention to how your frog works with the water. You do not want your frog dying in mid animation for no reason. Also pay close attention to what happens when you continue after a death or a successful exit. Is the frog back in its normal resting position in the correct location? If not, you did not reset the animator correctly.

Pacing Yourself

We are budgeting two-days for this part, with a suggested finish date of Friday, December 18. Honestly, we are not expecting a lot of students to get this one. If you get it, congratulations. And if a lot of people in the class gets it, even better. Everyone who gets this far deserves an A (an exam-level A).

Completing this activity successfully will guarantee you a low A on this assignment.

Animate the Death

It is still the case that if your frog dies it just disappears. That is not satisfactory. We want to see what killed our frog. That is going to require a dying animation. For this, you will need another sprite, given by DEATH_SPRITE. We recommend that you manage this sprite in the Frog class (so now the Frog class because a composite object). But you are free to manage it with a second class in models.py if you wish.

Either way, you should manage the death animation as a coroutine. There can only be one animator at a time. Either the frog is moving or it is dying. So this will be a lot like the two animators in coroutine1.py in the provided sample code.

Creating the Death Sprite

The format and file for the death sprite is defined in 'objects.json' using the DEATH_SPRITE key. You will notice that it has no hitboxes. That is because the death sprite cannot collide with anything (it is already dead). Thus it is safe for both the hitbox and hitboxes attribute to be None.

While you can create the death sprite each time the frog dies, it is better to create it once in the beginning, at the same time that you create the frog. That is why we suggest turning the Frog class into a composite. It displays either the frog or the death sprite, though both will be invisible if the frog makes it to the exit. In addition, the death sprite should be located exactly where the frog was when it died. This is exactly the thing that you learned how to do with composite objects in lab 22.

Once again, however, we will not be checking this aspect of your code. If the death sprite works, then it works. We do not care how you do it.

Animating the Death Sprite

The coroutine for the death sprite is even easier than the one for the frog. It does not have to move. It only has to go from the first frame to the last frame. The amount of time to animate DEATH_SPEED. If you could animate the frog, this is simple.

The important feature, however, is that you should not pause the game (and deduct the life) until the death animation is finished. If you pause to early, the player will see the skull-and-crossbones, but the death will not be animated.

Testing Your Code

When you are done, your game should look like the video at the start of the instructions (minus the sound). You are almost done.

Pacing Yourself

This is much easier than the frog movement, so you completed that task you should be able to finish this in a day. We suggest finishing this by Saturday, December 19. This will give you one day for the last task and then another day to check over your work.

Completing this activity successfully will guarantee you a solid A on this assignment.

Play Sounds

If you watch the video above you will notice that we have sound effects. The frog croaks when it jumps (CROAK_SOUND), splats when it dies (SPLAT_SOUND), and trills when it reaches an exit (TRILL_SOUND).

To load an audio file, you simply create a Sound object as follows:

    jumpSound = Sound(CROAK_SOUND)

Once it is loaded, you can play it whenever you want (such as when the frog jumps) by calling jumpSound.play().

We are not going to tell you anything more than that. If you made it this far, you can read the online specification to see how to use Sound objects. You cannot replay a sound until the current version of the sound stops. So if you want to play the same sound multiple times simultaneously (which is only likely to happen if you add extensions, you will need two different Sound objects for the same sound file. Proper game audio can get really complicated and this is one of the professor’s active areas of research.

Important: Loading sounds can take a while. We recommend that you load all sounds you plan to use at either the start of the game or the start of a level.

Pacing Yourself

You will want one last day to check all your work and go over the finishing touches. Therefore, we recommend that you finish this activity by Sunday, December 20.

Completing this activity successfully will guarantee you a high A (but not an A+) on this assignment.


Additional Features

If you are ahead of schedule, then you are welcome to do whatever you want to extend the game and make it more fun. Doing so will require that you not only add more code, but also that you make your own level files. For example, you might want to add turtles to the game. You will notice that we have provided a sprite sheet for a submerging turtle in the IMAGES directory. We also have provided a tasty fly for the frog to eat as a pickup. And what would Frogger be without a score documenting how fast or how far you made it in the game?

You are allowed to change anything you want so long the program runs normally on the level files that were provided. If you want to demonstrate anything new you have to define your own level file to show it off. We recommend that you give this level file a version number other than 1.0. In fact, you might want to veersion them in the order that you want us to look at them.

If you have additional features in your game, we may award extra credit for them. Indeed, additional features are the only way to make an A+ on this assignment. However, there are some important rules here.

First of all, we will not look at additional features unless you have (mostly) completed Task 2. There is no point in adding features if you cannot play the game. It is okay if you have bugs in Task 2, and the extra credit may compensate against some of those bugs. But do not work on additional features until the frog can successfully ride the logs.

Second, we will not answer any questions about how much extra credit is worth. Extra credit is extra credit. You do it because you want to, not because you are hoping it will save your grade. We will decide how much your effort is worth when we see it, after it is turned. Not before. This is a time for your imagination to shine, not for you to check off boxes.

We will say this much, however. No matter how many additional features you add, extra credit will never raise you grade by more than half a letter. So it can take you from a B+ to an A-, an A- to an A, and so on. But that is it. Do not get too carried away.

We highly recommend that you back up your solution before working on any additional features. Many times we have seen students completely ruin their perfectly good code adding the new features. Do not do that.

If you do add any extensions, we ask that you add a README text file to your submission. Document any extensions that you have added and tell us what level files we should run in order to experience them.


Finishing Touches

Before submitting anything, test your program to see that it works. Play for a while and make sure that as many parts of it as you can check are working. Cycle between multiple level files.

When you are done, reread the specifications of all your methods and functions (including those we stubbed in for you), and be sure that your specifications are clear and that your functions follow their specifications. If you implemented extensions, make sure your documentation makes it very clear what your extensions are.

As part of this assignment, we expect you to follow our style guidelines:

  • You have indented with spaces, not tabs (Atom Editor handles this automatically).
  • Classes are separated from each other by two blank lines
  • Methods are separated from each other by a single blank line
  • Class contents are ordered as follows: getters/setters, initializer, non-hidden methods, hidden methods
  • Lines are short enough that horizontal scrolling is not necessary (about 80 chars)
  • The specifications for all of the methods and classes are complete
  • Specifications are immediately after the method header and indented
  • No method is more than 30 lines long, not including the specification

We are serious about the last one. This is a potential 10 point deduction.

Turning it In

You are potentially modifying a lot of files in this assignment. At a bare minimum, you are modifying app.py, level.py, lanes.py, and models.py. You might be modifying consts.py. You might have extra art and sound files.

To simplify the submission process, we are not asking you upload each individual file. Instead, put all your files in a zip file called froggit.zip and submit this instead. We need to be able to play your game, and if anything is missing, we cannot play it. However, make sure that your file is less than 100 MB. CMS cannot take anything more than that and sound files can get very large.

If you did add any extensions, then your submission must include a README file documenting all of the extensions you added. You should also tell us what level files we should run in order to experience them. We cannot give you credit for extensions if we do not know about this. If there is no README file, we will assume the game has no extensions.

Completing the Survey

One last time, we need you to do a survey. As always, the survey will ask about things such as how long you spent on the assignment and your impression of the difficulty. Please try to complete the survey within a day of turning in this assignment. Remember that participation in surveys comprises 1% of your final grade.

This is designed to be a very hard assignment. We realized that is what we were going to have to do if we replaced the final exam with an assignment. We had to create an assignment that possibly half the class could not finish (though if everyone can finish it, I will happily give out a lot of As this semester). We are interested in whether or not that was the case, and how much time you actually spent on the assignment.