Old school text adventure as a way of re-learning coding

General discussion for topics related to the FreeBASIC project or its community.
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Hello and thank you for all the suggestions. There are really useful ideas here,things I never even thought of so once again thank you.

Sorry for the duplicate, when I posted the initial I got a blank screen as a response, assumed it had not worked, tried again, got the same blank screen and gave up. Oh well.

An explanation of how the preprocessor, interpreter and command handler currently work. I suspect this is not the best, or most elegant way of handling things - but then I'm not a multi-million dollar company. Sorry this is quite long.

Taking give the bone to the large dog and look and wear the blue shirt and put the yellow ball under the red table and look at it as an example.

1) The preprocessor throws away all definite and indefinite articles, so the, an and a, which gives give bone to large dog and look and wear blue shirt and put yellow ball under red table and look at it

2) It links some words together, like look and at which becomes look_at
2a) it converts some synonyms to standard wording like ‘into’ becoming ‘in’, ‘underneath’ becoming ‘under’, ‘take off’ becoming remove etc.

3) It ties adjectives and nouns together, and here it makes its first assumption that the basic entered syntax will be in the form <verb> <noun> [<and>]… or <verb> <something> <something> <noun> [<and>], or for some commands like get and put which can refer to another object <verb> <noun><placement> <noun> eg put bone on table. At this point it does not care what the adjective is, the user could have typed get shdjahsdjka bone and put it on agjsdgajd table.

So the above example is now: give bone to large=dog and look and wear blue=shirt and put yellow=ball under red=table and look_at it

4) The code then runs backwards though the sentence, so in the order it look_at and red=table under yellow=ball put and blue=shirt and look and large=dog to bone give finding each occurrence of it and looking either for the word following the next ‘and’ (red=table in this case) or if that is followed by a placement word (in this case ‘under’) the ‘word after that (here its ‘yellow=ball’). This is a bit ambiguous as ‘it’ could potentially mean yellow=ball or red=table, I’ve just gone for the most likely. So in the above we now have give bone to large=dog and look and wear blue=shirt and put yellow=ball under red=table and look_at yellow=ball.

4a) It does the same for the word ‘that’, however here it always takes the word after the next ‘and’ to be the noun. So if the player types get the ball and put it under the table and look at that., the preprocessor will produce get ball and put ball under table and look_at table.

5) It resolves things like get hat and ball to get hat and get ball by running through the sentence looking for verbs and adding the previous one if needed. This currently causes an issue if something like look at ball and pet ball is entered rather than ‘look at ball and get ball’. The preprocessor will spit out look_at ball and get pet ball.

6) Finally it handles basic plurals, if there is a blue shirt and a red shirt in the current location and the player types ‘get shirts’ then the preprocessor generates ‘get red=shirt and get blue=shirt’. If there are no shirts present then a standard ‘You cannot see any shirts here’ is generated. If the player types get shirt then this is handled in the actual command ‘get’.

7) The equals signs are then striped so the output from the preprocessor would be: give bone to large dog and look and wear blue shirt and put yellow ball under red table and look_at yellow ball

At present the interpreter simply takes each word in the preprocessor's output and tries them as a command, the first being ‘give’ here, splitting the sentence up on each ‘and’. Its here that more syntax checking is applied as it will error if the player had typed ‘look andget the red ball’ as it will not find the ‘and’ and so fail as it does not know what ‘andget’ is, similarly if they typed loik etc. Again it does not care about adjectives.

The individual commands (eg put) take at the input from the interpreter and apply any additional processing needed for the command. In the case of ‘put’ it checks for a placement word (eg under) and takes everything to the left of that as a thing and everything to the right of that as a place and then checks that they are at the current location. That is either in the room, carried by the player, worn by the player (eg put hat on table) or in/on etc a container in the room. Its here that adjectives are (finally) checked as each object has a simple name (hat, ball) and a longer one (straw hat, red ball). Adjectives have to match and be in the current order. If the dog is a ‘large friendly dog’ then ‘large dog’ and friendly dog will match but ‘friendly large dog’ won’t – even though its sort-of correct. Commands also solve the ‘get shirt’ issue when multiple shirts are present by building a list of possible matches and the asking the player which they meant.

This approach has the benefit that I don’t need to specify every colour, or kind of hat etc there has ever been. If the player types ‘get bowler hat’, but there is no bowler hat in the game they get back, ‘You cannot see that here’, rather than ‘I don’t understand bowler hat’. However it means that garbage like ‘get dsaljdla hat’ or just ‘get ajsdahjkjk’ also returns You cannot see that here, and as mentioned the processing at 5 fails if a command is mistyped. I’m not sure how to solve that right now and think I may have written myself into a hole.

Containers can have things in them, on them or under them. Pools, wells etc are containers as are NPC's. Thus they will be able to wear clothes and carry things. When an npc picks something up it will simply be added to their 'container'. Obviously having random npcs just wandering around with stuff the player needs would be very annoying so the npc will have to have a need for the item. I'm also thinking of implementing ownership, thus the player will not just be able to wonder into a shop, grab something and leave without paying for it.

I'm hoping to have simple conversations, but that is for the future, simple interactions like 'buy hat' are easy, more complex ones like 'ask the shopkeeper how much the hat costs', or say to the shopkeeper "do you think it will rain today?", are not.

Right now I am stuck on the verb substitution at 5 in the preprocessor and working out how to implement 'them', which I hope your suggestions will help me resolve.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote: ...
However it means that garbage like ‘get dsaljdla hat’ or just ‘get ajsdahjkjk’ also returns You cannot see that here, and as mentioned the processing at 5 fails if a command is mistyped. I’m not sure how to solve that right now and think I may have written myself into a hole.
...
You might try to 'guess' what the player wrote by using a function akin to Levenshtein's Distance:

Code: Select all

namespace Distances
  function min( a as integer, b as integer ) as integer
    return( iif( a < b, a, b ) )
  end function
  
  function max( a as integer, b as integer ) as integer
    return( iif( a > b, a, b ) )
  end function
end namespace

/'
  Computes the Levenshtein Distance between two strings.
  
  The Levenshtein Distance is a measure of how 'similar' two
  texts are, and is given by the amount of changes that need
  to be performed to one of the strings to match the other.
