联系方式

  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-23:00
  • 微信:codinghelp

您当前位置:首页 >> Python编程Python编程

日期:2020-12-04 09:20

Organization and Scope

While there are no test cases this time, you should be able to figure

out if everything is working simply by playing the game. There are

no tricky “restore everything to how it was” like with Turtles; no

nasty surprises lurking in the specifications. Just get the game

working.

Assignment Source Code

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

The last item is a link that you should refer to through the

assignment, while the other two are files to download. Only the first

is a must download, as it contains the all of the source code

necessary to complete the assignment.

The second file is a collection of demo code from the lesson

on GUI programming, as well as the lesson on coroutines. This

sample code contains a lot of hints on how to approach some of

the harder parts of this assignment, and we reference these

samples throughout the instructions.

As with the imager application, this assignment is organized as a

package with several files. To run the application, change the

directory in your command shell to just outside of the

folder froggit and type

File Description

froggit.zip The application package, with all the source code

samples.zip Several programs that give hints on this assignment

game2d API The documentation for how to use the game2d classes

python froggit

In this case, Python will run the entire folder. What this really means

is that it runs the script in __main__.py. This script imports each of the

other modules in this folder to create a complex application. To

work properly, the froggit folder should contain the following:

For the most part, you only need to understand the first four files -

app.py, level.py, lanes.py and models.py - as well as the JSON directory.

The other files and folders can be ignored. However, if you decide

to add a few extensions, it helps to understand how all of these fit

together.

app.py

This module contains the controller class Froggit. This is the

controller that launches the application, and is one of four modules

that you must modify for this assignment. While it is the primary

controller class, you will note that it has no script code. That is

File Description

app.py The primary controller class for the application

level.py A secondary controller for a single playable application

lanes.py A collection of mini-controllers for the individual lanes

models.py Model classes for the game (i.e. Frog)

consts.py All module with all of the constant (global variable) values

game2d A package with classes that can display graphics on the screen

Sounds A folder of sound effects approved for your use

Fonts A folder of True Type fonts approved for your use

Images A folder of images for the frog and various obstacles

JSON A folder with different levels you can play

contained in the module __main__.py (which you should not

modify).

level.py

This module contains the secondary controller class Level. This

class manages a single level of the game. It works as a

subcontroller, just like the example subcontroller.py in the provided

sample code. It is another of the four modules that you must

modify for this assignment, and is the one that will require the most

original code.

lanes.py

Levels in Frogger consist of horizontal lanes that the frog has to

traverse. These could be roads, water, or even just a strip of grass.

And each of these lanes is very different from one another. The frog

does not die from touching a road, but will die if it is hit by a car on

the road. On the other hand, the frog dies in the water (it cannot

swim) unless it is touching a log.

This will make the code quite complicated. So to make things

much easier, you will make a class for each type of lane: road,

water, grass and (exit) hedge. Technically these classes will act as

subcontrollers to Level just as Level is a subcontroller for Froggit. In

complex applications there are many layers of controllers.

models.py

This module contains the model class Frog. This class is similar to

those found in the pyro.py demo in the provided sample code. If you

want to add other model classes (e.g. turtles or pick-ups), then you

should add those here as well. This is the last of the four files you

must modify for this assignment.

consts.py

This module is filled with constants (global variables that should

not ever change). It is used

by app.py, level.py, lanes.py and models.py to ensure that these

modules agree on certain important values. It also contains code

for adjusting your default level. You should only modify this file if

you are adding additional features to your program.

game2d

This is a package containing the classes you will use to design

your game. These are classes that you will subclass, just like we

demonstrated in the lesson videos. In particular, the class Froggit is

a subclass of GameApp from this package. As part of this

assignment, you are expected to read the online

documentation which describes how to use the base classes.

Under no circumstances should you ever modify this package!

Sounds

This is a folder of sound effects that you will use in the final

activity. You are also free to add more if you wish; just put them in

this folder. All sounds must be WAV files. While we have gotten

