Design help with modeling a simple recipe

Hi, I’m new here. This got huge, so here’s a tl;dr:

  • Is there a data structure similar to a tuple in Inform?
  • Is there a way to model a kind to have a property that is a dynamic list, as opposed to a single value?
  • Stack Overflow says creating dynamic objects is not well-supported by Inform and is heavily discouraged, and that it usually means you’re doing something wrong. Why is that?
  • There’s a Dynamic Objects extension by Jesse McGrew but it was last updated in 2015, and isn’t listed in the extensions library. Is it a bad idea to try and use it?

Ok, now that that’s over with…here’s all the context.

Hi all! I decided to try learning Inform 7, and as part of my learning experiments I’m trying to make a game where you bake a simple loaf of bread. In this experiment, I want quantities, bake time, heat, etc. to matter - and I want it to be possible for the player to mess up. For example, if they add too much salt, it shouldn’t rise properly.

Here’s what it might look like:

> fill 1-tsp with active dry yeast
> pour 1-tsp into mixing bowl
> x mixing bowl
The bowl holds some active dry yeast.
> fill 1-cup with water
> pour 1-cup into mixing bowl
> fill 1-cup with water
> pour 1-cup into mixing bowl
> x mixing bowl
The bowl holds a mixture of active dry yeast and water.
> fill 1-tsp with white sugar
> pour 1-tsp into mixing bowl
> x mixing bowl
The mixing bowl holds a mixture of active dry yeast, water, and white sugar.
> beat mixing bowl
You beat the mixture in the mixing bowl until combined.
> x mixing bowl
The mixing bowl holds a foamy yeast mixture.

I’m having trouble modeling ingredients, however. Essentially, I need to be able to track what ingredients and how much are in a mixing bowl. I also need to be able to appropriately subdivide ingredients, such that you can scoop 1/2 cup of flour out of the flour tub into a small mixing bowl, and 1 cup of flour into a big mixing bowl.

My first thought was I could use Emily Short’s Measured Liquids to do this, modeling each ingredient as a liquid, but after some further investigation it turns out that Measured Liquids only allows one type of liquid per container. It does have various mixing rules, but as far as I can tell overriding the mixing rules is designed around changing what the result of the one liquid is after mixing, and not around allowing multiple liquids. Okay, I thought, is it possible to either modify or selectively reimplement Measured Liquids to make it have multiple liquids?

Here’s how Measured Liquids appears to model a “fluid container”, in pseudocode:

a kind of container called "fluid container" has:
  - <a kind of value called volume> "fluid capacity"
  - <a kind of value called volume> "fluid content"
  - <an entry in table of liquids> "liquid"

Essentially, each fluid container has 3 values, how much it can store, how much it has, and what liquid is stored.

What I’d want is something like this:

a tuple of "ingredient-and-volume" has:
  - <an entry in the table of liquids> "liquid"
  - <a kind of value called volume> "ingredient volume"

a kind of ingredient container called "ingredient container" has:
  - <a list of "ingredient-and-volume"> ingredients-and-volumes
  - <a kind of value called volume> "ingredient capacity"

