overview

learn the basics of object oriented programming (oop)

study object-oriented concepts and code

create an example object-oriented program (a Tamagotchi-style virtual pet)

download example files here

object oriented programming

oop is a programming methodolgy that breaks a program into separate modules

each module is independent, with its own private responsibilities

but each module must also cooperate with other modules

the "modules" of an object-oriented program are called "classes"

classes

each class is physically a separate part of the program

separate code block

separate file

typically, each class represents either an abstract concept:

- the current time
- a colour

or a real "thing"

- a push button
- a drop-down menu
- a race car

what's in a class?

you already know what variables and functions are

variables store data

var shoppingListPrices = [100, 15, 340];

functions perform tasks

function calculateCost (priceList) { var cost = 0; for (var i = 0; i < items.length; i++) { cost += priceList[i]; } return cost; }

classes are simply groups of functions and variables

a higher level of organization

oop adds a level of program organization on top of functions

functions add only a basic level of organization to a program (tasks are grouped)

but in oop, functions themselves are grouped together with variables into classes

in a sense, each class is like a miniature program

writing an object-oriented program is like writing many small programs that work together to form a larger program

why OOP?

object-oriented programming helps make a program:

- natural to plan and conceptualize
- reusable across projects (use one class in many programs)
- easy to change
- easy to expand upon
- easy to test
- stable and bug-free
- easy to co-develop with two or more programmers

from classes come objects

on its own, a class can't do anything

a classes is only a blueprint, used to create objects

much like a factory is used to create cars

each class creates its own type of object, much like a honda civic factory creates civics but a porche boxter factory creates boxters

a single class can create many objects (again, just like a factory can create many cars)

another word for "object" is "instance", meaning a single specific incarnation of the class

three steps to oop

to create an object-oriented program:

- create one or more classes
- make objects from those classes (i.e., "instantiate" objects)
- tell the objects what to do

what the objects do determines the behaviour of the program

a simple class: Tamagotchi

let's look at some code

the `Tamagotchi` class will represent a simple version of the child's toy by the same name

our Tamagotchi is a virtual pet that must be fed or it will die

the class file

first, create a text file named `Tamagotchi.as`

the text file will contain our class

the class statement

to create a new class, use the class statement:

class Tamagotchi { }

the class name must match the file name (`Tamagotchi.as`) exactly, including case-sensitivty!!

methods: the class's behavior

our `Tamagotchi` can be fed, digest food, and die

accordingly, the `Tamagotchi` will have a `feed()`, `digest()`, and `die()` function

functions defined in a class determine the class's behaviours

terminology: a function defined in a class is called a method

the digest() method

let's add the `digest()` method to `Tamagotchi` class

class Tamagotchi { private function digest () { trace("The Tamagotchi is digesting..."); } }

logic first, display later

for now, the `digest()` method only print a message in the Output window

but conceptually, the `Tamagotchi` already has the ability to digest food

later, we'll define how digesting should look on screen

display is an implementation detail

public versus private

notice the keyword "private" in our method definition:

private function digest () { trace("The Tamagotchi is digesting..."); }

methods defined with the `private` keyword can only be invoked with in the class in which they are defined

private methods prevent external code from misusing the class

for example, no external code can make our `Tamagotchi` digest too often

in contrast, methods defined with the `public` keyword can be invoked from anywhere in a program

creating an object

now that we have a simple class, we can already use it to create objects

our object will be an individual `Tamagotchi`, based on the blueprint provided by the `Tamagotchi` class

to create an object, we use this code:

new ClassName()

for example, this code creates an object from our `Tamagotchi` class:

new Tamagotchi()

storing objects

the code `new Tamagotchi()` returns a new `Tamagotchi` object

normally we want to store the new object somewhere, such as in a variable

for example, this code stores the new `Tamagotchi` object in a variable named `pet`

var pet = new Tamagotchi();

the technical name for "creating an object" is "instantiation"

accessing a class from a .fla file

we'll instantiate our `Tamagotchi` object in a flash document called `tamagotchi.fla`