MP3 to work on Windows, Python support for MP3 on MacOS is

unreliable.

Fonts

This is a folder of True Type Fonts, should you get tired of the

default font for this assignment. You can put whatever font you

want in this folder, provided it is a .ttf file. Other Font formats (such

as .ttc, .otf, or .dfont) are not supported. Be very careful with fonts,

however, as they are copyrighted in the same way images are. Do

not assume that you can include any font that you find on your

computer.

Images

This is a folder with image files for the frog and the obstacles.

The GImage and GSprite classes allow you to include these in

your game. You can put other images here if you wish.

JSON

This is a folder with JSON files that define a level. You will turn

these into dictionaries and use them to place objects in your game.

Understanding these files will be a major portion of this

assignment. But you should look at them briefly to familiarize

yourself with these files.

Assignment Scope

As we explained in class, your game is a subclass of GameApp.

The parent class does a lot of work for you. You just need to

implement three main methods. They are as follows:

Your goal is to implement all of these methods according to their

(provided) specification.

Method Description

start() Method to initialize the game state attributes

update(dt) Method to update the models for the next animation frame

draw() Method to draw all models to the screen

start()

This method should take the place of __init__. Because of how Kivy

works, initialization code should go here and not in the initializer

(which is called before the window is sized properly).

update(dt)

This method should move the position of everything for just one

animation step, and resolve any collisions (potentially deleting

objects). The speed at which this method is called is determined by

the (immutable) attribute fps, which is set by the constructor. The

parameter dt is time in seconds since the last call to update.

draw()

This method is called as soon as update is complete. Implementing

this method should be as simple as calling the method draw,

inherited from GObject, on each of the models.

These are the only three methods that you need to implement. But

obviously you are not going to put all of your code in those three

methods. The result would be an unreadable mess. An important

part of this assignment is developing new (helper) methods

whenever you need them so that each method is small and

manageable. Your grade will depend partly on the design of your

program. As one guideline, points will be deducted for methods

that are more than 30 lines long (not including specifications or

spacing).

You will also need to add methods and attributes to the

class Level in level.py, the class Frog in models.py and the

classes Lane, Road, Water. and Hedge in lanes.py. All of these classes are

completely empty, though we have given you a lot of hints in the

class specification. You should read all these specifications.

As you write the assignment, you may find that you need additional

attributes. All new instance attributes should be hidden. You should

list these new attributes and their invariants as single-line

comments after the class specification (as we have done this

semester). For example, if the Level class needs to access the exit

locations in a Hedge lane, then you are going to need a getter for

these exits (and we give hints on how to do this).

While documentation and encapsulation is essential for this

assignment, you do not need to enforce any preconditions or

invariants. However, you may find that debugging is a lot simpler if

you do, as it will help you localize errors. But we will not take off

points either way, as long as the game runs correctly.

Assignment Organization

This assignment follows the model-view-controller pattern

discussed in the lesson videos. The modules are clearly organized

so that each holds models, the view, or a controller. The

organization of these files is shown below. The arrows in this

diagram mean “accesses”. So the Froggit controller accesses the

view and Level subcontroller. The Level controller accesses the

lanes, the view, and the models. And Lane and its subclasses

access the view and the modesl.

This leads to an important separation of files. Froggit is never

permitted to access anything in models.py and lanes.py. Level is never

permitted to access anything in app.py. The classes in lanes.py may

not access anythign in app.py or level.py. And finally, models.py cannot

access anything other than the view. This is an important rule that

we will enforce while grading. All of this is shown in the diagram

below

The Import Relationships

In addition to the four main modules, there is another module with

no class or function definitions. It only has constants, which are

global variables that do not change. This is imported by

the models module and the various controllers. It is a way to help

them share information.

When approaching this assignment, you should always be thinking

about “what code goes where?” If you do not know what file to put

things in, please ask on Piazza (but do not post code). Here are

some rough guidelines.

Froggit

This controller does very little. All it does is keep track of the game

