Week 10
Note on static and dynamic type
Zuul
Variables (including Array & ArrayList components) have static type; objects have dynamic type.
Or, to phrase that the other way, the type of a variable is called static,
because you can tell the type "statically", without running the program
(the compiler can tell). The type of the contents is called dynamic because you have to run the program to identify it.
The type of any variable can be determined by the compiler, and all
type-checking must be successful (with the understanding that a
subclass value can be assigned to a base-class variable).
Until recently, the dynamic type of an object always matched the static
type of the variable that held it. But now the dynamic type can be a subclass of the static type.
Another term for this is polymorphism: one variable (of one static type) can have "many forms": can hold any of several subclass types.
Changes to Zuul-better to create LockSerpent
- Add new rooms
- Move room declarations from being local to createRooms() to being fields in the Game class, so we can refer to them by name
from anywhere in Game.java
- Add Room.setDescription, so a room description can be changed later (eg the steamTunnel)
- Create serpent_happy flag: should be in a room, but we can't do that! (Well, we could have steamtunnel.getSerpentHappy() return true or false, but that doesn't entirely help.)
- Add serpentCommand check to processCommand, ahead of others,
so processCommand can still handle anything serpentCommand decides to pass on.
Violations of rules:
cohesion is not really doing well here. The concept is that one method
should do "one thing", and we're really forced to address lots of
little checks. (Note that one can argue that this is acceptable cohesion, though; it is all tied to command processing.)
responsibility-driven design is faring even worse: processCommand is
dealing with too many things that should not be its responsibility.
Maybe the easiest problem to point to is coupling: we're trying to create a Room with special behavior, but we're having to put the code for that into class Game (where processCommand is)
Another way to look at coupling, which some people like to make a
separate rule, is that it is important to avoid lots of if-statements
that test for a specific object (eg if (currentRoom == steamTunnel) ...
or if (theShape instanceof Rectangle) ...). Such code generally
indicates
- lack of responsibility-driven design
- lack of localization: all the changes for a new object should be in one place.
- poor coupling (to add a new object means modifying all those conditions
- awkward cohesion: your big if-statement is arguably doing one thing, but it's much more complex than necessary.
- code duplication: each place you need to test an object will have the same if-code.
- code that is not easily extendible
Zuul 3.0
We finally move responsibility to rooms.
Note processCommand() here: all the weird stuff is out, replaced with
Room r = currentRoom.respond(command, inventory)
Look at Room.respond(Command c, ArrayList<Item> inventory), and goRoom()
which it calls for the command GO. The basic strategy is that figuring
out how to respond to a user command has been moved from the main class
(Game) to the Room class.
Also look at subclass constructors!
Now look at how respond() works in LockedRoom and SerpentRoom.
Start with LockedRoom: note second field of constructor.
Note how LockedRoom.respond() does relatively little; it only handles room-specific situations. All other responses are passed back to super.respond().
What can go wrong? Well, it took a while to figure out how to handle
GO, and in an earlier version I had the main loop distinguish between
responses to GO and responses to all other commands, calling a
different room method in each case. That proved unnecessary!
Another problem: what to do about inventory.
It's a field that, in this version, belongs in Game. How do we access
it within a special Room? We can't even add an accessor getInventory()
to Game, because we don't have access to the Game object when we're in
a Room class. Yet we need to access inventory just to do TAKE and DROP!
My solution: pass inventory as a parameter to Room.respond(). That way,
room responses can take into account the player's inventory.
Try with GIVE COOKIE, and then after further exploring the basement, try GIVE GLOWING_DONUT
Examples from v 3.0
LockedRoom:
constructor has a lockedDir field
respond() just checks
if (commandWord.equals("go")
&& direction.equals(locked_dir)
&& Game.findByName("key", inv) == null)
Note that all keys are the same here.
SerpentRoom:
No special constructor fields
GO: if (commandWord.equals("go")
&& !serpent_happy
&& object != null
&& object.equals("up"))
GIVE:
note inventory checks
note how we handle GIVEing different things
note how we add the key or snake
TAKE SERPENT
simple addition to get a better response than "You can't take that!"
Wednesday:
This monday: project 1
You will have extra time for this.
There is a partner option: if you wish, you can do the project with a partner. You'll have to do more (eg add three puzzle rooms instead of two).
ValveRoom (Version 3.1)
Note how we handle GO DOWN and TURN VALVE
Addition: require EAT COOKIE before you can TURN VALVE, and it must be the immediately preceding turn.
turn valve:
check state of hasEaten; if it's not true, say we're "too weak and hungry"
hasEaten: set to false after every response
trick: avoid all those inconvenient returns?
eat cookie
do we have the cookie?
remove from inventory
hasEaten = true;
eat glowing_donut??
Note how we can sneak around "from behind" here!
Do we care about that?
How can we fix this?
- create two rooms that block your way until the water is drained; the route between them is inaccessible.
- have the room share a common field, "valveTurned", that determines passability
- have the valve be "visible" in only one of the rooms
How can we do this?
Zuul 4.2
DarkRoom and the Flashlight
DustyRoom
ZooRoom
DarkRoom
Flashlight; Light generally
Note that an Item can now have added attributes besides its name! Yet TAKE, DROP still work without changes.
Adding a Player class
Holds:
- inventory
- currentRoom (because that's a player attribute!)
- isAlive (if you want it to be possible for the player to die)
Note that isAlive would have to be checked in Game.java
What can we do with this approach to puzzles?
One limitation is the two-word command; we can't say
UNLOCK CHEST WITH KEY
Would we ever need that?
Basic puzzle ideas:
Puzzles usually require us to have found a particular object / item.
The item may suggest its use, such as the "cookie", and the valveroom
hint "you are too weak and hungry".
Suggestions as to a particular verb should be built into the game.
Ideally, the user should not be left groping with synonyms. Some game
guides would list all possible verbs in the book.