Dynamic Tables by Jesse McGrew


This extension allows the number of rows in a table to be changed at runtime. For example:

change the Table of Ice Cream Flavors to have 31 rows;

Using that phrase, we may change the size of any table to any non-negative number of rows at any time. After resizing the table, the data that was stored in the table will still be there, the usual table phrases like "the number of rows in Table of Ice Cream Flavors" will reflect the new size, and in general the table will behave exactly as if we had defined it with 31 rows in the source code.

The only way to tell that the table has been resized at all is to use the new phrase "the original number of rows in Table of Ice Cream Flavors", which will return the number of rows that were defined in the source code. If the table hasn't been resized, the original number of rows will be the same as the number of rows.

When we make a table larger, new blank rows are added at the end of the table.

When we make a table smaller, rows are deleted from the end of the table. If the deleted rows aren't blank, the data stored there will be lost. To avoid losing data unnecessarily, we might want to sort the table before making it smaller, since sorting will push all the blank rows to the end.

We can even change a table to have fewer rows than it was defined with. This probably isn't useful, though, since it will use more memory than if we had left the table at its original size.

Changing the size of a table will allocate memory from Inform's heap, the space that's also used for indexed text, lists, and stored actions. The operation might fail if the heap is too full, leaving the table at its old size; this extension won't print an error message, but we can check the number of rows afterward to be sure it succeeded. If we change a table back to its original size, the memory will be reclaimed and the table will go back into the space it originally occupied.

If the game doesn't use indexed text, lists, or stored actions at all, Inform won't normally create a heap, so this extension forces it to create a small heap in that situation (half the normal minimum size). If that isn't big enough, we should add a stored action variable somewhere to make Inform create the heap itself, and then the usual settings like "Use dynamic memory allocation of at least 16384" will work if we need even more space.

Section: Creating New Tables

This extension also allows us to create new tables at runtime, which can be referred to with table-name variables or properties. For example:

let T be a new table with columns { speaker, quip } and 10 blank rows;

After that phrase, "T" is now a table-name variable pointing to a new table, containing two columns and ten rows. Despite the variable's type being called "table-name", the new table has no actual name, and "say T" will show "** Dynamically created table **" instead.

Note that we have two obligations if we use this syntax. First, we must define the columns ("speaker" and "quip" in this example) in another table in the source code somewhere, so that Inform knows what kinds of value are supposed to go there. We can reuse columns from another table we're already using, or we can define a new table just for this purpose; once the columns have been defined, we can create as many new tables using them as we need.

Second, we must not lose the value of "T", since it's the only way to refer to the new table. If the table is going to stay in use until the end of the game, we should store it in a global variable or a property. If we only need the table temporarily, we should return its memory when we're done with it by writing:

deallocate T;