state (e.g. whether or not the game is paused). Most of the time it

just calls the methods in Level, and lets Level do all the work.

However, if you need anything between lives, like a paused

message or the final result, this goes here. This is similar to the

class MainApp from the demo subcontroller.py

Level

This class does all the hard work. In addition to the initializer (which

is a proper __init__, not start), it needs its

own update and draw methods. This is a subcontroller, and you

should use the demo subcontroller.py (in the provided sample code)

as a template.

The most complex method will be the update and you will certainly

violate the 30-line rule if you do not break it up into helpers. For the

basic game, this method will need to do the following:

? Animate the frog according to player input

? Move all the obstacles in each lane

? Detect any collisions between the frog and an obstacle

? Remove the frog after a death or a successful exit

In our code, each one of these is a separate helper (though the

second one will actually be managed by the Lane class below). You

should think about doing this in your code as well.

The Lanes

There are four types of lanes in the game: road, water, grass, and

the hedge. The hedge is where the exits are placed. The road has

cars (and trucks). The water has logs to jump on. The grass is a

safe zone that gives you a rest.

Because each lane acts differently, you want to make a separate

class for each one. However, they do have a lot in common, so

they will all be subclasses of the class Lane. These classes already

appear in lanes.py with their specifications.

Each lane is essentially a mini-level. The frog needs to get

succesfully past it. That is why each lane will be its own

subcontroller, controlling its own lane. As a subcontroller, each lane

should have its own __init__, update, and draw methods. However,

most of these will go in Lane and the other classes will safely inherit

them. When the Level class needs to update the lanes, all it will do

is loop through the lanes and call the update method for each one.

The Models

The models just keep track of data. Most of the time, models just

have attributes, with getters and setters. Think Image from the

previous assignment. However, sometimes models have additional

methods that perform computation on the data, like swapPixel.

The models in this assignment are the game objects on screen: the

frog and any obstacles. Most of the time, you do not need a new

class for a module, as the class GImage does everything that you

needs for the cars, logs, and exits. However, the frog is very special

and so the frog will need a custom class. We have written the

specification of this for you.

If you want to add any additional features to your game, you may

need to add some new models here. For example, turtles that will

occasionally dive under water have to be animated in the same

way that the frog does. So they will need their own classes for

exactly the same reason. You should only add new classes for the

additional features.

Suggested Micro-Deadlines

You should implement the application in stages, as described in

these instructions. Do not try to get everything working all at once.

Make sure that each stage is working before moving on to the next

stage.

Set up a schedule.

Overview of Froggit

The layout of a Froggit game depends on the level file you are

using. We have included many different level files in

the JSON directory. The files easy1.json and easy2.json are easy games

to help you test your game, while roadsonly.json and complete.json are

a little more challenging. The level that you use is defined by the

variable DEFAULT_LEVEL in const.py. You can also change the level at

any time by specifying it when you run the game. For example, if

you type

python froggit roadsonly.json

it will play the game with the level roadsonly.json as the DEFAULT_LEVEL.

Below is the set-up for the complete.json to test out your game in the

end.

The Starting Position

All of the levels are arranged as a grid, where each grid square

is GRID_SIZE pixels on each side. The lanes are one grid unit in

height, and several grid units in width. Each exit is one grid unit

wide. Cars or logs can take up multiple grid squares, depending on

the specific obstacle. This grid is not explicitly visible, but it

becomes obvious once you play the game for a bit.

The Invisible Grid

Once the game begins, the cars and logs move across the screen.

They go in different directions, specified by the 'speed' key in the

JSON dictionary. Once they go off the screen, they wrap back

around to the other side. How fast they wrap around is defined by

the 'offscreen' key in the JSON dictionary.

The frog has to safely make it to (i.e. touch) one of the exits, which

is a lily pad at the final hedge. When that happens, the game puts a

bluish frog on the lily pad and restarts another frog at the starting

point, as show below. Exits may not be reused. The second frog

cannot use the exit that currently has a frog on it.

At the First Exit

