#lang scribble/manual @(require "../vkp-scrbl/unnumbered.rkt" "../vkp-scrbl/utils.rkt") @title{Lecture 11: Designing interactive games} @para[#:style "boxed"]{ Use a library to design an interactive game.@linebreak{} Practice working with subclasses and super classes.@linebreak{}@linebreak{} @hspace[4] @link["OceanGame.java"]{OceanGame.java} @hspace[4] @link["javalib.jar"]{javalib.jar} @hspace[2]} @section*{Lecture 11: Outline} @itemlist[ @item{How to design a game} @item{Extending the World class - using a library} @item{Extending the Posn class - adding functionality} @item{Designing graphical representation of the game}] @section{How to design a game} We have seen how to design a super class that provides an abstraction for several related subclasses. Another use for abstract classes and super classes is in designing an infrastructure for building a collection of similarly structured applications. Often a several classes and interfaces are combined into a @emph{library}. In Java such collection of classes and interfaces is combined into a @emph{package} that can then be imported into the user's program. One such library we have seen and have been using is the @emph{tester} library. In the first course in @emph{DrRacket} we have designed interactive graphics-based games -- by definng only the game behavior and the graphical display of the game state. The entire part of teh program that generates the window in which the game is shown, displays the graphics, and manages the user's interactions is embedded in the library. We have a similar library available for the design of interactive grahics-based games in Java. The @emph{javalib} library consists of several components. For the beginner there is the @emph{javalib.funworld} that manages the dislay and behavior of the game components and the user interactions, and @emph{javalib.worldimages} that handles the creation and manipulation of the graphical shapes and images. Let use wee what goes into designing a simple game. Here is a snapshot of out game: @image[#:scale 1.0 #:suffixes '(".png")]{oceangame.png} A shark moves up and down on the left side of the screen, hoping to eat some fish to keep from starving to death. A school of fish swims right to left -- somewhat randomly. The player controls the shark's movements using the @emph{up} and @emph{down} arrow keys. The game ends when the shark dies of hunger. So, to represent this game we need to represent a shark, a school of fish, their behavior, their interactions, and the display of the game scene at each point in the game. The following class diagram represents the objects that comprise our @emph{OceanWorld} game: @verbatim{ +----------------+ | OceanWorld | +----------------+ +--| Shark shark | | | ILoFish school |--+ | +----------------+ | +----------------+ | | | | v v v | +------------+ +---------+ | | Shark | | ILoFish | | +------------+ +---------+ | | Posn loc |-+ / \ | | int hunger | | --- | +------------+ | | | +--------+ ------------------- | | | | | | +----------+ +--------------+ | | | MtLoFish | | ConsLoFish | | | +----------+ +--------------+ | | +----------+ +-| Fish first | | | | | ILoFish rest |-+ | +-----------+ | +--------------+ | | | v v v | +----------+ +-------+ | | Fish | | Posn | | +----------+ +-------+ +--| Posn loc | | int x | +----------+ | int y | +-------+ } @section{Extending the World class - using a library} The abstract class @tt{World} in the @emph{javalib.funworld} library provides a framework for the game design. The game programmer defines a class that extends the @tt{World} class and defines the game behavior by implementing and overriding a few methods in the @tt{World} class. To define the graphical representation of the game scene, the programmer must implement the abstract method @verbatim{public WorldImage makeImage()} We will discuss the design of the images later. The three key methods that define the game behavior are defined as concrete methods in the @tt{World} class, but their implementation is only a @emph{skeleton} -- each method just returns @tt{this}: @verbatim{ // produce the World after one clock tick has passed // in this World World onTick() { ... } // produce the World after user pressed the given key // in this World World onKeyEvent(String ke){ ... } // produce the World after user clicked the mouse // at the given position in this World World onMouseClick(Posn pos){ ... } } (The @tt{Posn} class that represents a position on the canvas is defined in the @emph{javalib.worldimages} package, and will be discussed in the next section.) The game programmer @emph{overrides} those methods that are relevant to her game. So, a @emph{wacamole} game where the programmer has to hit randomly appearing objects by clicking the mouse at the correct locations does not need to override the @tt{onKeyEvent} method. A board game where the user selects next move using the keys may not need the @tt{onTick} method. Our @tt{OceanWorld} game does not respond to mouse clicks and so does not use the @tt{onMouseClick} method. The design of the method @tt{onTick()} and @tt{onKeyEvent(String ke)} is straightforward, we just have to remember to delegate the work to the classes that are responsible for each task. So, both, the class @tt{Shark} and the class @tt{Fish} will have a method @tt{onTick()}: the shark will get hungrier, and the fish will move left (for now, some fixed distance, say 3 pixels) and some random distance up or down (no more than 2 pixels). The method in the @tt{OcenWorld} class will then just produce a new @tt{OceanWorld} composed of @tt{this.shark.onTick()} and @tt{this.school.onTick()}. The @tt{onTick()} method for the @tt{ILoFish} will produce a list of @tt{Fish} where each fish invoked its @tt{onTick()} method. Only a shark reacts to the key events. The @tt{String}s that represent the arrow keys are @tt{"up"}, @tt{"down"}, @tt{"left"}, and @tt{"right"}. For a game with just one fish, the code so far would be: @verbatim{ // A Shark swims in the ocean, looking for fish to eat class Shark { int y; // the horizontal coordinate int health; // the hunger level, when zero, the shark dies // the standard complete constructor Shark(int y, int health) { this.y = y; this.health = health; } // move the shark up or down, on up-down key press Shark onKeyEvent(String ke) { if (ke.equals("up")) { return new Shark(this.y - 3, this.health); } else { if (ke.equals("down")) { return new Shark(this.y + 3, this.health); } else { return this; } } } // produce a shark hungrier than before Shark onTick(){ return new Shark(this.y, this.health - 1); } } // A Fish swims in the ocean - providing nourishment for the shark class Fish { Random rand = new Random(); // to generate random numbers Posn p; // the location of this fish // the standard complete constructor Fish(Posn p) { this.p = p; } // fish swims from right to left towards the waiting shark Fish swim() { return new Fish(new Posn(this.p.x - 3, this.p.y + rand.nextInt(5) - 2)); } } //An Ocean has a shark and a fish swimming in it class Ocean extends World { Shark shark; // the hungry shark Fish fish; // the fish the shark will eat (maybe) Ocean(Shark shark, School fish) { this.shark = shark; this.fish = fish; } // the fish swim from right to left on each tick public World onTick() { return new Ocean(this.shark.onTick(), this.fish.onTick()); } // the shark moves up or down as directed by the arrow key public World onKeyEvent(String ke){ return new Ocean(this.shark.onKeyEvent(ke), this.fish); } } } Of course, we need to make examples of the world, and design tests as well. The description here is just a sketch that shows which methods are needed to implement the game behavior. @subsection{Starting the game.} Just as in @emph{drRacket}, the game starts by invoking the method @tt{bigBang(int width, int height, double factor)} We provide the @tt{width} and the @tt{height} of the game area and specify the tick rate. We then invoke this method in the last test case of our @tt{Examples...} class: @verbatim{ OceanWorld startWorld = new OceanWorld(...); // run the game boolean testRun(Tester t){ return this.startWorld.bigBang(400, 400, 0.1); } } @subsection{Ending the game.} To end the game the @tt{World} class provides three methods: @section{Extending the Posn class - adding functionality} @section{Designign graphical representation of the game} The colors for the shapes are provided in one of the following two libraries: @verbatim{ import javalib.colors.*; import java.awt.Color; }