Here, instead of having a single liquid and a single volume, I’d have a list of ingredients and a list of volumes. I tried looking up how to create tuple and list data structures, but I found information on how to have lists of concrete objects (http://inform7.com/book/WI_21_6.html) and that lead me to suspect that I was totally barking up the wrong tree, or was thinking with a completely different mental model. Possibly this is my normal programming knowledge crashing headfirst into Inform and getting a concussion. My question is, is the model above possible in Inform, or, like, is my brain just not on the wavelength entirely?

My second try was to model each ingredient as an object with a volume. So, you’d start with a “flour” object with volume “7 cups” in the flour tub, and when you scoop 1/2 cup of flour it creates a new “flour” object with volume “1/2 cup” in the measuring cup and changes the flour in the flour tub to “6 1/2 cups”. However, Stack Overflow (https://stackoverflow.com/questions/5386306/need-help-creating-new-objects-in-inform7/5672312) tells me that Inform doesn’t handle dynamic object creation well and I shouldn’t do this, though it doesn’t really say why I shouldn’t do this. Is there a more fleshed-out explanation for why it’d be bad, and if it is bad what would be a better way?

Also, I did find a Dynamic Objects extension (new users can only put 2 links in a post, so, uh, Google it if curious?) but it doesn’t seem to be listed in the Extensions Library (Google Inform 7 Extensions Library 'cuz I can only put 2 links in a post, RIP) and the last commit is 2015, which leads me to believe that it might be abandoned, is that accurate?

Sorry for the gigantic question post, if it’d be better to split this up into multiple, more specific posts I can do that too. Thanks for reading.

3 Likes

I am by no means an expert, so I may be unaware of or forgetting something, but:

From what I know, Inform does not have tuples.

I know less about the Dynamic Objects extension than you do, unfortunately.

It is indeed possible for things to have properties which are lists, if I’m reading your question correctly.

An ingredient is a kind of thing. A bowl is a kind of thing. A bowl has a list of ingredients called the contents.

You could have a container have two lists, one of ingredients and one of their volumes. For some intents and purposes (such as speed, memory usage, etc.), this is roughly the same as using tuples but with more work. You would of course have to be very careful when modifying the lists, and you would lose the ability to sort. I get why you wouldn’t want to do it this way.

I spent some time thinking about it, and here is how I would do it. It’s a bit hacky, and only works for 1 mixing container at a time, but it makes sense to me and seems like it should be comfortable to work with once it’s up and running.

A volume is a kind of value.
2.0 cups (plural) specifies a volume.

An ingredient is a kind of thing.
[The ingredients are either nowhere, or in the bowl,
 and the volume specifies the total amount in the bowl.]
Some flour is an ingredient. The flour is nowhere. The flour has a volume.
Some salt is an ingredient. The salt is nowhere. The salt has a volume.

A measure is a kind of thing. A measure has a volume. A measure has an ingredient. A measure can be empty or full.
A source is a kind of thing. A source has a volume. A source has an ingredient. 
The Kitchen is a room.

The half cup is a measure. It is in the Kitchen. The volume of the half cup is 0.5 cups. The half cup is empty.
The tablespoon is a measure. It is in the Kitchen. The volume of the tablespoon is 0.0625 cups. The tablespoon is empty.
The tub is a source. The description is "A tub of flour." It is in the Kitchen. The ingredient of the tub is the flour. The volume of the tub is 10 cups. Understand "flour" as the tub.
The shaker is a source. The description is "A salt shaker." It is in the Kitchen. The ingredient of the shaker is the salt. The volume of the shaker is 0.5 cups. Understand "salt" as the shaker.
The bowl is a container in the Kitchen.
	
Measuring is an action applying to two things. 
Understand "measure [something] with [something]" as measuring. 
Understand "measure [something] of [something]" as measuring (with nouns reversed).

Check measuring:
	if the noun is not a source:
		say "You can't measure that." instead;
	if the noun is empty:
		say "There's no [the ingredient of the noun] left." instead;
	if the second noun is not a measure:
		say "You can't measure with that." instead;
	if the second noun is full:
		say "You'll need to empty [the second noun] first." instead.
		
Carry out measuring:
	now the second noun is full;
	now the ingredient of the second noun is the ingredient of the noun;
	now the volume of the noun is the volume of the noun minus the volume of the second noun.
	
Report measuring:
	say "[The second noun] is now filled with [the ingredient of the noun]."

Instead of inserting a measure into the bowl:
	now the noun is empty;
	now the ingredient of the noun is in the bowl;
	let X be the volume of the ingredient of the noun;
	let X be X plus the volume of the noun;
	now the volume of the ingredient of the noun is X;
	say "You empty [the noun] into the bowl."

Obviously there is still work to be done here. For one, it might be a bit hard to get commands like “measure a tablespoon of salt” and “put salt in bowl” to be properly understood. But I hope this gives you some ideas. (You’re free to use it however you’d like.)

1 Like

Hi,
(i) there’s no tuple value as such. As suggested above one approach (which I worked through in the example scenario below) is to keep two list properties carefully aligned, so that corresponding entries provide the paired elements of the tuple. Another approach would be to use a table with 2 columns- the potential problems with this approach are that unlike list properties, the lengths of tables are defined at compile-time and are not mutable in play, and consequently the tables themselves, which resemble statically defined structure arrays, must be defined in the source rather than potentially being created and built dynamically in code, as can be done with lists, which resemble dynamically-sizable single-type arrays,

Inform 6 allows for dynamic creation and destruction of objects but, like I6’s multiple class inheritance, this has not yet been recreated in Inform 7. I assume the Dynamic Objects extension does what it sounds like it does by using a lot of I6 hacking but I’ve never looked at it. Does it not install and compile? 2015 is relatively recent in I7 terms.

"Recipe" by PB

The Kitchen is a room.

Section - Preamble

[we will model the ingredients as number properties of their dispensers, which become depleted as ingredients are added to mixtures.
Mixtures are implemented as list properties of their containers (which don't contain any objects- their contents are represented by the mixture list and the corresponding amounts list, which are updated as ingredients are added)]

Section - Ingredients and Units

An ingredient is a kind of value. The ingredients are flour, sugar, yeast, water, cocoa, milk, raisins and butter.
A unit is a kind of value. The units are fluid ounce, ounce, teaspoonful and tablespoonful.

Table of Stores
dispenser	ingredient	amount left	unit
a milk jug	milk	5	fluid ounce
a packet of yeast	yeast	3	teaspoonful
a bag of flour	flour	9	ounce
a tin of cocoa	cocoa	4	tablespoonful
a packet of raisins	raisins	8	ounce
a butter dish	butter	8	ounce
a packet of sugar	sugar	20	ounce
a water tap	water	10000	fluid ounce


Section - Mixing Bowls

A mixing-bowl is kind of open unopenable container. A mixing-bowl has a list of ingredients called mixture. A mixing-bowl has a list of numbers called amounts.
Understand "mixture" as a mixing-bowl.

The mixing bowl is a mixing-bowl.  It is on the counter. The counter is in the Kitchen.
The pan is a mixing-bowl. It is in the cupboard. The cupboard is a closed, fixed in place, openable container. It is in the Kitchen.

Rule before printing the name of a mixing-bowl:
	omit contents in listing; [otherwise we'll be told it's empty, even when it's got a mixture in it]
Rule after printing the name of a mixing-bowl when printing the locale description:
	let n be the number of entries in the mixture of the item described;
	if n > 1:
		say "(containing a mixture)";
	else if n is 1:
		say "(containing some [entry 1 of the mixture of the item described])";

[intercept examining, so we can give our own version of what's in it]
Instead of examining a mixing-bowl:
	say "[The noun] ";
	let n be the number of entries in the mixture of the item described;
	if n is 0:
		say "is empty.";
	if n > 1:
		say "contains a mixture.";
	else if n is 1:
		say "contains some [entry 1 of the mixture of the noun].";

[We need some way of reversing errors. It's assumed that ingredients once added can't be individually removed]
Emptying it is an action applying to one thing.		
Understand "Empty [mixing-bowl]" as emptying it.
Understand "Tip away/out [mixing-bowl]" as emptying it.

Check emptying:
	if the number of entries in the mixture of the noun is 0:
		say "[The noun] is already empty!";
		stop the action;
		
Carry out emptying:
	now the mixture of the noun is {};
	now the amounts of the noun is {};

To tip is a verb.	
Report emptying:
	say "[We] [tip] the contents of [the noun] down the sink.";
	
Section - Dispensers
		
A dispenser is a kind of thing. Every dispenser is on the counter. [we've already got everything out and ready to go!]
Some dispensers are defined by the Table of Stores.  The tap is fixed in place.
Instead of examining a dispenser which is not the tap:
	if amount left is 0:
		say "[The noun] is empty.";
	else:
		say "[The noun] contains about [amount left] [unit][if amount left of the noun is not 1]s[end if] of [ingredient].";
		
Section - Making Our Recipe- Adding Ingredients

[we define adding ingredients to mxing bowls as the main action, but also intercept attempts to 'put' the ingredients (interpreted by Inform as their dispensers, as the ingredients have no existence as objects in this implementation).] 

Adding it to is an action applying to one ingredient and one thing.
Understand "Add some/-- more/-- [ingredient] to [something]" as adding it to.
Understand "Put some/more [ingredient] in [something]" as adding it to.
Understand "Put some more [ingredient] in [something]" as adding it to.

[Allow the player to omit the mixing bowl, and just say e.g. 'Add butter']
Does the player mean adding an ingredient to a mixing-bowl: it is very likely.

The adding it to action has a number called the amount.
The adding it to action has a unit called the suitable-units.
The adding it to action has an object called the source.

[intercept things like 'put milk in the bowl'. 'put milk' is not permitted]
Instead of inserting a dispenser into a mixing-bowl:
	let ingredient_intended be the ingredient corresponding to a dispenser of the noun in the Table Of Stores;
	try adding ingredient_intended to the second noun;


Check adding:
	[stop the player messing up the kitchen]
	unless the second noun is a mixing-bowl:
		say "[We] [won't] achieve anything palatable by putting [the ingredient understood] [if the second noun is a container]in[else]on[end if] [the second noun]!";
		stop the action;
	[check how much we want to add and if there's enough available]
	now the source is the dispenser corresponding to an ingredient of the ingredient understood in the Table Of Stores;
	now the suitable-units is the unit corresponding to an ingredient of the ingredient understood in the Table Of Stores;
	[the parser only allows two variables (we've chosen the ingredient and the mixing bowl) in the typed command, so we now need to go back to the player for the amount]
	say "How many [suitable-units]s? >";
	now the amount is the  amount-chosen; [invoke I6 routine to read in a number]
	unless amount is at most the amount left of source:
		say "[There's] not enough left in the [source] to add [amount] [suitable-units][if amount > 1]s[end if].";
		stop the action;

		
Carry out adding:
	now the amount left of the source is the amount left of the source - amount;
	[if we're adding more of an existing ingredient, find the relevant entry in the mixture and add the amount to the amounts]
	if the ingredient understood is listed in the mixture of the second noun:
		let count be 1;
		repeat with N running through the mixture of the second noun:
			if N is the ingredient understood:
				now entry count of the amounts of the second noun is entry count of the amounts of the second noun + amount;
				break;
		increment count;
	else:
		add the ingredient understood to the mixture of the second noun;
		add the amount to the amounts of the second noun;
	

Report adding:
	let n be the number of entries in the mixture of the second noun;
	say "[We] add [amount] [suitable-units][if amount is not 1]s[end if] of [the ingredient understood] to [if n > 1]the mixture in [end if][the second noun].";

Section - I6 inclusions

To decide which number is amount-chosen:
	(- GetNumber() -)

Include (-

[ GetNumber number_typed;
	while (true) {
		VM_ReadKeyboard(buffer, parse); 							! get input from player
		wn = 1; number_typed = TryNumber(wn); 						! try to parse the first word as a number
		if (number_typed >0 ) return number_typed; 	 				!-1000 indicates no number could be parsed
		print "I could not make sense of that number. Please try again: >";	! if not >0 try again		}
];

-)

There is some evidence that Inform was going to have a tuple type (and there is a partial implementation of the I6 code behind it) but there is no way to actually access it from I7 at present.

Tables are definitely an option – while it’s true that you must declare the maximum possible size of the table at compile time, it is possible to add, remove, and otherwise mutate rows in the table anywhere from zero up to that limit.

You can do a similar sort of thing with objects, and RB10.3 provides some examples of this. (The Snip! example is another such.) These still have an upper limit to the number of objects that can be simultaneously in play at any given moment, but you can “recycle” them once they are no longer needed (for example, once the player starts making a new recipe).

And the Dynamic Objects extension allows for truly dynamic objects (although you should consider putting some kind of limits in anyway, to avoid silliness). It was last updated for 6L02 but it still compiles and runs fine on the current 6M62 version – the differences between 6L and 6M are relatively small, vs. 6L and any previous version. (Also, there’s a Dynamic Tables extension that provides a way to do the same thing for tables, within reason.)

Yes, sorry should have been clearer: the kinds and headings of the columns and the number of rows in a table are usually immutable in play and must be declared at compile-time for every table, but the order and contents of rows can be freely changed or blanked out.

Also different tables can be referenced by means of ‘table name’ variables or properties, as in ‘The ever-changing-schedule is a table name that varies,’ or ‘Every teacher has a table name called timetable.’

I’m hoping that @HanonO will comment on this because “The Baker of Shireton” involved bread making and you could mess up (or produce variants on the standard loaf) depending on how you entered instructions.

But I’m also hoping that it’ll return to the conversation about removing unnecessary details from parser narratives.

I’d like to know more about your decision to build a model that handles the ingredients this way. How are these details enhancing the experience?

1 Like

Well OK, but the request was for technical help with implementation, not on game design :wink:

Seriously, although there can be enjoyment in a well-fleshed out simulation if it’s an intrinsically interesting activity, the general advice to programmers ‘keep pinching yourself regularly to remind yourself you’re creating a game not a simulation’ is good, I think. A good starting point is to ask yourself, ‘Would I describe this in a novel?’ An obvious exception to this would be if you’re implementing a good puzzle…

Probably also a similarly good idea to self-reflect on questions along the line of ‘why can’t Inform easily implement X, Y or Z?’ If Inform doesn’t make it easy, a well-designed and thought-out game possibly doesn’t need it…?

I should say I’m the worst at taking my own advice.

1 Like

I’m really blown away by how much effort you all have put into answering my giant cavalcade of questions, I wasn’t expecting anybody to actually go and write up examples! I just want to express my appreciation. I haven’t actually had time to test any of the suggestions out or do further experimentation, but I think I might try using an object pool.

One thing that was noted in the Snips example (http://inform7.com/book/RB_10_6.html) posted above was that Inform has a fairly low maximum number of objects - " writing a game that required 1000 strings in the string repository would place silly demands on the resources of the system" - which, at what point should I worry about performance? Is this a carryover from when computers were slower, or does Inform regularly run in restricted environments?

Well, mostly it’s I wanted to learn Inform 7, and I also bake bread, so I figured I may as well make a game about baking a simple loaf of bread to learn Inform 7. There’s no narrative, it’s just, you know, bake a loaf of bread, eat some of the bread, hooray! You have bread now, game over.

2 Likes

If you’re targeting Glulx (which is the default), then you can mostly ignore the limits until you get a build error or the runtime performance gets noticeably worse. (There are some cases where Inform iterates through all the objects that exist one by one. This is usually blazingly fast anyway, but it may get less so if you do start making silly numbers of objects.)

And even the build errors are frequently just soft limits that will tell you a specific setting you need to increase to get a bit more headroom.

Okay, I’ll take that as meaning that I shouldn’t worry about it until it pops up.

Anyways, I managed to get filling, pouring, and all that working using the synchronized list approach. It gives pretty wacky quantities if you fill a smaller container with a mixture from a larger container, since it has to scale the ingredient volumes so the smaller container has the proper ratios, but I can figure out a way to handle that later.

>[3] fill 1-tsp with salt
You fill the 1-tsp measuring spoon from the 500g cylinder of salt.

>[4] pour 1-tsp into 3-qt
You pour the 1-tsp measuring spoon into the 3-qt mixing bowl.

>[5] x 3-qt
The 3-qt mixing bowl contains 1.0 tsp salt.

>[6] fill 1-cup with bread flour
You fill the 1-cup dry measuring cup from the bread flour bin.

>[7] pour 1-cup into 3-qt
You pour the 1-cup dry measuring cup into the 3-qt mixing bowl.

>[8] x 3-qt
The 3-qt mixing bowl contains a mixture of salt and bread flour, quantities 1.0 tsp and 1.0 cup - TODO: reformat.

>[9] pour 3-qt into half-cup
You fill the 1/2-cup dry measuring cup from the 3-qt mixing bowl.

>[10] x 3-qt
The 3-qt mixing bowl contains a mixture of salt and bread flour, quantities 0.5102 tsp and 4.08163 fl oz - TODO: reformat.

>[11] x half-cup
The 1/2-cup dry measuring cup contains a mixture of salt and bread flour, quantities 0.4898 tsp and 3.91837 fl oz - TODO: reformat.

It’s working great! Super thanks to everybody who responded to help out. Oh, also, I tried importing Dynamic Objects and it still compiles, but I didn’t end up using it because the synchronized list approach worked pretty well.

Now I have a weird issue with extending the standard verb “set”. Specifically, I have a stand mixer, and I want the user to be able to write something like set the stand mixer to speed 4. However, I’d also like to be able to parse set the stand mixer to 4. I tried to make Inform 7 understand “set [StandMixer] to [a number]” and then write a rule to dispatch that as try setting the stand mixer to speed 0 but…it gets an error when I try to compile it, and I don’t really understand why.

I’ve pasted my scratch project below.

"Scratch" by MoyTW

The Kitchen is a room.

Section 1 - Stand Mixer

[ Ok, so...if you just write "zero, stir, two..." it will interpret "two" not to mean the string two but the numerical value two, and then throw an error due to mixed string/number types. Hence speed <number>. ]
[ I would put "off" in here but I also have an oven with off/bake/broil, and apparently you can only define one "off" in the whole file (!?) - so it's speed 0 here. ]
StandMixerStatus is a kind of value. The StandMixerStatuses are speed 0, stir, speed 2, speed 4, speed 6, speed 8 and speed 10.

A StandMixer is a kind of thing.
A StandMixer has a StandMixerStatus called status. The status of a StandMixer is usually speed 0.

Instead of examining a StandMixer:
	say "[The noun] is currently set at [the status of the noun]";

Understand "set [StandMixer] to [a number]" as setting it by number. Setting it by number is an action applying to one thing and one number.
Understand "set [StandMixer] to [StandMixerStatus]" as setting it by StandMixerStatus. Setting it by StandMixerStatus is an action applying to one thing and one StandMixerStatus.

Instead of setting a StandMixer to something:
	say "The settings on the stand mixer are 0, stir, 2, 4, 6, 8, and 10.";

Check setting StandMixer by number (this is the only valid number settings rule):
	let valid_mixer_numbers be {0, 2, 4, 6, 8, 10};
	if the number understood is not listed in valid_mixer_numbers:
		say "The settings on the stand mixer are 0, stir, 2, 4, 6, 8, and 10.";
		stop the action.

Carry out setting StandMixer by number:
	if the number understood is:
		[ -- 0: try setting the stand mixer to speed 0; ] [ This is what I *want* to do ]
		-- 0: try putting the noun on the table; [ Try clearly works in this case ]
		-- 2: now the status of the noun is speed 2;
		-- 4: now the status of the noun is speed 4;
		-- 6: now the status of the noun is speed 6;
		-- 8: now the status of the noun is speed 8;
		-- 10: now the status of the noun is speed 10;

Carry out setting StandMixer by StandMixerStatus:
	now the status of the noun is the StandMixerStatus understood;

Test stand with "set stand mixer to asdf / x stand mixer / set stand mixer to 4 / x stand mixer / set stand mixer to 0 / x stand mixer / set stand mixer to speed 0 / x stand mixer / set stand mixer to speed 8 / x stand mixer"

Section 2 - Room

A countertop is a supporter in the kitchen. It is fixed in place.

The StandMixer called the stand mixer is on the countertop.

A table is a supporter in the kitchen. It is fixed in place.

Here’s the error I get when I replace the action for the 0-match:

Problem. You wrote 'try setting the stand mixer to speed 0'  , but 'setting the stand mixer to speed 0' is not an action I can try. This looks as if it might be because it contains something of the wrong kind. My best try involved seeing if 'speed 0' could be a topic, which might have made sense, but it turned out to be a standmixerstatus.

 See the manual: 7.4 > 7.4. Try and try silently

I was trying to match this phrase:

 try (setting the stand mixer to speed 0 - action) 

But I didn't recognise 'setting the stand mixer to speed 0'.

which is deeply confusing, because I know set the stand mixer to speed 0 works:

>[6] x stand mixer
The stand mixer is currently set at speed 4
>[7] set stand mixer to speed 0
>[8] x stand mixer
The stand mixer is currently set at speed 0

so why can’t it delegate the type properly in the try? It does pick it up and place it when you set the stand mixer to 0:

>[5] set stand mixer to 0
(first taking the stand mixer)
You put the stand mixer on the table.

so clearly the try is working fine, it just can’t seem to resolve the type dispatch in the “setting”. I suspect it has something to do with my Understand statements but playing around and reading the docs hasn’t been enough for me to get it.

It looks like you’re trying to hand a player command to the try... statement. This doesn’t work. The grammar line (“set [StandMixer] to [a number]”) is for the player’s benefit only. In code, you have to use the formal action name, exactly as you’ve defined it.

In this case, this would be:

try setting the stand mixer by StandMixerStatus speed 0;

Baking in parser is hard, y’all…

I am so lame, I don’t even think I have the source text for Baker of Shireton anywhere. Lame.

As I recall, I used a finite number of dough balls and a finite number of loaves that were stored off-stage and applied adjectives to them based on how long they existed in the oven or in the world.

I believe each dough-ball and bread loaf had a number that counted up, so dough was called “unrisen” for five turns, after which it became “risen” and then after five more turns it became “sourdough”. Dough that was in the oven for five turns was swapped with a loaf of bread from off-stage as appropriate - sourdough became a loaf of bread with the adjective “sourdough”. If the player had added barley it became “barley” bread. The oven also incremented another heat counter that added the adjective “burnt” to a loaf it was in for ten turns or more. The adjective “hot” was added to new loaves, and then the heat counter counted down once out of the oven so you could have “hot” “sourdough” bread for a limited time, and the customers would pay more for fresh loaves. This was the work of a ridiculously complicated every turn rule.

I got it to work, but it was incredibly clunky and any other programmer probably would do it in a much better way using custom rulebooks or some other magic I’m not aware of.

I purposely simplified bread baking so as not to involve mixing ingredients other than adding barley, waiting long enough for it to become sourdough, or turning “coppery” if you put a coin into the bread for nefarious intentions. Essentially the command “make dough” narrated that the player was mixing flour water yeast, etc., without having to fool with it too extensively.

Doing this in the choice-based sequel was aeons easier because I used a real-time timer, and instead of actually modeling objects, the bread in its various formats was just a pile of variables.

Thanks for sharing. My current design is going in an excessively simulationist direction, not because I think it’ll be, like, good? (it won’t be in the sense that, mechanically, measuring 5 1/4 cups of flour and dumping it into a bowl via parser text is actually incredibly unexciting) but mostly as a learning exercise. If I were actually making a game I’d definitely try and simplify it down to any narratively important decisions.

One thing I’m taking away from the answers here is that Inform doesn’t really do “new [object of kind]” like you would in a normal programming language, and that people usually just handle “I need a new X” by making a bunch of Xs and, like, nulling out the location. Enforced object pooling, I guess?

1 Like

The Dynamic Objects extension does enable the “new X” method; much like other languages, this will allocate a completely new object from the heap. But it’s less popular with Inform due to a preference for fixed-size stories, such that if it runs at all then it should run to completion without random out of memory errors partway through.

1 Like

Correct. Inform doesn’t support object creation on the fly during play (Although as Gavin pointed out, there is a tricky extension that does) which is why the usual method is to create as many “props” as you need in the world and then move them on and off-stage as necessary.

Sometimes this is required for certain effects, but in general there are better ways to handle something like a jar full of jelly beans without modeling 3000 individual candies.

What he said.

If you are feeling the need to create an undefined number of new objects dynamically in play you are probably being too ‘simulationist’ in your modeling approach.

And there’s nothing wrong with being ‘too simulationist’ especially if you’re learning and prototyping - Inform 7 can actually do that.

Agreed- and of course, there’s also no absolute right or wrong way of doing anything.

1 Like