If a frog dies, the game pauses and deducts a life from the player.

These lives are shown by the frog heads in the top right corner. In

the picture below, the game is paused after losing the first life.

Down One Life

There are two ways for the frog to die. The frog dies if it is hit by (or

even touches) a car. The frog also dies if touches the water. Why

this is a frog that cannot swim is a mystery from the early days of

video games. To survive on the water, the frog must hop on a log.

But the logs carry the frog with it when they move, making the

game a little more challenging.

Once you understand those rules, the game is simple. You win by

filling up every lily pad at the hedge. You lose if you run out of lives

before this happens. In the video below, we show both outcomes

on the level easy2.json.

Game State

One of the challenges with making an application like this is

keeping track of the game state. In the description above, we can

identity several distinct phases of the game:

? Before the game starts, but no level has been selected

? When the level has been loaded, but the frog and obstacles

are not moving

? While the game is ongoing, and the obstacles are moving

onscreen

? While the game is paused (e.g. to show a message)

? While the game is creating a new frog to replace the old one

? After the game is over

Keeping these phases straight is an important part of implementing

the game. You need this information to

implement update in Froggit correctly. For example, whenever the

game is ongoing, the method update should instruct the Level object

to move the frog. However, if the game has just started, there is

no Level object yet, and the method update should create one.

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

states:

? STATE_INACTIVE, before a level has started

? STATE_LOADING, when it is time to load the level file

? STATE_ACTIVE, when the game is ongoing and the obstacles are

moving

? STATE_PAUSED, when the game is paused to display a message

? STATE_CONTINUE, when the player is waiting for a new frog

? STATE_COMPLETE, when the game is over

All of these constants are available in consts.py. The current

application state should be stored in a hidden

attribute _state inside Froggit. You are free to add more states if you

add extensions. However, your basic game should stick to these

six states.

The rules for changing between these six states are outlined in the

specification of method update in Froggit. You should read that in its

entirety. However, we will cover these rules in the instructions

below as well.

Level JSONs

All of the information about a game level is stored in a JSON file.

You should remember what a JSON string is from the very first

assignment. A JSON file is just a file that stores a very large JSON

string. These JSON files are a lightweight way to store information

as nested dictionaries, which is the standard way of sending

complex data across the Internet.

The tricky part about JSONs is coverting the string (or file) into the

Python dictionary. Fortunately, we have provided a tool that makes

this part easy. The GameApp class includes a method

called load_json which your Froggit class will inherit. Simply specify

the name of the JSON file. As long as that file is stored in

the JSON directory, this method will load this file and convert to ta

Python dictionary for you.

This means that working with level files is really all about working

with a complex nested dictionary. To understand how these

dictionaries work, look at the file easy2.json. This includes all of the

features that you will need to implement for the basic game. The

top-level dictionary has five keys:

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

This version is only relevant if you define your own levels. It is

discussed in the section on additional features.

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

The size represents the size of your game in grid squares, not

pixels. You multiply these by GRID_SIZE to get the size of the game.

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

The start value is the starting grid square for the frog (grid squares

start at 0 like any list). You multiply its two integers by GRID_SIZE to

get the starting pixel position for the frog.

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

Objects need to wrap back to the beginning once they go off

screen. But you do not want to do that immediately, as the images

will snap and flicker. You want them to go completely offscreen

before you wrap them. This value is how far (in grid squares) that

any image must be offscreen before it is time to wrap it back

around. This is discussed in the activity to move the obstacles.

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

The list of lanes should have the same number of elements as the

(grid) height of the level. The are ordered bottom up. The first lane

in the list is the bottom row, and the last lane in the list is the top

one.

The real nesting happens in these lanes. Each lane is its own

dictionary, with up to three keys. These are as follows:

? 'type' : The lane type.

The lane type is a string, and is one

of 'grass’, 'road', 'water' and 'hedge'. Unless you add new types of

lanes to the game, there are no other possibilities.

? 'speed' : A float indicating how fast obstacles move in this