'/
function levenshteinBetween( s1 as const string, s2 as const string ) as integer
  dim as integer _
    l1 = len( s1 ), _
    l2 = len( s2 ), _
    maxDistance = Distances.max( l1, l2 ), _
    dist( 0 to maxDistance, 0 to maxDistance )
  
  for i as integer = 0 to l1
    dist( 0, i ) = i
  next
  
  for i as integer = 0 to l2
    dist( i, 0 ) = i
  next
  
  dim as integer t, track
  
  for j as integer = 1 to l1
    for i as integer = 1 to l2
      if( s1[ i - 1 ] = s2[ j - 1 ] ) then
        track = 0
      else
        track = 1
      end if
      
      t = Distances.min( dist( i - 1, j ) + 1, dist( i, j - 1 ) + 1 )
      
      dist( i, j ) = Distances.min( t, dist( i - 1, j - 1 ) + track )
    next
  next
  
  return( dist( l2, l1 ) )
end function

var verb = "look"

dim as string s

? "The verb is: '" + verb + "'"
? "Write it or something similar. Type 'quit' to finish."

do while( lcase( s ) <> "quit" )
  input s
  
  ? "The Levenshtein Distance between '" + _
    s + "' and '" + verb + "' is: " & levenshteinBetween( s, verb )
loop
That way you can measure 'how close' what was written was from what it needs to be, and even autocorrect it if necessary, or suggest alternatives.
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Many thanks Paul, I'd never heard of this technique. I was wondering about using a soundex, this could be easier.

A quick couple of questions on your code;

Code: Select all

for _
    i as integer => 0 to l2
   
    dist( i, 0 ) => i
  next
 
  dim as integer _
    t, track
How is the i as integer handled? Its creating a variable, but is this on the stack, or main data area? In addition is its scope only for the life of the loop and is there any benefit in doing things this way, rather than a declaration at the top of the sub?

Equally any benefit in dimming a variable in code rather than at the sub start as in the dim as integer t,track? I'm guessing its scope is from the dim to the end of the sub?

Thanks again.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote:...
How is the i as integer handled? Its creating a variable, but is this on the stack, or main data area? In addition is its scope only for the life of the loop and is there any benefit in doing things this way, rather than a declaration at the top of the sub?
Equivalent to:

Code: Select all

for( int i = 0; i <= l1; i++ ) {
  dist[ 0 ][ i ] = i ;
  }
Pure convenience. It should be exactly the same speed (both are on the stack), and the scope is indeed only the loop by virtue of the declaration embedded in it. If you declare it outside, it assumes the outside scope. I, for one, declare variables as close to their first use as is practically possible, and if appropriate and possible, immediately assign and/or scope them (FreeBasic supports explicit scoping via the scope/end scope construct)
olympic sleeper wrote:...
Equally any benefit in dimming a variable in code rather than at the sub start as in the dim as integer t,track? I'm guessing its scope is from the dim to the end of the sub?

Thanks again.
Indeed. All code blocks are scoped (and as you can see, FreeBasic supports namespaces too). Perhaps the most mystifying aspect from people that come from other languages (especially C/C++) is the lack of any function main or similar. Generally speaking, any code that's not within a sub/function is part of the outer namespace. If there's no namespace currently declared, then the global namespace is assumed. However, vars declared there are NOT globals, unless explicitly declared so with dim shared.

You're welcome. Quite interesting topic ;)
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Hello again,

Things are progressing and objects can now be placed, in, on under, beside etc others. This will allow npcs to be described as being around a location (near a particular object) rather than just 'here'.

However I'm not very happy with the currently generated text as it mainly consists of short sentences and I want something a bit more flowing. Here’s an example test location.

Code: Select all

This is the middle of a sandy beach that stretches away to the north and south. A short wooden jetty leading east ends here.

Inside a wooden crate there is a bank safe and inside this there is a cash box and a cloth bag.
Sitting on the bank safe there is a rubber duck.
Inside the cash box there is a silver penny.
Inside the cloth bag there is a glittery gem.
Sitting under a steel table is an iron bound chest.
You can also see a small silver key and a small pebble here.
Its generated using a tag list based on the object and what it holds, has on it etc. This is the list for the above.

Code: Select all

(50)P50p[50]{50}(64)<59>P64p[64]{64}(59)<60><62>P59p[59]<65>{59}(60)<61>P60p[60]{60}(62)<63>P62p[62]{62}(65)P65p[65]{65}(61)P61p[61]{61}(63)P63p[63]{63}(66)P66p[66]{66}(67)P67p[67]{67}<68>(68)P68p[68]{68}
Each object has 4 tags, so the 1st section (50)P50p[50]{50} refers to object 50, anything that has a tag <like_this> in it has something, in on or near it. 50 does not (its the silver key) so that is printed out last. Object 64 (the wooden crate) does however as its tags are (64)<59>P64p[64]{64}. tag<59> is the bank safe, if you look at the safe's tag list you'll see (59)<60><62>P59p[59]<65>{59}. There are 2 objects in the safe, 60 - the cash box - and 62 - the cloth bag, and one on it, 65 - the duck.

It might be obvious from the numbers that the tag list is build from the room via the objects db and in the order the objects are stored, hence the numbers always increase.

The display code runs through the tag list printing info for each object, this is why it is able to tie the info on the safe together - all the tags are in one place, but not the fact that the cash box, which is in the safe, contains something. Maybe a better sentence would have been;

Code: Select all

Inside a wooden crate there is a bank safe and inside this there is a cash box, containing a silver penny, and a cloth bag, containing a glittery gem.
However I am rather stuck as to how to get this out of a tag list and may need to rework it as a relationship table. This in turn would create issues where more objects are available on or in something. Think of a shop shelf which contains 4 jewellery boxes, each containing 4 jewels, the sentence would get overly long and complex very quickly.

Any suggestions?
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote:Any suggestions?
Yes, several =D
...
However I am rather stuck as to how to get this out of a tag list and may need to rework it as a relationship table. This in turn would create issues where more objects are available on or in something. Think of a shop shelf which contains 4 jewellery boxes, each containing 4 jewels, the sentence would get overly long and complex very quickly.
...
I've seen adventure games that differentiate between 'look' and 'examine': 'look' is more general, 'examine' is more thorough and can reveal details that 'look' doesn't. Upon 'looking' at, say, the place, it might say something like this:

