99 Bottles of Beer (Inform 7)

From LiteratePrograms
Jump to: navigation, search
Other implementations: Erlang | Forth | Haskell | Inform 7 | Java | Perl

By shifting phase, mid-verse to mid-verse, a typical repeat of the 99 bottles of beer song is:

 Take one down and pass it around, 98 bottles of beer on the wall.
 
 98 bottles of beer on the wall, 98 bottles of beer.

Contents

[edit] theory

We implement "99 bottles" recursively, using the phrase precedence rules of Inform 7 (in case of ambiguity, the most specific match is chosen) to avoid any explicit alternation (if-thens) in the code.

[edit] practice

[edit] all in all, you're just another bottle on the wall

Inform 7 has a relatively sophisticated condition matching system, but it seems to only work for objects, not numeric values. Therefore we create a wall class as a thin wrapper around the variable (inventory) we use to keep track of the number of bottles.

<<wall definition>>=
A wall is a kind of room.  A wall has a number called the inventory.
To stock (w - a wall) with (n - a number) bottles: change the inventory of w to n.
To drink from (w - a wall): decrease the inventory of w by 1.
typical verses
base case

[edit] we don't need no alternation

The typical verse translates straightforwardly. A peculiarity of Inform is the use of the built-in [s] for simple automatic pluralization.

Question: in which position is the recursive call for act on?

<<typical verses>>=
To say beer count of (w - a wall): say "[inventory of w] bottle[s] of beer".
To act on (w - a wall):
	say "take one down and pass it around, ";
	drink from w;
	say "[beer count of w].[line break]";
	say "[beer count of w] on the wall, [beer count of w].";
	act on w.

By defining empty wall, we can add phrases to handle the base case separately.

<<base case>>=
Definition: a wall is empty if its inventory is zero.
To say beer count of (w - an empty wall): say "no more bottles of beer".
To act on (w - an empty wall): say "go to the store to buy some more, ".

Question: how similar are Inform 7's definitions and conditions to COBOL 88-levels?

[edit] (re)stocking

The (slight) advantage of choosing the recursive over the iterative is that we need neither save nor pass on the initial bottle count for restocking.

<<stocking>>=
To versify on (w - a wall) with (n - a number) bottles:
	stock w with n bottles;
	say "You start to sing:[line break]";
	say "'[beer count of w] on the wall, [beer count of w].";
	act on w;
	stock w with n bottles; say "[beer count of w].'".

[edit] understanding

Of course, now that we've implemented this behavior, we need some way for the player to trigger it. We define a new action, caterwauling to go along with the versify on ... with ... phrase, then retarget the built-in sing verb to trigger our behavior.

<<hook up actions>>=
caterwauling is an action applying to a number.
understand "sing [a number]" as caterwauling.
understand "[a number]" as caterwauling.
instead of singing: change the number understood to 99; try caterwauling.
carry out caterwauling: 
	let n be the number understood;
	versify on the stage with n bottles.

Exercise: this is suitable for our example, but not very good Inform 7.

  • text output should occur in the "report" phase instead of the "carry out" phase for an action.
  • normally, new code-level actions are paired with new verbs.

Write an alternative to this section by re-implementing the verb "to sing", with "check", "carry out" and "report" phases for the singing action behaving appropriately.

[edit] wrapping up

Finally, we add the header boilerplate, an initial location with instructions, and a minimal test suite.

<<99bottles.inform7>>=
"99 bottles" by Dave

wall definition
stocking
hook up actions
The stage is a wall. "type 'sing' to sing '99 bottles of beer'".
test me with "sing".

We can test this either with test me for the full 99 bottles, or sing 3 (or even 3) for a shorter test.

>sing 3
You start to sing:
"3 bottles of beer on the wall, 3 bottles of beer.
take one down and pass it around, 2 bottles of beer.

2 bottles of beer on the wall, 2 bottles of beer.
take one down and pass it around, 1 bottle of beer.

1 bottle of beer on the wall, 1 bottle of beer.
take one down and pass it around, no more bottles of beer.

no more bottles of beer on the wall, no more bottles of beer.
go to the store to buy some more, 3 bottles of beer."
Download code
hijacker
hijacker
hijacker
hijacker