to use a class such as `Tamagotchi` in a `.fla` file, flash must know the location of the class file (`.as` file)

flash automatically finds all classes whose `.as` files reside in the same directory as the `.fla`

to make our `Tamagotchi` class available to `tamagotchi.fla`, we'll put `tamagotchi.fla` in the same directory as `Tamagotchi.as`

then on frame 1 of tamagotchi.fla's timeline, we'll enter this code:

var pet = new Tamagotchi();

other class access options

other ways to make a class accessible to a `.fla` file:

- include it in a component
- add the class file's containing folder to the classpath

our same-directory approach is the easiest, so we'll stick to it for now

no #include required!

note that a class does not have to be `#included` like raw code in an `.as` file does

access to the class is provided automatically by flash

the feed() method

let's add a method, `feed()`, to give the `Tamagotchi` food

for now, use `trace()` as a placeholder, fill in real code later

public function feed (foodItem) { trace("You fed the Tamagotchi."); }

`feed()` is public because external code might need to feed the `Tamagotchi`

invoking methods on an object

we now have a `Tamagotchi` object, stored in the variable `pet`

we can make the `Tamagotchi` do things by invoking its methods

to invoke a method on an object, use this code:

theObject.theMethod()

for example, this code invokes the `feed()` method on our `Tamagotchi` object

pet.feed()

when we export `tamagotchi.swf` from `tamagotchi.fla`, the Output panel displays:

You fed the Tamagotchi.

what we've learned so far...

to review, the following code:

- makes a new `Tamagotchi`
- stores it in pet
- gives the pet some food

var pet = new Tamagotchi(); pet.feed();

eventually, we'll create a user interface for controlling the pet

calling the wrong method

what happens if we invoke a method on pet that doesn't exist

for example, suppose we miskenly assume that our `Tamagotchi` can sing:

pet.sing()

when we export the movie, nothing happens

or, suppose we make a typo when calling a method:

pet.fede()

the `Tamagotchi` class does not define methods named `sing()` or `fede()`, so the method invocations fail

datatypes and calling the right method

if flash knows what type of object is stored in a variable, it can tell us when we invoke a non-existent method

but flash can't guess the type on its own, we must tell flash the type manually

telling flash the type of a variable is called "declaring the datatype" of the variable

to declare a datatype we use this code:

var someVariable:ClassName;

Tamagotchis can't sing

now let's declare `pet`'s datatype so flash can warn us when we invoke a non-existent method

var pet:Tamagotchi = new Tamagotchi();

now when we invoke pet.sing(), flash tells us `Tamagotchi` objects can't sing:

pet.sing(); **Error** Scene=Scene 1, layer=Layer 1, frame=1:Line 4: There is no method with the name 'sing'. pet.sing();

maintaining state

so far, our `Tamagotchi` will live forever

it never gets hungry

we need to monitor the `Tamagotchi`'s hunger

in other words, we need to maintain the `Tamagotchi`'s state (i.e., current condition)

we need variables

variables defined in a class describe an object's current state

when defined in a class, variables are called properties

Tamagotchi's properties

our `Tamagotchi` class needs to track:

- amount of hunger (current calories)
- the `Tamagotchi`'s name

here are the corresponding properties:

private var currentCalories:Number; private var name:String;

storing the right type of data

take another look at the `currentCalories` property definition:

private var currentCalories:Number;

notice that it includes includes a datatype declaration (`:Number`)

a property can only store data matching its declared datatype

for example, the property `currentCalories` can only store a number, while `name` can only store a string

type mismatch errors

when the wrong type of data is assigned to a property, flash displays a "type mismatch error"

for example, this code:

currentCalories = "just a few";

causes this error:

Type mismatch in assignment statement: found String where Number is required. currentCalories = "just a few";

the code so far

here's our `Tamagotchi` class so far, with the three properties added:

class Tamagotchi { private var currentCalories:Number; private var name:String; private function digest () { trace("The Tamagotchi is digesting..."); } public function feed () { trace("You fed the Tamagtochi..."); } }

notice that, by convention, properties should be defined before methods

private properties

take a closer look at the `name` property definition