Code: Select all

You're standing there. There's a bank safe near you, with a bag upon it that seems to contain something.
The command you had to issue was 'look around'. Now, the first time you entered at a location, the command was issued automatically. On subsecuent arrivals, only the name (or a really short description) was provided, and you had to issue it manually. If there was some significant change (like, say, you managed to open the bank safe somehow), it would also add it. Instead of 'there's a bank safe here', it provided something along the lines of 'an open bank safe'.
There were just significant details provided (a bank safe is bound to contain something very important, and most likely locked; the bag contains 'something'. It doesn't just tells, like a shopping list, what each one contains. You had to explicitly 'examine' things to get a more thorough description:

Code: Select all

? examine bag

The bag contains some pieces of jewelry, and a small box that rattles when you shake it.
See? It is the narrative itself that directs you towards the objects that may contain something useful, and directs attention towards the important things. Now:

Code: Select all

? look at jewelry

Lots of small pieces of jewelry. A golden ring caught your attention; it has carvings that appear to be runes of some kind.

? examine ring

Upon closer examination, you are reasonably sure that the carvings are written in Ancient Elvish. You can't read them, but perhaps someone in town might know how?

? look at box

It is a small box, about a few inches in size, wrapped in velvet. It makes a pleasant rattling sound when you shake it.

? examine box

You open the box and there's just a small peeble inside. What a waste of time.
Again, it is the narrative that 'hints' you. In this case, the ring could be very important, but the stone is clearly not. However, the 'pleasing' there (which is just flavor text) might compel the player to try and open it (perhaps triggering some optional event in the process). You can always 'get the peeble' if you want, but you'll be carrying something useless in the context of the overall adventure (or you can make it so that it can be thrown at people, just for gigs).
Also, perhaps it is the box that is actually valuable since it is a container object that is, in and of itself, useful; just not the peeble.
...
Things are progressing and objects can now be placed, in, on under, beside etc others. This will allow npcs to be described as being around a location (near a particular object) rather than just 'here'.
...
Is this important from a gameplay standpoint, or just for narrative flavor? Having important things in unexpected places (ie 'underneath a rug, which is under some random table' could be extremely annoying, since the first time you encounter something important there, you'll be compelled to search every crevice of every piece of random furniture that you might encounter. As I mentioned above, a good narrative can help direct the attention of the reader to the important details.

As for NPCs, unless they can move within a location, and this has a significant gameplay impact, then it might just be confusing. If this is not so, then it might suffice to say that a NPC is 'near a tree' than 'beside a tree'. If you do want this ability, consider implementing a narrative that helps the player sift between really important and relevant objects/NPCs from the ones that aren't.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

...
However I'm not very happy with the currently generated text as it mainly consists of short sentences and I want something a bit more flowing. Here’s an example test location.

Code: Select all

This is the middle of a sandy beach that stretches away to the north and south. A short wooden jetty leading east ends here.

Inside a wooden crate there is a bank safe and inside this there is a cash box and a cloth bag.
Sitting on the bank safe there is a rubber duck.
Inside the cash box there is a silver penny.
Inside the cloth bag there is a glittery gem.
Sitting under a steel table is an iron bound chest.
You can also see a small silver key and a small pebble here.
This is what I meant with 'little, significant details'. Because if you do this, it appears as the PC has some kind of X-ray vision, since he can tell what is inside the safe (perhaps even before opening it!). Also, as I mentioned before, you can select the description of the objects based on their state: instead of a 'bank safe', you can say 'a closed bank safe' and, upon opening it, the description changes to 'an open bank safe'.

You can adscribe relationships in the description of the 'container' object. For example:

Code: Select all

There's a bank safe here. You can see a rubber duck sitting above it.
For more objects, this could become:

Code: Select all

There's a bank safe here. Sitting on it, there's a toy, a pencil, and a bag that might contain something useful.
Note how you may filter the description using the 'look'/'examine' scheme I mentioned before, and use it to draw attention to the relevant things. You can 'examine' the toy, and it will then be replaced with 'a rubber duck'. If you 'examine' the pencil, it becomes 'a red coloring pencil'. And if you 'examine' the bag, it reveals its contents.
...
Its generated using a tag list based on the object and what it holds, has on it etc. This is the list for the above.

Code: Select all

(50)P50p[50]{50}(64)<59>P64p[64]{64}(59)<60><62>P59p[59]<65>{59}(60)<61>P60p[60]{60}(62)<63>P62p[62]{62}(65)P65p[65]{65}(61)P61p[61]{61}(63)P63p[63]{63}(66)P66p[66]{66}(67)P67p[67]{67}<68>(68)P68p[68]{68}
Each object has 4 tags, so the 1st section (50)P50p[50]{50} refers to object 50, anything that has a tag <like_this> in it has something, in on or near it. 50 does not (its the silver key) so that is printed out last. Object 64 (the wooden crate) does however as its tags are (64)<59>P64p[64]{64}. tag<59> is the bank safe, if you look at the safe's tag list you'll see (59)<60><62>P59p[59]<65>{59}. There are 2 objects in the safe, 60 - the cash box - and 62 - the cloth bag, and one on it, 65 - the duck.

It might be obvious from the numbers that the tag list is build from the room via the objects db and in the order the objects are stored, hence the numbers always increase.
...
Just a thought: you can have a linked list of 'tags', of all number of things, for each object. Like this:

Code: Select all

"bank safe" {
  "above" : "rubber duck",
  "beside" : "broom"
  }
"wooden table" : {
  "above" : "flowers", 
  "below" : "iron chest"
  }

tag( "above", "bank safe" ) // Returns "rubber duck"
tag( "below", "bank safe" ) // Returns an empty string or NULL
tag( "beside", "wooden table" ) // Ditto
That is, laying tags like this allows you to have tags of any kind associated with each object. Kind of like key-value pairs.
...
The display code runs through the tag list printing info for each object, this is why it is able to tie the info on the safe together - all the tags are in one place, but not the fact that the cash box, which is in the safe, contains something. Maybe a better sentence would have been;

Code: Select all

Inside a wooden crate there is a bank safe and inside this there is a cash box, containing a silver penny, and a cloth bag, containing a glittery gem.
...
There are several ways you can render this: by using the object's state and their relationships (use the container object and relate all the others). This is manageable if you use the 'look'/'examine' pair, but sanity should be applied: there's only so much crap you can cram above/below/inside something (the 'besides' tag can work differently, though). How specific or general you want to make this is up to you.

For the record, a friend of mine developed an adventure as homework for a class on data structures on college, and he used only linked lists and associative arrays to great effect. He implemented these sort of relationships mostly implicitly, but you were able to put something 'above'/'below' or 'inside' something else if you wanted. He used a really simple and abstract scheme: his objects were 'tiny' (say, a ring), 'small' (a bag or box), 'medium' (about the size of a child; some things of this class could not be carried, some could but only one at a time), 'large' (about the size of a person; not carriable under most circumstances), 'big' (something that could hold/withstand a person, like a coach or a table; could not be carried but some could be pushed/pulled), and 'huge' (about the size of a room; definitely not carriable). He then laid out the relationships of them also abstractly (each category could hold a certain number of the category immediately below; varied across objects but not much), and leaned on these relationships for descriptions (but 'locations' were treated differently). I can elaborate a little on this 'abstract treatment' if you want, as much as I can recall from it (I saw it many years ago)
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Hello Paul,

Many thanks for the suggestions. Yes the bank safe should have said it was open and for some reason did not (missed that). I like the idea of levels of detail, it will save a lot of headaches later when I come to describing shops. Rather than descibing everything on a shelf I can just say there is a shelf of <whatever> here, think I'll play around with the container code and see. I was using linked lists for the objects and rooms but got into a real mess and am now using a simple string of tags for objects. This allows me to use Basic's instr to see if something is here for example. That typed this could be a simpler case use for them.

There will be several puzzles that require the player to put things in particular places and as mentioned npcs will be able to move around a room (and the whole game), though they will be certain places at certain times. So shop keepers will be in their shops during the day etc. I'm currently working on animals and npcs having stances (sitting, standing etc) so cats etc can sleep on things, and generally get under your feet in the way that cats do. Things already have a size and weight so you cannot put the elephant on the fragile table (not that there is an elephant in the game) and I have implemented the ability to carry extra stuff simply by allowing a few different containers to have the player as their container.

I would be interested in how your friend handled abstraction, its always good to see how other people have solved problems. Something I am trying to do is allow flexability in how problems are solved, some puzzles will be fixed but I want to try and prevent the use everything with everything style of play.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote:Hello Paul,

Many thanks for the suggestions. Yes the bank safe should have said it was open and for some reason did not (missed that). I like the idea of levels of detail, it will save a lot of headaches later when I come to describing shops. Rather than descibing everything on a shelf I can just say there is a shelf of <whatever> here, think I'll play around with the container code and see.
Indeed, or you can simply say that there is 'a full/half full/empty shelf'. This can also be used at the location level (see explanation below).
...
I was using linked lists for the objects and rooms but got into a real mess and am now using a simple string of tags for objects. This allows me to use Basic's instr to see if something is here for example. That typed this could be a simpler case use for them.
A friend of mine had to implement a simple adventure, as part of his course on data structures on college. He wanted something simple yet interesting and, if at all possible, very replayable. So, I suggested him the book 'The Turn of the Screw' by Henry James as a base story to work with. It has very few locations, few key characters, and an intriguing story. Turns out he absolutely loved it, and created the adventure based on that. Here is a (very simplified) diagram of the locations for the adventure. I have put it together more or less from what I can remember, having omitted some small yet important locations (and some that only appeared on certain playthroughs):
Image
Now, much as a play, the adventure worked with 3 basic objects: Actors, Props and Sets. An 'Actor' is just the player/NPC: a person that can move and interacted with Sets and Props (NPCs also did this, even when the player wasn't at the location). A 'Prop' is what you'll call an 'object': things that can be carried/examined and such. And 'Sets' were the locations: you could go there, 'look around' them, and little else. Sets could contain any other object from the model, even other Sets. You can see that the Set 'Bly' contains three other Sets (the 'West wing', the 'Hallway' and the 'East wing', each also containing other Sets).

Now, everything relied either on linked lists or associative arrays (hash tables). In the diagram, the solid arrows indicate a 'you can go there from here' relationship. Perhaps more interesting are the dashed arrows: they indicate 'what you can see from here'. If you pay attention to them, from the 'First floor', you can see the 'Back yard' either from 'Miles' room' or from the 'Governess' room', but from the 'Back yard' itself you would only see the 'First floor' (that is, the description provided would be more general, with detail added so as to alter the narrative with hints). For example, upon describing the first floor as seen from the backyard, it could add something along the lines of 'I feel like someone is staring at me. Do I see someone at my room's window?'

Most Sets were doubly linked (this provided the connections between the different sets; where you could go, from where you are), but visibility wasn't doubly linked as I explained above. So, he implemented the 'level of detail' of stuff from the links alone: note that from the 'First floor hallway' you couldn't see neither the 'Back yard' nor the 'Front yard', for example.

Now, instead of 'go north' and such (and describing Sets in terms of orientation), he just provided a colorful yet abstract rendering of the place, noting the things that could be seen from there, and suggesting where you could go, and drawing attention to important details through the narrative.

How did you moved? You simply stated where you wanted to go. In the diagram, the Governess is at the 'Fountain' on the 'Front yard': the place where the adventure starts. Mrs Grose would greet you there, tell you the names of the locations on both the low ground and the first floor; you could later ask Flora to 'give you a tour' of the mansion if you wanted, as in the novel, both to refine your knowledge of the places, and to strenghten your bond with Flora. Mrs Grose would then leave you on the 'Main hall' in Bly's 'Hallway'. And from there, you can now start exploring the whole map right away.

So, if the player was at the 'Road to the lake' and wanted to return to your room (you played as the Governess from the novel), you simply wrote 'go to your room' or 'return to your room', and the Governess will pathfind (each Set represented a node for pathfinding purposes) to her room in the first floor. The process could be thought out like this:

Code: Select all

start: { Road to lake }
finish: { Governess' room }
path: { Bly*, Lobby, Hallway*, Main hall, Stairs, First floor*, Hallway, Governess' room }
Here, the '*' denotes a higher level Set (which contains other Sets). Now, the cost for each node was used to implement the passage of time: so the trip from the road to the lake to the Governess' room took her, say, 20 minutes; but the trip from the 'Stairs' to her room would take her seconds.

As the Governess walked through the Sets, other NPCs also did their stuff: moved around and interacted with the Sets and Props, and generally working out their schedule. Upon entering one non-container location, sometimes an event was triggered that you had to deal with, or just provided some info on the current situation of events in the game. The Governess also pondered while she walked, and offered hints/thoughts on what she should do next/who to talk to/which object she might need to tackle certain task.

There's also other details in the diagram, that we can discuss further if you're interested. I'll do my best to recall how he implemented them at the time (the original was in C++).
...
I'm currently working on animals and npcs having stances (sitting, standing etc) so cats etc can sleep on things, and generally get under your feet in the way that cats do.
...
To me, this is threading too thin. If body language is important (because the player is, say, a detective investigating a murder case), then the effort is justified. But this is of course just my opinion, and if it can be managed, it might prove interesting (especially for conversations and other similar situations).
...
Things already have a size and weight so you cannot put the elephant on the fragile table (not that there is an elephant in the game)...
For this, my friend simply allowed a certain number of 'sizes' on or above each other, with variations. Nothing too concrete, just something along the lines of 'this bag can contain some small objects', 'this table can withstand one large object, several medium objects, and a lot of small objects'. The semantics of the italicized words (like 'several' and 'a lot') was up to the objects to implement, but in the data for the game he just specified them like that (it relied heavily on OOP constructs, so this is trivial to do).
...and I have implemented the ability to carry extra stuff simply by allowing a few different containers to have the player as their container.
...
Indeed, this can be easily implemented with linked lists. Simply change the parent of the container to something else. That's it. Everything is also carried with it, and note that the 'size' tags were made implicit in the model I explained above: you could carry a bag full of stuff that fitted in it, just not a wooden table, since it won't fit on you!.
Last edited by paul doe on Jul 02, 2020 13:46, edited 8 times in total.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

...
Something I am trying to do is allow flexability in how problems are solved, some puzzles will be fixed but I want to try and prevent the use everything with everything style of play.
Regarding this: if you've read the novel in question, you already know that it is very self-contained: few places, few people, few things actually happen (which, notably, does NOT mean the book isn't interesting; quite on the contrary!). Now, my friend wanted something akin to a roguelike: every time you play, it should be different. The Actors, Sets and Props would be the same, with some plot-related exceptions. So, what he did was to randomize the plot of the adventure every time you played.

How this worked? In the novel, Bly's mansion is haunted by two ghosts: that of Mr Quint (the Master's helper), and Mrs Jessel (the former governess). Since the novel pivots around these two figures and the current governess, he used these three elements to change the plot: sometimes the ghosts were real, sometimes they were just the governess (and thus the player's!) delusions; sometimes Mrs Grose would be an unconditional ally, sometimes she'll be the contagonist of the story; sometimes Miles was a maniacal murderer, sometimes he was just misunderstood; and so on. This created a lot of dramatical tension between the Actors, since you never knew what everyone was up to (and that, as it turns out, included yourself!).

Thus, once the plot line was established and the main actors decided, it was just a matter of scattering the Props needed for each one, and/or create/remove locations from the game, and decide which puzzles could fit into it. There were few of those, but were really devious, and sometimes even optional: they could, for example, set some events in motion that gave you a better ending (especially the ones that involved both children). I was told that my friend's teacher liked it so much that he played it for weeks, trying to get every plot and see every ending XD

My own war story: while he was implementing it, I was asked to playtest it. So, at one point, I asked Flora to come with me to some place, and she refused. So I had this large bag with me, and actually put Flora in there (since she was stated to be 'smallish' and the bag was somewhat large). It would have turned out fine if it wasn't for Mrs Grose (which I encountered in the hallway), as she asked me what the bag contained. Since it was a large bag, filled to the brim with Flora, the Governess had to make some effort to carry it: some NPCs such as Mrs Grose and the gardener helped you carry something reasonably heavy if you asked them/they saw you carrying it and breaking a sweat in the process.
So I lied; she got suspicious, and demanded I showed her the contents of the bag. So I had to open it, and a really upset Flora came out of it, wanting to kill me; Mrs Grose thought that I was mad, so I got the boot, and a bad ending. My friend laughs even to this day, and frequently told me that 'only you could have tried something like that' =D
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Hi Paul,

Many thanks for the reply. I am really interested in how the game was able to handle the flora smuggling, as I suspect your friend did not write it in(?)
I don't have much idea on how to handle the emotions of npcs yet, other than if you are somewhere they don't want/expect you they get annoyed. But that could be hard coded - if player is in bad guys secret hide-out and bad guy enters.... oops. You mention a diagram, but I cannot see it - am I looking in the wrong place?

Equally pathfinding will be something of a challenge. I have simple short labels for the locations (jetty etc) so could simply do a search through those, but using a hash sounds a better option, except...

For the life of me I cannot remember anything about hastables, time to spend on youtube I guess.

Thanks again.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote:Hi Paul,

Many thanks for the reply. I am really interested in how the game was able to handle the flora smuggling, as I suspect your friend did not write it in(?)
...
Indeed, he did not. I just exploited the implicit object relationships model I explained before. Of course, he got it fixed afterwards =D
...
I don't have much idea on how to handle the emotions of npcs yet, other than if you are somewhere they don't want/expect you they get annoyed. But that could be hard coded - if player is in bad guys secret hide-out and bad guy enters.... oops.
I seem to recal they had a simple sliding scale, and certain actions moved this scale. In the case of the smuggling, Flora wasn't upset because I put her in the bag and tried to kidnap her, but because I forced her to do something she refused to do. It was important to ask politely to each NPC and try to have a good relationship with everyone, especially the two children (Flora and Miles), since this was the way to get the better endings (you, as the Governess of Bly, were tasked to take care of them and their education)
...
You mention a diagram, but I cannot see it - am I looking in the wrong place?
...
Is right there, in the post. The diagram of the adventure map as I (more or less) remember.
...
Equally pathfinding will be something of a challenge. I have simple short labels for the locations (jetty etc) so could simply do a search through those, but using a hash sounds a better option, except...
Pathfinding is trivial to implement in the model he used. Path cost wasn't used as a heuristic, but as a means to rougly establish the passage of time, as I explained. Each Actor had a schedule (including you) that had to be fulfilled over the course of a day. Failure to do so repeatedly would mean you'd get fired (the only way to lose the adventure, since you couldn't die).
olympic sleeper
Posts: 41
Joined: Jun 07, 2020 15:47

Re: Old school text adventure as a way of re-learning coding

Post by olympic sleeper »

Hello Paul,

Many thanks for the reply and link, very useful, regarding the map, oops. I was looking for an image in your post rather than a link and missed it completely, sorry.

I wonder if you, or anyone else for that matter, can suggest a way forward with an issue I’m having regarding the object’s descriptions when printing a room. As mentioned I am walking through a tag list (now slightly amended from the previous) and describing the objects that the tags relate to. It *amost* works, but that almost is causing me a real headache. Here’s an example output, The whole text is auto generated depending on where you are and where the objects are in ralation to each other.

Code: Select all

You are standing at the top of a wooden ladder in the middle of a sandy beach that stretches to the north and south.

Within touching distance on a cliff ledge is a Jackdaw nest. Inside this there is a small silver key. out of reach below a wooden crate containing a bank safe and inside this there is an open cash box and a lumpy cloth bag and on it there is a rubber duck. Inside the cash box there is a silver penny. Underneath a steel table there is an iron bound chest.                                                                                                                                          You can also see a small pebble and an arm chair here.
The second paragraph is missing words here and there as below;
Within touching distance on a cliff ledge is a Jackdaw nest and inside this there is a small silver key. Out of reach below, is a wooden crate containing a bank safe and inside this there is an open cash box and a lumpy cloth bag and on it there is a rubber duck. Inside the cash box there is a silver penny. Underneath a steel table there is an iron bound chest. You can also see a small pebble and an arm chair here.
So its close, but when I try to fix the problems I get spurious occurrences of ‘is and ‘and’ where I don’t want them and am adding more and more exceptions to the code to try and iron them out. In short the code is a mess and until I sort it out I wont be able to move on.

So could someone suggest, perhaps, a technique that will enable me generate text in the above format? It would be easy just to list the things you can see, but I want this to read more like a novel than a shopping list. In particular I am trying to resolve the following.

At the start of the description You can see, or You are near/next to within touching distance etc is printed followed by all close objects depending on
1. height – everything at your height is accessible, provided its not in a closed container,
2. distance – not that important, but if I get get you are standing next to a lampost … then great, as you can see I am almost there on that.
3. If something has been described before it is referred to in its short form or as it, this or that.
4. Overly long lists are split by punctuation – this is causing me a headache
5. Things at a different height are described as out of reach, as you can see this is almost working, but odd words are missing, and when I try to add them the code then starts dumping them everywhere.

1 is causing me problems with anything other than a standard 'you can see' in formatting the run-on from this and what (if any) conjuctions are there.
5. equally is a problem, the code is trying to look at the player's height, the height of the current object and the height of the prevoiusly described object to try and prevent a zig-zag effect of within touching distance is a duck and out of reach is a key and within touching distance... etc. The tag list has ben built so that all 'near' objects height wise are first in the list.

I guess I'm looking for a pointer to an idea, like the Dijkstra's algorithm that I have just missed. Any ideas?
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

olympic sleeper wrote:Any ideas?
Several, as usual =D
olympic sleeper wrote:...
I was looking for an image in your post rather than a link and missed it completely, sorry.
Strange. The post should show a small(ish) preview image, linked to a bigger version of it. It shows it here.
...

Code: Select all

Within touching distance on a cliff ledge is a Jackdaw nest [u]and [/u]inside this there is a small silver key. [u]O[/u]ut of reach below[u],[/u] [u]is [/u]a wooden crate containing a bank safe and inside this there is an open cash box and a lumpy cloth bag and on it there is a rubber duck. Inside the cash box there is a silver penny. Underneath a steel table there is an iron bound chest.                                                                                                                                          You can also see a small pebble and an arm chair here.
That is precisely why my friend above settled for a more 'abstract' description, with detail added as you 'examined' things at will: clutter. One way to keep the sentences to a manageable level is simply to not allow so many objects at the same location. For example, the output could be:

Code: Select all

Near a cliff ledge there is a Jackdaw nest. Below, there is a wooden crate, but it's out of your reach; a steel table lies before you, with an iron bound chest underneath.
Or:

Code: Select all

A Jackdaw nest lies near a cliff ledge. There is a wooden crate that's out of your reach, below. Also, a steel table is over there, and an iron bound chest lies underneath.
As you can see, only the format of the description changes, not its contents. You can have, say, an array of these formats handy and chose one randomly when you describe the objects. You could use some representation for the grammars such as BNF, and perform the appropriate substitutions when you emit the text. Then, you would select a grammar from a set depending on the object's composition, it's gender, its position relative to the player, and all sorts of other criteria; have a set of those (3 or 4 will suffice), and then chose one at random from this last set. That could provide some of the 'narrative flavour' you seek without overcomplicating implementations (you'll only have to code the substitution algorithm), and provide a useful way to fine tune the sentences/grammars as you want.

However, for this to work at maximum efficiency, the description should be as uncluttered and clear as possible, with all unneeded details removed. It is a fact that the average person can only retain so much info on their minds at once (that's why we invented computers). The article suggests the average person can only work with four bits of info at once in their minds. If you stay within those bounds, you'll not only avoid straining your players, but also simplify your implementations and make the adventure more enjoyable at the same time, which is no small feat ;)

For example, you can see the wooden crate, but is it open? If it is, you could see 'a metal box' inside from where you are, but since the description states it's 'out of reach', how could the PC assert that it is indeed a 'bank safe', and even know its contents (and the content of its contents)?

Also, to me, 'out of reach', suggests that you can't effectively reach it from where you are (you have to try another way), not that it is several feet apart and you have to walk over it to examine/interact with. If the semantics is indeed this last one, you'll have to implement positioning within locations, which will become a mess pretty quickly (especially with only text to convey information). In that regard, it would be better (easier from an implementation/description standpoint) to simply make the PC movement within a location implicit, and just assume that he/she will be close/right next to the object you last interacted with (or at the starting position if no interaction has taken place).

As an added benefit, it will make the adventure more dynamic. But that's up to you, of course. If you want to implement detailed positioning, be aware of the fact that the objects positioning relative to each other will change if you enter a location from the south or from the north, say. Also, this relativity has to be worked on in the descriptions themselves.
...
1. height – everything at your height is accessible, provided its not in a closed container,
2. distance – not that important, but if I get get you are standing next to a lampost … then great, as you can see I am almost there on that.
3. If something has been described before it is referred to in its short form or as it, this or that.
4. Overly long lists are split by punctuation – this is causing me a headache
5. Things at a different height are described as out of reach, as you can see this is almost working, but odd words are missing, and when I try to add them the code then starts dumping them everywhere.

1 is causing me problems with anything other than a standard 'you can see' in formatting the run-on from this and what (if any) conjuctions are there.
5. equally is a problem, the code is trying to look at the player's height, the height of the current object and the height of the prevoiusly described object to try and prevent a zig-zag effect of within touching distance is a duck and out of reach is a key and within touching distance... etc. The tag list has ben built so that all 'near' objects height wise are first in the list.
...
For both '1' and '5', see the suggestion above: using several preset grammars to work with, and chosing one at random to provide variety. To chain the sentences, group the objects by closeness (objects that are at a similar height/distance from the player should be described together), and use one grammar that chains them either by commas (two to four objects) or semicolons (up to five/six). More than that, and you'll need to punctuate. For example:

Code: Select all

You're standing there. Here, there's a big table, with several wooden chairs and a steel chair; and on top of it, there's a box of bullets, a 9mm pistol, and a small key.
Note that 'on top of it' refers to the steel chair, not the table. You can use colons to provide some 'color' for a particular description:

Code: Select all

You're standing there. Here, there's a big table: a scattering of objects decorate it. Surrounding it, there's several wooden chairs and a steel chair; and on top of it, there's a box of bullets, a 9mm pistol, and a small key.
Note also that the sentence that follows the colon needs to establish the narrative context again, so a 'surrounding it' was added, which indeed refers to the table (since it's not in a new paragraph, the context is still the one from the previous sentence).

For a very useful resource on writing, see this page:

Mythcreants

Contains a load of useful posts on storytelling, formatting, tips and tricks of the trade. IIRC, for my friend's adventure, he also used the idea of preset grammars for text emitting (though not BNF; he used a custom one), and also supported things like text coloring/underlined as a way to hint players on interactable/important objects.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: Old school text adventure as a way of re-learning coding

Post by paul doe »

Allow me to flesh out the sentence construction idea a little. Let us not concern with efficiency issues for now, and just implement an associative array as a linked list of key-value pairs. Clarity of concept is preferable at this stage (you can later replace it with a more appropriate/efficient version):

Code: Select all

type KeyValue
  public:
    declare constructor( as const string, as const string )
    declare destructor()
    
    declare property key() as string
    declare property key( as const string )
    
    as string value
    as KeyValue ptr _next, _previous
    
  private:
    as string _key
end type

constructor KeyValue( aKey as const string, aValue as const string )
  _key = lcase( aKey )
  value = aValue
end constructor

destructor KeyValue() : end destructor

property KeyValue.key() as string
  return( _key )
end property

property KeyValue.key( value as const string )
  _key = lcase( value )
end property

operator = ( lhs as KeyValue, rhs as KeyValue ) as integer
  return( lhs.key = rhs.key andAlso lhs.value = rhs.value )
end operator

type AssociativeArray
  public:
    declare constructor()
    declare constructor( as AssociativeArray )
    declare destructor()
    
    declare operator let( as AssociativeArray )
    
    declare operator []( as integer ) byref as string
    declare operator []( as const string ) byref as string
    
    declare property count() as integer
    declare property first() as KeyValue ptr
    declare property last() as KeyValue ptr
    
    declare function clear() byref as AssociativeArray
    declare function add( as const string, as const string ) byref as AssociativeArray
    declare function add( as AssociativeArray ) byref as AssociativeArray
    declare function remove( as AssociativeArray ) byref as AssociativeArray
    declare function removeFirst( as const string ) byref as AssociativeArray
    declare function removeLast( as const string ) byref as AssociativeArray
    declare function removeAll( as const string ) byref as AssociativeArray
    declare function findAll( as const string ) as AssociativeArray
    declare function intersect( as AssociativeArray ) as AssociativeArray
    
  private:
    declare sub remove( as KeyValue ptr )
    
    static as string NULLSTR
    
    as KeyValue ptr _
      _first, _last
    as integer _count
    
    #macro dispose()
      scope
        var n = _first
        
        do while( n <> 0 )
          var tmp = n->_next
          
          delete( n )
          
          n = tmp
        loop
      end scope
    #endmacro
    
    #macro copyFrom( __n__ )
      scope
        var n = __n__
        
        do while( n <> 0 )
          add( n->key, n->value )
          
          n = n->_next
        loop
      end scope
    #endmacro
end type

dim as string AssociativeArray.NULLSTR

constructor AssociativeArray() : end constructor

constructor AssociativeArray( rhs as AssociativeArray )
  copyFrom( rhs.first )
end constructor

destructor AssociativeArray()
  dispose()
end destructor

operator AssociativeArray.let( rhs as AssociativeArray )
  dispose()
  copyFrom( rhs.first )
end operator

operator AssociativeArray.[]( index as integer ) byref as string
  NULLSTR = ""
  
  var _
    n = _first, _
    c = 0
  
  do while( n <> 0 )
    if( c = index ) then
      return( n->value )
    end if
    
    n = n->_next
    c += 1
  loop
  
  return( NULLSTR )
end operator

operator AssociativeArray.[]( key as const string ) byref as string
  NULLSTR = ""
  
  var n = _first
  
  do while( n <> 0 )
    if( lcase( key ) = n->key ) then
      return( n->value )
    end if
    
    n = n->_next
  loop
  
  return( NULLSTR )
end operator

property AssociativeArray.first() as KeyValue ptr
  return( _first )
end property

property AssociativeArray.last() as KeyValue ptr
  return( _last )
end property

property AssociativeArray.count() as integer
  return( _count )
end property

sub AssociativeArray.remove( aNode as KeyValue ptr )
  if( aNode->_previous = 0 ) then
    _first = aNode->_next
    _first->_previous = 0
  elseif( aNode->_next = 0 ) then
    _last = aNode->_previous
    _last->_next = 0
  else
    aNode->_previous->_next = aNode->_next
    aNode->_next->_previous = aNode->_previous
  end if
  
  if( aNode <> 0 ) then
    delete( aNode )
    _count -= 1
  end if
end sub

function AssociativeArray.clear() byref as AssociativeArray
  dispose()
  _first = 0
  _last = 0
  _count = 0
  
  return( this )
end function

function AssociativeArray.add( aKey as const string, aValue as const string ) byref as AssociativeArray
  var newNode = new KeyValue( aKey, aValue )
  
  newNode->_previous = _last
  newNode->_next = 0
  
  if( _last = 0 ) then
    _first = newNode
  else
    _last->_next = newNode
  end if
  
  _last = newNode
  _count += 1
  
  return( this )
end function

function AssociativeArray.add( rhs as AssociativeArray ) byref as AssociativeArray
  var n = rhs.first
  
  do while( n <> 0 )
    add( n->key, n->value )
    
    n = n->_next
  loop
  
  return( this )
end function

function AssociativeArray.remove( rhs as AssociativeArray ) byref as AssociativeArray
  var n = _last
  
  do while( n <> 0 )
    var rhsn = rhs.last
    
    do while( rhsn <> 0 )
      if( *n = *rhsn ) then
        remove( n )
      end if
      
      rhsn = rhsn->_previous
    loop
    
    n = n->_previous
  loop
  
  return( this )
end function

function AssociativeArray.removeFirst( aKey as const string ) byref as AssociativeArray
  var n = _first
  
  do while( n <> 0 )
    if( lcase( aKey ) = n->key ) then
      remove( n )
      exit do
    end if
    
    n = n->_next
  loop
  
  return( this )
end function

function AssociativeArray.removeLast( aKey as const string ) byref as AssociativeArray
  var n = _last
  
  do while( n <> 0 )
    if( lcase( aKey ) = n->key ) then
      remove( n )
      exit do
    end if
    
    n = n->_previous
  loop
  
  return( this )
end function

function AssociativeArray.removeAll( aKey as const string ) byref as AssociativeArray
  var n = _first
  
  do while( n <> 0 )
    if( lcase( aKey ) = n->key ) then
      remove( n )
    end if
    
    n = n->_next
  loop
  
  return( this )
end function

function AssociativeArray.findAll( aKey as const string ) as AssociativeArray
  var _
    a = AssociativeArray(), _
    n = _first
  
  do while( n <> 0 )
    if( lcase( aKey ) = n->key ) then
      a.add( n->key, n->value )
    end if
    
    n = n->_next
  loop
  
  return( a )
end function

function AssociativeArray.intersect( rhs as AssociativeArray ) as AssociativeArray
  var _
    a = AssociativeArray(), _
    n = _first
  
  do while( n <> 0 )
    var rhsn = rhs.first
    
    do while( rhsn <> 0 )
      if( *n = *rhsn ) then
        a.add( n->key, n->value )
      end if
    
      rhsn = rhsn->_next
    loop
    
    n = n->_next
  loop
  
  return( a )
end function

operator + ( lhs as AssociativeArray, rhs as AssociativeArray ) as AssociativeArray
  return( AssociativeArray().add( lhs ).add( rhs ) )
end operator

operator - ( lhs as AssociativeArray, rhs as AssociativeArray ) as AssociativeArray
  return( AssociativeArray( lhs ).remove( rhs ) )
end operator

operator ^ ( lhs as AssociativeArray, rhs as AssociativeArray ) as AssociativeArray
  return( AssociativeArray( lhs ).intersect( rhs ) )
end operator
Now, with the associative array in place, the sentence building algorithm can use a very simple scheme for replacement, which is nonetheless quite easy to extend and improve:

Code: Select all

'' For choosing a value between min and max randomly
#define range( _min_, _max_ ) ( int( rnd() * ( ( _max_ + 1 ) - _min_ ) + _min_ ) )

'' Returns a string with all tags replaced.
function replaced( s as const string, tags as AssociativeArray ) as string
  dim as string result = s
  dim as integer pos_ = 0
  
  do while( pos_ < len( result ) - 1 )
    if( chr( result[ pos_ ] ) = "{" ) then
      dim as string tag = ""
      
      '' Skip start tag
      pos_ += 1
      
      do while( pos_ < len( result ) andAlso chr( result[ pos_ ] ) <> "}" )
        tag += chr( result[ pos_ ] )
        pos_ += 1
      loop
      
      '' Skip end tag
      pos_ += 1
      
      '' Fetch all texts associated with this tag
      var texts = tags.findAll( tag )
      
      '' And perform the replacement, choosing one text randomly
      var _
        leftPart = left( result, pos_ - ( len( tag ) + 2 ) ), _
        repl = texts[ range( 0, texts.count - 1 ) ], _
        rightPart = mid( result, pos_ + 1, len( s ) )
        
        result = leftPart + repl + rightPart
    end if
    
    pos_ += 1
  loop
  
  return( result )
end function

randomize()

/'
  Define some tags, and the texts associated with it.
  
  Note that, when performing the replacement, the associated text
  will also get replaced. This way, you can create really complex,
  believable sentences from a relatively small set of tags and
  texts. And of course, you can replace the simple random scheme
  with any other more useful and/or appropriate.
'/
var tags = AssociativeArray() _
  .add( "action", "do something {qualifier} cool" ) _
  .add( "action", "think of something {qualifier} profound" ) _
  .add( "stat", "fetch something {qualifier} shiny" ) _
  .add( "stat", "look at something {qualifier} nice" ) _
  .add( "qualifier", "unbelievably" ) _
  .add( "qualifier", "awfully" ) _
  .add( "qualifier", "incredibly" ) _
  .add( "qualifier", "really" )

var statement = "I want to {action}, and also {stat}!"

? replaced( statement, tags )

sleep()
Last edited by paul doe on Jul 05, 2020 5:42, edited 1 time in total.
Post Reply