lane.

All obstacles in a lane move at the same pace. This is the number

of pixels they move per second. Movement is left-to-right. If the

speed is negative, then the movement is right-to-left.

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

The exact obstacles vary by lane type. For a road, these are the

cars. For a water lane, these are the logs. For the hedge, these are

the exits (which technically count as obstacles).

Not all lanes have all three keys. Grass has no obstacles. The

hedge has exits, but they do not move and so they have no speed.

However, every lane is guaranteed to have a 'type', and it is

guaranteed to be one of those four values.

Finally, there are the obstacles in the list for 'objects'. All obstacles

have two keys:

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

For this assignment, it is the image file (minus the '.png’ suffix) for

this obstacle. So type 'log2' corresponds to the image file 'log2.png'

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

Technically, the position represents a grid square, so you multiply it

by GRID_SIZE to get the position of the obstacle. However, obstacles

are different from the frog in the fact that they can actually be

between two grid squares (which is why this is a float).

If you understand all of these features, then you will have no

problem completing this assignment.

JSON Assumptions

One of the preconditions for this assignment is that level files are

properly formatted. That is, they are proper JSONs and they do not

have any important keys missing. As we said above, it is okay for

some keys to be missing, like 'speed' or 'objects'. But other keys

like 'size' and 'start' absolutely have to be there.

It is not your responsibility to enforce that the JSON files are in

the correct format. That is the responsibility of the level designer,

who is typically a different person on the team than a programmer.

However if you add any extensions to the game, then you will

likely be designing your own level files. And if you make mistakes in

your level files, you may cause your program to crash. Again, this is

a problem with the level file and not the game itself.

Task 1: Setup

We have divided these instructions into three parts. This task

involves setting up the game. That includes loading a level file and

using it to draw objects on the screen. While you will be able to

move the frog when you are done, nothing else will work. No

obstacles will move and you cannot win or lose the game.

We have made all of the instructions in this section very explicit. As

we move on to later tasks, the instructions will become a little more

vague.

Review the Constants

The very first thing that you should do is read the file consts.py. If

you ever need a value like the size of the grid, the initial size of the

game window, the frog movement speed, or so on, this is where

you go. When writing code, you should always use the constants,

not raw numbers (or “magic numbers,” as we call them). Magic

numbers make your code hard to debug, and if you make a change

(e.g. to make the grid size larger), you have no idea about all of the

locations in your code that need to be changed.

With that said, you are welcome to change any of these numbers if

you wish. You are also encouraged to add more constants if you

think of other numeric values that you need. Anytime that you find

yourself putting a number in your code, ask yourself whether or not

it would make sense as a constant.

Create a Welcome Screen

We start with a simple warm-up to get you used to defining state

and drawing graphics elements. When the player starts the

application, they should be greeted by a welcome screen. Your

initial welcome screen should start with two lines of text.

Because the welcome message is before any game has started, it

belongs in the Froggit class, not the Level class. You are already

seeing how we separate what goes where.

The welcomes will look something like the one above. It should tell

the player the name of the game, and tell the player to “Press ‘S’ to

Start”. You can change the wording here if you want. It could say

something else, as long as it is clear that the user should press a

key on the keyboard to continue. However, we recommend against

allowing the user to press any key, since in later steps that will

make it easy for the user to accidentally miss an important

message (or jump immediately in front of a truck).

To create a text message, you need to create a GLabel and store in

it an attribute. If you read the class invariant for Froggit, you will see

two attributes named _title and _text. The title attribute is the logo

of the game. The text attribute is for any messages to display to

the player. The text is typically smaller, like a footnote. While we will

never show the logo after the initial state STATE_INACTIVE, we will use

the text attribute for messages throughout the game.

Since the welcome message should appear as soon as you start

the game, it should be created in the method start, the first

important method of the class Froggit. When creating your

message, you will want to set things like the font size and position

of the text. If you are unsure of how to do this, look at the

class MainApp from the demo subcontroller.py in the provided sample

code.

As you can see from the documentation for GLabel and GObject,

graphics objects have a lot of attributes to specify things such as

position, size, color, font style, and so on. You should experiment

with these attributes to get the welcome screen that you want.

The first thing to understand is the positioning. In Kivy, the screen

origin (0,0) is at the bottom-left corner, and not the center like it

was for the Turtle assignment. If you want to find the center of the

window (our version centers these on the screen), the Froggit object

has attributes width and height that store the size of the window. In

addition, for the label object, the attributes x and y store

the center of the label. So you can center the label horizontally by

assigning x to width/2.

When placing label objects, you do not always want to center

them. Sometimes you would like the label to be flush againt the

edge of the window. For that reason we have attributes

like left, right, top, and bottom. These are alternate attributes

for x and y. They move the label in much the same way, but give

you a little more control over the positioning.

One you understand how to position the label, it is time to think

about your font choice and style. If you want to look exactlty like

the picture above, we have some constants to help you in const.py.

The font is ALLOY_FONT, which is a reference to AlloyInk). The title has