private var name:String;

that property definition uses the keyword `private`

private properties can only be assigned and retrieved from within the class in which they are defined

my properties are none of your business

suppose we want to know the name of our `Tamagotchi`

if we try to retrieve the `name` property directly, like this

trace(pet.name);

flash displays this error:

The member is private and cannot be accessed.

(terminology: "member" means method or property)

outside of the `Tamagotchi` class, Flash will not let us retrieve the `name` property

accessor methods

so how do you retrieve or modify the state of an object externally without setting properties?

using accessor methods

accessor methods set and retieve properties

a property-assignment accessor

this accessor sets the name of a `Tamagotchi`

public function setName (newName) { name = newName; }

notice that the property `name` is referred to directly within the method

using an assignment accessor

to give a `Tamagotchi` a new name, we call the `setName()` method instead of assigning the `name` property a value

for example, we use:

pet.setName("yamada");

not:

pet.name = "yamada";

a property-retrieval accessor

this accessor returns the current name of a `Tamagotchi`

public function getName () { return name; }

to retrieve a `Tamagotchi`'s name, we call the `getName()` method instead of accessing the name property

for example, we use:

trace(pet.getName());

not:

trace(pet.name);

encapsulation

properties that are not externally accessible are encapsulated by their class

similarly, the source code in methods is encapsulated by its class

conceptually, encapsulation means hiding the internal details of a class's operation

goal of encapsulation: use an object without understanding or depending on its internal details

public methods are the tools you use to control the object

build classes like cars are built for drivers...

...drivers don't need to know how a car engine works to use a car--they just press the acceleration pedal

benefits of encapsulation (assignment filtering)

prevents external code from assigning values to properties that would cause an object to malfunction

for example, a user might give a `Tamagotchi` a name that won't fit into a database

filtering the assignment through `setName()` gives the class a chance to restrict a property's value to a certain range

for example, this version of `setName()` checks displays an error for names that are too long or too short