(Note that "T" will no longer be a valid table-name once it's been deallocated, so it must not be used afterward.)

To see whether a particular table-name value points to a table that was created this way, we can write:

if T was dynamically created,

This syntax is useful when we have a table-name variable that might point to a table defined in the source, or maybe a new table, and so we aren't sure whether it needs to be deallocated when we're done with it. It can also be used to check whether the table was created successfully -- if there wasn't enough memory to create the new table, the phrase will return false.

Section: Change Log

Version 2 adds the "new table" and "deallocate table" features.

Version 3 works with Inform 7 version 6E59.

Version 4 fixes a bug where dynamically created tables couldn't be resized.

Version 5 works with (and requires) version 6L38.

Example: * Notlob - A parrot that assigns a value to everything he hears, and repeats the lines back in his preferred order.

The Table of Parrot Quips holds the lines that the parrot has heard, along with a score indicating how much the parrot likes each one, and a usage count to limit the number of times he repeats each one. We use a dynamic table, rather than three lists, because we need to keep the score and usage count associated with the correct text: table sorting keeps rows together, but lists can only be sorted independently of each other.

"Notlob"

Include Dynamic Tables by Jesse McGrew.

Pet Shoppe is a room. "A sign reads: 'If you're looking for pets, look no further! Pets are something we sell and you can buy them from us.'"

The parrot is an animal in the Pet Shoppe. "A parrot is perched here." Instead of buying the parrot, say "Except that one."

Table of Parrot Quips
quip text     quip score     usage count
indexed text     a number     a number

The highest quip score is a number that varies. The highest quip score is 0.

After answering the parrot that:
     let the textual quip be indexed text;
     let the textual quip be the topic understood;
     let the current quip score be the calculated quip score of the textual quip;
     if the number of blank rows in the Table of Parrot Quips is 0, change the Table of Parrot Quips to have (2 * the number of rows in the Table of Parrot Quips) rows;
     choose a blank row in the Table of Parrot Quips;
     now the quip text entry is the textual quip;
     now the quip score entry is the current quip score;
     now the usage count entry is 0;
     sort the Table of Parrot Quips in reverse quip score order;
     if the current quip score is greater than the highest quip score:
         say "The parrot listens intently, whistling with excitement.";
         now the highest quip score is the current quip score;
     otherwise:
         say "The parrot listens."

To decide which number is the calculated quip score of (msg - indexed text):
     let msg be msg in lower case;
     let the result be 0;
     repeat with N running from 1 to the number of characters in msg:
         let C be character number N in msg;
         if C is "p" or C is "a" or C is "r" or C is "o" or C is "t", increase the result by 1;
     decide on the result.

Every turn when the highest quip score is greater than 0:
     choose row 1 in the Table of Parrot Quips;
     say "The parrot squawks, '[quip text entry]'";
     increase the usage count entry by 1;
     if the usage count entry is 3:
         blank out the whole row;
         if the number of filled rows in the Table of Parrot Quips is 0:
             now the highest quip score is 0;
         otherwise:
             sort the Table of Parrot Quips in reverse quip score order;
             choose row 1 in the Table of Parrot Quips;
             now the highest quip score is the quip score entry.

Test me with "say hello polly / say hello polly parrot / say i've got a lovely fresh cuttlefish for you / say pining for the fjords / z / z / z / z".

Example: ** Multiplication - Using the "new table" feature to create a 2D multiplication table using cross references.

A table is a two-dimensional structure already, but you can only access columns by name, not by number. This example shows how to create a table that contains references to other tables, so that we can multiply two numbers by looking up the first one in the master table, cross referencing to another table, and looking up the second number in that other table.

"Multiplication"

Include Dynamic Tables by Jesse McGrew.

Table of Multiplication
factor     cross reference
a number     a table-name
with 19 blank rows.

Table of Dummy Columns
product
a number

When play begins:
     repeat with R running from 1 to 20:
         let the subtable be a new table with columns { factor, product } and 20 blank rows;
         choose row R in the Table of Multiplication;
         now the factor entry is R;
         now the cross reference entry is the subtable;
         repeat with C running from 1 to 20:
             choose row C in the subtable;
             now the factor entry is C;
             now the product entry is R * C.

To look up (X - number) times (Y - number) in the multiplication table:
     if X is a factor listed in the Table of Multiplication:
         let the subtable be the cross reference entry;
         if Y is a factor listed in the subtable:
             say "[X in capitalized words] times [Y in words] is [product entry in words].";
             stop;
     say "That isn't in the multiplication table."

To say (N - number) in capitalized words:
     let temp be indexed text;
     let temp be "[N in words]";
     say temp in sentence case.

The other factor is a number that varies.

After reading a command:
     if the player's command includes "by/times [number]":
         now the other factor is the number understood;
         replace the matched text with "by __num__".

Multiplying is an action applying to one number. Understand "multiply [number] by/times __num__" as multiplying.

Carry out multiplying:
     look up the number understood times the other factor in the multiplication table.

Classroom is a room. "This is a peaceful place in which to multiply numbers by other numbers."

Test me with "multiply 5 times 6 / multiply 12 by 8 / multiply 67 by 91".

(We could do this without the extension by defining all twenty-one tables in the source text, but that's a lot of typing.)