size ALLOY_LARGE while the message has ALLOY_MEDIUM. However, you are

not constrained to these choices. You may chose a different font or

font size if you wish, so long as it fits on the screen.

Drawing the Welcome Message

Simply adding this code to start is not enough. If you were to run

the application right now, all you would see is a blank white

window. You have to tell Python what to draw. To do this, simply

add the lines

self._title.draw(self.view)

self._text.draw(self.view)

to the method draw in Froggit. The (non-hidden) attribute view is a

reference to the window (much like the Window object in Assignment

4).Hence this method call instructs Python to draw this text label in

the window. Now run the application and check if you see your

welcome message appears.

Initializing the Game State

The other thing that you have to do in the beginning is initialize the

game state. The attribute _state (included in the class specification)

should start out as STATE_INACTIVE. That way we know that the game

is not ongoing, and the program should (not yet) be attempting to

animate anything on the screen. In addition, the other attributes

listed (particularly _level) should be None, since we have not done

anything yet.

The _state attribute is an important part of many of the invariants in

this game. In particular, we want your new attribute for the

welcome message to have the following invariant:

? If the state is STATE_INACTIVE, then there is a welcome message

with title and text.

? If the state is not STATE_INACTIVE, the _title attribute is None.

? If the state is STATE_ACTIVE, the _text attribute is None.

Does your start() method satisfy this invariant? Note the difference

between the last two invariants. That will become important later.

Dismissing the Welcome Screen

The welcome screen should not show up forever. The player should

be able to dismiss the welcome screen (and start a new game)

when he or she presses the S key. To respond to keyboard events,

you will need the attribute input, which is an instance

of GInput.This class has several methods for identifying what keys

are currently pressed.

When using the attribute input, remember the issues that we

discussed in class. The method update(dt) is called every 16

milliseconds. If you hold a key down, then you see a lot of key

presses. You just want the first press! That means you need some

way to determine whether or not the key was pressed this

animation frame and not in the previous one. See the state.py demo

from the sample code for some ideas on how to do this. This may

require you to add a new attribute to Froggit.

If you detect a key press, then you should change the

state STATE_INACTIVE to STATE_LOADING. This will load a level file and start

a new game. You are not ready to actually write the code to start

the game, but switching states is an important first activity.

Invariants must be satisfied at the end of every method, so you

need to assign None to both _title and _text now. This will require a

simple change to method draw() to keep it from crashing (you

cannot draw None). Once you have done that, run the application.

Does the message disappear when you press a key?

Documenting your New Attributes

When working on the steps above, you may have needed to add

new attributes beyond the ones that we have provided. Whenever

you a new attribute, you must add it and its corresponding

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

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.

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 lowto-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 midto-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.

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.

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.

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.

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.


版权所有:留学生编程辅导网 2020 All Rights Reserved 联系方式:QQ:99515681 微信:codinghelp 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。 站长地图

python代写
微信客服:codinghelp