public function setName (newName) { if (newName.length > 20) { trace("Warning: specified name is too long."); newName = newName.substr(0, 20); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; }

benefits of encapsulation (retrieval filtering)

encapsulation allows a class to adjust a property's value before returning it

the accessor method might return:

- a different value than the property
- a default value if the property is not set

this version of `getName()` returns the string "Unnamed Pet" for `Tamagotchi`s whose names have not yet been set

public function getName () { if (name == null) { return "Unnamed Pet"; } else { return name; } }

benefits of encapsulation (easy refactoring)

encapsulation prevents external code from becoming dependent on specific implementation details

lets you change a class internally ("refactor") without breaking external code that uses the class

for example, if we change the property `name` to `firstName`, any code that uses the `name` property directly will break

but code that uses `setName()` will not break

if a car's engine changes from gasoline to solar power or hydrogen, the driver's use of the car should not be affected

the driver should still use the acceleration pedal to speed the car up

this code is not encapsulated, and should be avoided:

car.gasolinePerSecond = 100;

this code is preferred because it will still work even if the car's energy source changes:

car.accelerate();

constructor functions

when we create a new `Tamagotchi`, we want to be able to name it

in other words, we want to be able to set the state of the object when we create it

to initialize an object at creation time, we use a constructor function

a constructor function runs automatically when an object is created

the code in a constructor function can initialize the object by setting its properties and invoking its methods

Tamagotchi's constructor

here's a constructor function for our `Tamagotchi` class

public function Tamagotchi (petName) { setName(petName); }

all constructors must have the exact same name as the class (case-sensitivity matters)

our constructor accepts a parameter used to set the `Tamagotchi`'s name

notice that the name property is set via `setName()` even within the class

initializing an object

we can now name our `Tamagotchi` when we create it, as follows:

var pet:Tamagotchi = new Tamagotchi("yamada");

the string "yamada" is passed to the constructor function

the constructor function uses the string to set the `Tamagotchi`'s name

class properties

to store information that applies to an entire class of objects, not just to a specific object, use class properties (also called static properties)

class properties contain information that is globally required by all objects of a class, and does not vary between objects

for example:

- the default size of a dialog box in an application
- the maximum speed of a car in a racing game

class property syntax

class properties are defined with the `static` attribute

for example, this code creates a class property named `x` that stores a numeric value

private static var x:Number;

when referring to class properties, include the class name (e.g., `SomeClass.someProp`, not just `someProp`)

magic values

our `setName()` and `getName()` methods both use literal values

`setName()` uses the number 20:

public function setName (newName) { if (newName.length > 20) { ...

`getName()` uses the string "Unnamed Pet"

public function getName () { if (name == null) { return "Unnamed Pet"; ...

unexplained literal values such as 20 and "Unnamed Pet" used in methods are known as magic values (magic strings, magic numbers, etc)

magic values are bad

magic values are discouraged because:

- their purpose is not self-evident
- they are error prone because they are decentralized

for example, to change the maximum name length we must change "20" in two places--easy to miss one

storing magic values in class properties

class properties are ideal for storing magic values

for example, every `Tamagotchi` object has the same maximum name length

therefore, create a class property to store the maximum name length:

private static var MAX_NAME_LENGTH:Number = 20;

then update the `setName()` method to use the class property:

public function setName (newName) { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; }

now the code is much easier to read and change!

storing default values in class properties

any `Tamagotchi` without a name is called "Unnamed Pet"

therefore, create a class property to store the default name

private static var DEFAULT_NAME:String = "Unnamed Pet";

then update `getName()` to use the class property:

public function getName () { if (name == null) { return Tamagotchi.DEFAULT_NAME; } else { return name; } }

use CAPITAL LETTERS when naming class properties with fixed values

class properties don't change on a per-instance basis

therefore, class property values are often set at declaration time, not in the class constructor

the code so far

here's our `Tamagotchi` class so far:

class Tamagotchi { private var currentCalories:Number; private var name:String; private static var MAX_NAME_LENGTH:Number = 20; private static var DEFAULT_NAME:String = "Unnamed Pet"; public function Tamagotchi (petName) { setName(petName); } private function digest () { trace("The Tamagotchi is digesting..."); } public function feed (foodItem) { trace("You fed the Tamagotchi."); } public function setName (newName) { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; } public function getName () { if (name == null) { return Tamagotchi.DEFAULT_NAME; } else { return name; } } }

bringing the Tamagotchi to life

to simulate the `Tamagotchi`'s life, we need to:

- give the `Tamagotchi` a maximum amount of calories
- decrease calories over time

maximum calories

every `Tamagotchi` object has the same maximum number of calories

hence, implement as a class property

private static var MAX_CALORIES:Number = 2000;

calorie consumption rate

every `Tamagotchi` consumes calories at the same rate: 100 per second

store the calorie consumption rate in a class property:

private static var CALORIES_PER_SECOND:Number = 100;

alive or dead?

at any point in time, we need a quick way to check if the Tamagotchi is alive

hence, create a Boolean property to store life/death status:

private var isAlive:Boolean;

Tamagotchi birth

when a new `Tamagotchi` object is created, the constructor starts its life-cycle

previously, the constructor looked like this:

public function Tamagotchi (petName) { setName(petName); }

now the constructor also:

- gives the `Tamagotchi` a full stomach

currentCalories = Tamagotchi.MAX_CALORIES;

- initializes `isAlive` to `true` (the `Tamagotchi` is not dead)

isAlive = true;

- tells the `Tamagotchi` to digest once per second

digestIntervalID = setInterval(this, "digest", 1000);

you stop digesting when you die

this code calls `digest()` every 1000 milliseconds:

setInterval(this, "digest", 1000);

`setInterval()` returns an ID number used to stop calling the method later

we store the interval ID for the `digest()` method call in a new property:

private var digestIntervalID:Number;

when the `Tamagotchi` dies, we'll use `digestIntervalID` to stop calling `digest()`

updated Tamagotchi constructor

here's the full constructor code listing so far:

public function Tamagotchi (petName) { setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); }

displaying health status

the `displayHealthStatus()` method outputs the `Tamagotchi`'s current health

for now, our `Tamagotchi` displays its health status in the Flash Output window

private function displayHealthStatus () { var caloriePercentage:Number = Math.floor((currentCalories /Tamagotchi.MAX_CALORIES)*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); }

remember, display is an implementation detail! we'll add graphics later

more important to get the logic working

Tamagotchi death

when a `Tamagotchi` dies, we call the `die()` method

`die()` has three reponsibilities:

- set the state of the object to inactive

clearInterval(digestIntervalID); isAlive = false;

- release any resources used by the class

Key.addListener(this);

- notify the user of the `Tamagotchi`'s death

trace(getName() + " has died.");

die() code listing

here's the full code listing for `die()`:

private function die (deathType) { clearInterval(digestIntervalID); isAlive = false; Key.addListener(this); trace(getName() + " has died."); }

consuming calories

every second, the `digest()` method is called

`digest()` reduces the current calories

if the calories run out, `digest()` calls `die()`

otherwise, `digest()` requests a display update

private function digest () { trace("The Tamagotchi is digesting..."); currentCalories -= Tamagotchi.CALORIES_PER_SECOND; if (currentCalories <= 0) { die(); } else { displayHealthStatus(); } }

the code so far

our Tamagotchi now consumes calories and can die

here's the code for the `Tamagotchi` class so far:

class Tamagotchi { private var currentCalories:Number; private var name:String; private var isAlive:Boolean; private var digestIntervalID:Number; private static var MAX_NAME_LENGTH:Number = 20; private static var DEFAULT_NAME:String = "Unnamed Pet"; private static var MAX_CALORIES:Number = 2000; private static var CALORIES_PER_SECOND:Number = 100; public function Tamagotchi (petName) { setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); } private function digest () { trace("The Tamagotchi is digesting..."); currentCalories -= Tamagotchi.CALORIES_PER_SECOND; if (currentCalories <= 0) { die(); } else { displayHealthStatus(); } } public function feed (foodItem) { trace("You fed the Tamagotchi."); } private function displayHealthStatus () { var caloriePercentage:Number = Math.floor((currentCalories /Tamagotchi.MAX_CALORIES)*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); } private function die (deathType) { clearInterval(digestIntervalID); isAlive = false; Key.addListener(this); trace(getName() + " has died."); } public function setName (newName) { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; } public function getName () { if (name == null) { return Tamagotchi.DEFAULT_NAME; } else { return name; } } }

Tamagotchi food: sushi and apples

our `Tamagotchi` eats two types of food: sushi and apples

each type of food will be represented by a class: `Sushi` and `Apple`

factoring out the similarities

our `Sushi` and `Apple` class are nearly identical

each class must manage:

- the food's name
- the food's calorie value

`Sushi` and `Apple` are similar because both are types of food

inheritance

in OOP we model "types of things" using inheritance

inheritance is used to establish hierarchical class relationships

in inheritance, one class (the subclass) adopts (inherits) the features of another class (the superclass)

many subclasses can inherit the features of a single superclass

for example, we'll create a general class called `Food`

`Sushi` and `Apple` will both inherit the features of `Food`

the Food class (the superclass)

here's the source code for `Food`:

class Food { private var calories; private var name; public function Food (initialCalories:Number) { setCalories(initialCalories); } public function getCalories () { return calories; } public function setCalories (newCalories) { calories = newCalories; } public function getName () { return name; } public function setName (newName) { name = newName; } }

the Sushi class (a subclass)

we use the keyword `extends` to make one class inherit from another class

class Sushi extends Food { }

now `Sushi` inherits the methods and properties of `Food`

`Sushi` also adds one property of its own, indicating its default calorie value

class Sushi extends Food { private static var DEFAULT_CALORIES = 500; }

calling the superclass constructor

every subclass must invoke its superclass's constructor function

within the subclass constructor, use `super()` to invoke the superclass constructor

`Sushi` provides the `Food` constructor with a specific calorie value (500)

`Sushi`'s constructor also sets the name of the food

class Sushi extends Food { private static var DEFAULT_CALORIES = 500; public function Sushi () { super(Sushi.DEFAULT_CALORIES); setName("Sushi"); } }

the Apple class

`Apple` is identical to `Sushi`, but has its own calorie value and name

class Apple extends Food { private static var DEFAULT_CALORIES = 100; public function Apple () { super(Apple.DEFAULT_CALORIES); setName("Apple"); } }

both `Apple` and `Sushi` borrow the methods and properties of `Food`

hence, if the `Food` class changes, `Apple` and `Sushi` automatically change too

method return types

as with properties, datatypes can be declared for method return values

for example, our `getName()` method can indicate that it returns a `String` value as follows:

public function getName ():String { // ...code not shown }

a method that does not return a value has a return type of "Void" (capital "V"!)

private function displayHealthStatus ():Void { // ...code not shown }

why declare a method return type?

benefits of declaring a method's return type:

- warning from the compiler if a return statement is not present or returns the wrong type of value

public function getName ():String { return calorieCount; // Recall that calorieCount is a number. }

**Error** Line 89: The expression returned must match the function's return type.

- warning from the compiler if the return value is assigned to the wrong type of variable

var petCalories:Number = pet.getName();

**Error** Line 2: Type mismatch in assignment statement: found String where Number is required.

the programmer probably wants `getCalories()` not `getName()` (assuming `getCalories()` exists)

method parameter types

datatypes can also be declared for method parameters

public function setName (newName:String):Void { // ...code not shown }

benefit of declaring parameter types:

- warning from the compiler if the wrong type of data is passed to the method

// ERROR! setName() requires a String, not a Number pet.setName(25);

// ERROR! setName() requires a String, not a Name object pet.setName(new Name("yamada"));

what can you feed a Tamagotchi?

earlier we defined a basic feed() method:

public function feed (foodItem) { trace("You fed the Tamagotchi."); }

now we can limit what a `Tamagotchi` can eat by setting `foodItem`'s datatype

`Tamagotchi`s can only eat instances of `Food` or of `Food` subclasses

public function feed (foodItem:Food) { trace("You fed the Tamagotchi."); }

adding calories at feeding time

now that our food classes are ready, we can give the `Tamagotchi` calories when `feed()` is called

as a precaution, we also prevent dead `Tamagotchi`s from being fed

public function feed (foodItem:Food):Void { if (isAlive == false) { trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate the " + foodItem.getName() + " (" + foodItem.getCalories() + " calories)."); if (foodItem.getCalories() + currentCalories > Tamagotchi.MAX_CALORIES) { currentCalories = Tamagotchi.MAX_CALORIES; } else { currentCalories += foodItem.getCalories(); } }

the code so far

here are the classes in our Tamagotchi application, complete with method parameter and return types:

class Tamagotchi { private var currentCalories:Number; private var name:String; private var isAlive:Boolean; private var digestIntervalID:Number; private static var MAX_NAME_LENGTH:Number = 20; private static var DEFAULT_NAME:String = "Unnamed Pet"; private static var MAX_CALORIES:Number = 2000; private static var CALORIES_PER_SECOND:Number = 100; public function Tamagotchi (petName:String) { setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); } private function digest ():Void { trace("The Tamagotchi is digesting..."); currentCalories -= Tamagotchi.CALORIES_PER_SECOND; if (currentCalories <= 0) { die(); } else { displayHealthStatus(); } } public function feed (foodItem:Food):Void { if (isAlive == false) { trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate the " + foodItem.getName() + " (" + foodItem.getCalories() + " calories)."); if (foodItem.getCalories() + currentCalories > Tamagotchi.MAX_CALORIES) { currentCalories = Tamagotchi.MAX_CALORIES; } else { currentCalories += foodItem.getCalories(); } } private function displayHealthStatus ():Void { var caloriePercentage:Number = Math.floor((currentCalories /Tamagotchi.MAX_CALORIES)*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); } private function die (deathType:Number):Void { clearInterval(digestIntervalID); isAlive = false; Key.addListener(this); trace(getName() + " has died."); } public function setName (newName:String):Void { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; } public function getName ():String { if (name == null) { return Tamagotchi.DEFAULT_NAME; } else { return name; } } }

class Food { private var calories:Number; private var name:String; public function Food (initialCalories:Number) { setCalories(initialCalories); } public function getCalories ():Number { return calories; } public function setCalories (newCalories:Number):Void { calories = newCalories; } public function getName ():String { return name; } public function setName (newName:String):Void { name = newName; } }

class Sushi extends Food { private static var DEFAULT_CALORIES:Number = 500; public function Sushi () { super(Sushi.DEFAULT_CALORIES); setName("Sushi"); } }

class Apple extends Food { private static var DEFAULT_CALORIES:Number = 100; public function Apple () { super(Apple.DEFAULT_CALORIES); setName("Apple"); } }

handling user input

our `Tamagotchi` now works, but needs code to handle user input

for simplicity, we'll make the `Tamagotchi` keyboard controlled

the 'a' key will feed the `Tamagotchi` an apple

the 's' key will feed the `Tamagotchi` sushi

responding to the keyboard

a new method, `onKeyDown()`, will handle keyboard events

private function onKeyDown ():Void { if (Key.getCode() == 65) { feed(new Apple()); } else if (Key.getCode() == 83) { feed(new Sushi()); } }

registering for keyboard events

in the constructor, we'll tell our `Tamagotchi` to listen for keystrokes:

Key.addListener(this);

"this" is the current `Tamagotchi` object

so the constructor now looks like this:

public function Tamagotchi (petName:String) { setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); Key.addListener(this); }

adding graphical display

now that the `Tamagotchi`'s logic is fully functional, we can add graphics

to display the `Tamagotchi` graphically we must:

- create a movie clip containing Tamagotchi graphics
- add two new movie clip-related properties to our class
- update our class's constructor, `setName()`, `die()`, and `displayHealthStatus()` methods

TamagotchiSymbol movie clip

movie clip that contains `Tamagotchi` graphics is called `TamagotchiSymbol`

`TamagotchiSymbol` is exported as "TamagotchiSymbol" so it can be attached by the `Tamagotchi` class (via `attachMovie()`)

`TamagotchiSymbol` contains *no code*!, only graphics

the contents of TamagotchiSymbol

`TamagotchiSymbol` contains a text field for the `Tamagotchi`'s name

`TamagotchiSymbol` contains one nested clips, `hungerIcon`

labeled frames on the timeline provide different graphical states

`TamagotchiSymbol` has two states: "alive" and "dead"

`hungerIcon` has three states: "full", "hungry", and "starving"

movie clip-management properties

two new properties in the `Tamagotchi` class

`clip` stores an instance of the `TamagotchiSymbol` movie clip

private var clip:MovieClip;

`SYMBOL_ID` stores the identifier of the `TamagotchiSymbol` movie clip

private static var SYMBOL_ID:String = "TamagotchiSymbol";

`SYMBOL_ID` is the same for all `Tamagotchi` instances, so it's a class property

TamagotchiSymbol constructor updates

the constructor now creates and stores an instance of `TamagotchiSymbol`

the constructor's parameters tell it where to put the instance in the Flash movie

public function Tamagotchi (petName:String, target:MovieClip, depth:Number, x:Number, y:Number) { clip = target.attachMovie(Tamagotchi.SYMBOL_ID, "tamagotchi" + depth, depth); clip._x = x; clip._y = y; setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); Key.addListener(this); }

creating the TamagotchiSymbol instance

when creating a new Tamagotchi, we must now specify the parent, depth, and position for the Tamagotchi in the movie:

var pet:Tamagotchi = new Tamagotchi("Yamada", this, 0, 100, 100);

displaying the Tamagotchi's name

the `setName()` method now must display the `Tamagotchi`'s name in `TamagotchiSymbol`:

public function setName (newName:String):Void { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; clip.name_txt.text = name; }

notice the flexibility of the accessor method

details of name-setting operation can change internally without affecting external use

displaying health icons

update `displayHealthStatus()` so it displays health state visually via `hungerIcon` and `exhaustionIcon`

private function displayHealthStatus ():Void { var caloriePercentage:Number = Math.floor((currentCalories /Tamagotchi.MAX_CALORIES)*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage < 20) { clip.hungerIcon.gotoAndStop("starving"); } else if (caloriePercentage < 50) { clip.hungerIcon.gotoAndStop("hungry"); } else { clip.hungerIcon.gotoAndStop("full"); } }

display system is encapsulated

display system can easily change without affecting external use of `Tamagotchi` objects

displaying Tamagotchi death

update `die()` method so it displays death visually

private function die (deathType:Number):Void { clearInterval(digestIntervalID); isAlive = false; Key.addListener(this); trace(getName() + " has died."); clip.gotoAndStop("dead"); }

code complete

here's the final `Tamagotchi` class code

class Tamagotchi { private var currentCalories:Number; private var name:String; private var isAlive:Boolean; private var digestIntervalID:Number; private var clip:MovieClip; private static var MAX_NAME_LENGTH:Number = 20; private static var DEFAULT_NAME:String = "Unnamed Pet"; private static var MAX_CALORIES:Number = 2000; private static var CALORIES_PER_SECOND:Number = 100; private static var SYMBOL_ID:String = "TamagotchiSymbol"; public function Tamagotchi (petName:String, target:MovieClip, depth:Number, x:Number, y:Number) { clip = target.attachMovie(Tamagotchi.SYMBOL_ID, "tamagotchi" + depth, depth); clip._x = x; clip._y = y; setName(petName); currentCalories = Tamagotchi.MAX_CALORIES; isAlive = true; digestIntervalID = setInterval(this, "digest", 1000); Key.addListener(this); } public function feed (foodItem:Food):Void { if (isAlive == false) { trace(getName() + " is dead. You can't feed it."); return; } trace(getName() + " ate the " + foodItem.getName() + " (" + foodItem.getCalories() + " calories)."); if (foodItem.getCalories() + currentCalories > Tamagotchi.MAX_CALORIES) { currentCalories = Tamagotchi.MAX_CALORIES; } else { currentCalories += foodItem.getCalories(); } } public function setName (newName:String):Void { if (newName.length > Tamagotchi.MAX_NAME_LENGTH) { trace("Warning: specified name is too long."); newName = newName.substr(0, Tamagotchi.MAX_NAME_LENGTH); } else if (newName == "") { trace("Warning: specified name is too short."); return; } name = newName; clip.name_txt.text = name; } public function getName ():String { if (name == null) { return Tamagotchi.DEFAULT_NAME; } else { return name; } } private function die (deathType:Number):Void { clearInterval(digestIntervalID); isAlive = false; Key.addListener(this); trace(getName() + " has died."); clip.gotoAndStop("dead"); } private function displayHealthStatus ():Void { var caloriePercentage:Number = Math.floor((currentCalories /Tamagotchi.MAX_CALORIES)*100); trace(getName() + " has " + currentCalories + " calories" + " (" + caloriePercentage + "% of its food) remaining."); if (caloriePercentage < 20) { clip.hungerIcon.gotoAndStop("starving"); } else if (caloriePercentage < 50) { clip.hungerIcon.gotoAndStop("hungry"); } else { clip.hungerIcon.gotoAndStop("full"); } } private function digest ():Void { trace("The Tamagotchi is digesting..."); currentCalories -= Tamagotchi.CALORIES_PER_SECOND; if (currentCalories <= 0) { die(); } else { displayHealthStatus(); } } private function onKeyDown ():Void { if (Key.getCode() == 65) { feed(new Apple()); } else if (Key.getCode() == 83) { feed(new Sushi()); } } }

known limitations

same keyboard inputs will affect multiple `Tamagotchi` objects

in a multi-Tamagotchi application, need to add input focus support for interaction with a single Tamagotchi at a time

`Tamagotchi` class's use of `TamagotchiSymbol` movie clip could be better encapsulated

no way to "restart" a dead `Tamagotchi`

no way to reposition a `Tamagotchi` after creating it

no mouse control (e.g., can't click hunger icon to feed Tamagotchi)