Random number sequence

Hi, I'm having a problem in my game and hoping you get help me solve it.
Without going into too many details, my problem is this.
I want 5 random numbers in the range 1-5, (or 0-4) but those numbers cannot be duplicated.
I also want let's say 8 occurrences of that sequence of 5 numbers, but those cannot be duplicated either.

eg.
1 2 3 4 5
1 3 2 4 5
1 4 2 3 5
1 5 4 2 3
2 1 3 4 5
2 3 1 4 5
...

Hopefully you get the idea
Now forgive the pseudo-code

a = randomnumber(1-5)
b = randomnumber(1-5)
c = randomnumber(1-5)
d = randomnumber(1-5)
e = randomnumber(1-5)

If a=b or a=c or a=d or a=e or b=c or b=d or b=e or c=d or c=e or d=e
then regenerate random numbers

It's the next bit that I'm having trouble with; The eight other sequences that cannot match.
The eight sequences of 5 numbers would have to be stored in an attribute so that the same set of numbers is printed in a certain location.

eg.
1 2 3 4 5 - is printed in the kitchen
2 1 4 3 5 - is printed in the hall
3 5 1 4 2 - is printed in the study

Is this possible.
Can anyone help.
Many thanks.


I think your system may have a problem because when you pick 5 numbers from 1 to 5, the probability of them all being different is ⅘×⅗×⅖×⅕, or around 1 in 26; and generating random numbers tends to be one of the slowest functions in a programming language.

My instinct is to do something like this. A little over-complex, but I think it's more stable.

<function name="MakeNumberSequence" type="stringlist">
  numbers_not_used = Split ("1;2;3;4;5")
  result = NewStringList()
  for (i, 1, 5) {
    next_number = PickOneString (numbers_not_used)
    list remove (numbers_not_used, next_number)
    list add (result, next_number)
  }
  if (HasAttribute (game, "used_number_sequences")) {
    while (ListContains (game.used_number_sequences, Join(result, ""))) {
      PermuteNumberSequence(result)
    }
  }
  else {
    game.used_number_sequences = NewStringList()
  }
  list add (game.used_number_sequences, Join(result, ""))
  return (result)
</function>

<function name="PermuteNumberSequence" parameters="sequence">
  change = PickOneString (sequence)
  list remove (sequence, change)
  list add (sequence, change)
</function>

MakeNumberSequence() returns a number sequence, a stringlist with 5 elements. It puts the sequences it has generated into a stringlist attribute, and checks against them to make sure it doesn't pick the same sequence twice. If it finds a duplicate, it calls PermuteNumberSequence, which takes a randomly selected item out of the list and moves it to the end. This takes slightly less than a fifth of the time that generating a whole new sequence would take, and I think it has the same chance of generating a non-matching one.

(Actually, it's not going to be that slow; because if you generated 8 number sequences at random, there'd be a 78.7% chance of them all being different)

Alternate method

If you were looking for more sequences, I'd suggest making a list of all the different sequences of 5 digits. There's 3125 possible sets of 5 digits from 1 to 5; of which only 120 contain all 5 digits in some order. So it wouldn't take too long for a computer to make a list of all 120, and then you can just pick one at random from the list. If you remove it from the list when you've used it, then you know you'll get all different ones.

Something like (off the top of my head, not yet tested):

<function name="GetUniqueNumberSequence" type="string">
  firsttime {
    game.unused_number_sequences = NewStringList()
    done = false
    output = ""
    number_to_add = 1
    can_remove = false
    while (not done) {
      output = output + " " + ToString(number_to_add)
      if (LengthOf(output) = 10) {
        list add (game.unused_number_sequences, Right (output, 9))
        can_remove = true
      }
      else {
        number_to_add = 1
      }
      while (can_remove or Instr(output, ToString(number_to_add)) > 0) {
        if (can_remove) {
          number_to_add = ToInt (Right (output, 1))
          output = Left (output, LengthOf(output) - 2)
          can_remove = false
        }
        if (number_to_add = 5) {
          if (output = "") {
            done = true
          }
          else {
            can_remove = true
          }
        }
        else {
          number_to_add = number_to_add +1
        }
      }
    }
  }
  result = PickOneString (game.unused_number_sequences)
  list remove (game.unused_number_sequences, result)
  return (result)
</function>

(actually... if you want easier-to-read code rather than more efficient:

firsttime {
  game.unused_sequences = NewStringList()
  for (a, 1, 5) {
    for (b, 1, 5) {
      for (c, 1, 5) {
        for (d, 1, 5) {
          for (e, 1, 5) {
            if (not (a=b or a=c or a=d or a=e or b=c or b=d or b=e or c=d or c=e or d=e)) {
              list add (game.unused_sequences, ""+a+" "+b+" "+c+" "+d+" "+e)
            }
          }
        }
      }
    }
  }
}
result = PickOneString (game.unused_sequences)
list remove (game.unused_sequences, result)
return (result)

obviously, you want the computer to generate your sequence combinations, especially if they scale (nearly no work for you: sequences of only a single number, example '1', would be a/the single sequence combination of: 1 --- compared to --- for example: sequences of '1 to 5' numbers, factorial: 5! = n(n-1)(n-2)(n-3) = 5 x 4 x 3 x 2 = 120 sequence combinations: 12345, 12354, 12435, 12453, 12534, 12543, 13245, 13254, 13425, 13452, 13524, 13542, 14235, 14253, 14325, 14352, 14523, 14532, 15234, 15243, 15324, 15342, 15423, 15432, 21345, etc etc etc, 54321) (note that the '1XXXXs' have 24 combinations, and thus '5x[24] = 5x[4x3x2] = 120 = 24 '1XXXX' combinations + 24 '2XXXX' combinations + 24 '3XXXX' combinations + 24 '4XXXX' combinations + 24 '5XXXX' combinations = 120 combinations)

AND, thus, you've no need for using randomization chance (as that's just extra un-needed operations until you get matches/hits, so instead, directly generate correct-viable sequence combinations with your design) for generating your sequence combinations, you'd just use randomization for selecting which sequence combination out of all (or X selected quantity) of them

I'm not sure which is more/most efficient:

  1. iteration (if you can do this without any extra/nested iterations, which might use #2 or maybe not, not sure if mrangel's first design is doing a single iteration or not) (nested/layers of iteration is exponential: no nesting aka a single iteration: N^1, each extra nesting/layer of iteration: N^2, N^3, N^4, etc etc etc, which is very steep/inefficient/LOTS_OF_OPERATIONS_QUICKLY_LOL, though in terms of the hardware itself, it does iterations very fast, so this might mitigate the exponential nature of nesting iterations, but it won't mitigate the space that is used/needed for it, I think... lol)

  2. "shift/bit" handling (and single iteration or maybe via concatenation is possible as substitute for iteration, but withOUT recursion), but would probably be messy handling... I think)

  3. recursion (using #1 and/or #2 and/or whatever other design)


you'd then just store and/or select which and how many sequence combinations you want


I was trying to create a design for you, but I suck at math work, so was having trouble with doing recursion (still not that good at recursion yet) and/or "shift/bit" handling... I need more time, and a more clear/focused/non-tired head/brain... sighs... maybe at some point I can be at mrangel's level, and not take likely (at least) a week, to come up with some working design... lol. mr angel did those designs in like an hour... HK is jealous... sighs... programming, especially math work, is not easy when you're not very smart and especially when you also suck at math, sighs... lol


K.V.

Have you ever read any of mrangel's books?

You should check one out, if not. (Especially if you have Kindle Unlimited (which has a 30 day trial period)).

https://www.amazon.com/Angel-Wedge/e/B00N5Q5XIK

#SupportYourLocalCodeSlinger


Thanks guys,
Do those functions mrangel, just generate the one set of 5 numbers, or do the generate all eight sets of 5 numbers.


@Dr Agon

The functions I suggested each generate a sequence. But they keep track of the ones they've previously generated, so if you run them more than once they're guaranteed to give different sequences.

The first one gives a stringlist with 5 elements, one number in each. The other two return a string like "1 2 4 5 3". I assume you can convert between the two.

Actually, the last one could be made a little more efficient:

firsttime {
  game.unused_sequences = NewStringList()
  for (a, 1, 5) {
    for (b, 1, 5) {
      if (not a=b) {
        for (c, 1, 5) {
          if (not (a=c or b=c)) {
            for (d, 1, 5) {
              if (not (a=d or b=d or c=d)) {
                for (e, 1, 5) {
                  if (not (a=e or b=e or c=e or d=e)) {
                    list add (game.unused_sequences, ""+a+" "+b+" "+c+" "+d+" "+e)
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
result = PickOneString (game.unused_sequences)
list remove (game.unused_sequences, result)
return (result)

KV, mrangel. PM'd. Hopefully that explains less cryptically what I'd like to do.
Any thoughts, get back to me.


So my idea then, would be to split the attribute. In this case game.unused_sequence and have quests own {select Text processor function print the result.

eg.
{select:game.unused_sequence:text1:text2:text3:text4:text5} {select:game.unused_sequence:text1:text2:text3:text4:text5} {select:game.unused_sequence:text1:text2:text3:text4:text5}... etc.

K.V.

I'm still trying comprehend that last bit of code from mrangel, but I bet it can be plugged right into that code you just posted (somehow), DrAgon.


"Actually, the last one could be made a little more efficient:"

I seriously love the way you guys think about code. I've learned a lot just reading your posts. If I ever get serious about coding (hey, stop laughing!), This site is a massive resource. And the kind, generous, open people here are a breath of fresh air, to say the very least. I may not understand half of what you chaps are saying, but I sure do love the way you say it!


So, are yout text1/text2/text3 the same each time?
Like, you're trying to generate a "dark, narrow, twisty, turny" tunnel, a "twisty, narrow, turny, dark" cavern, and similar?

If so:

firsttime {
  words = Split("text1;text2;text3;text4;text5")
  game.unused_sequences = NewStringList()
  foreach (a, words) {
    foreach (b, words) {
      if (not a=b) {
        foreach (c, words) {
          if (not (a=c or b=c)) {
            foreach (d, words) {
              if (not (a=d or b=d or c=d)) {
                foreach (e, words) {
                  if (not (a=e or b=e or c=e or d=e)) {
                    list add (game.unused_sequences, a+", "+b+", "+c+", "+d+", "+e)
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
result = PickOneString (game.unused_sequences)
list remove (game.unused_sequences, result)
return (result)

This function will return a string like "text1, text4, text3, text5, text2"

Or a little less efficient, but maybe easier to read:

firsttime {
  words = Split("text1;text2;text3;text4;text5")
  game.unused_sequences = NewStringList()
  foreach (a, words) {
    foreach (b, words - a) {
      foreach (c, words - a - b) {
        foreach (d, words - a - b - c) {
          foreach (e, words - a - b - c - d) {
            list add (game.unused_sequences, a+", "+b+", "+c+", "+d+", "+e)
          }
        }
      }
    }
  }
}
result = PickOneString (game.unused_sequences)
list remove (game.unused_sequences, result)
return (result)

If I'm understanding this correctly, mrangel is creating a list of every possible combination (string of 5 texts) of texts. Brilliant coding!


If I'm understanding this correctly, mrangel is creating a list of every possible combination (string of 5 texts) of texts. Brilliant coding!

Yep; all in the firsttime block. Then every time you call the function, it just uses PickOneString to pull one out of the list at random; and removes that one from the list so that it won't be chosen again.

This method is pretty neat, because you can easily add a text6 to the Split statement , and it will give you every combination of 5 texts from those 6.

The main problem is that generating all of the sequences can take up quite a bit of memory. For 5 strings, there's 120 sequences, which isn't a lot. But then for 6 strings there would be 720 options in the list, and for 7 strings 5040. So, this method only works for relatively small sets (above 10 strings or so, I think the server might start having issues with it)


And now for something completely different...
Does Quest have a "Base" function or command?
As in "Octal", or "Hex" or a selectable base?
I know it can convert to Roman Numerals…


As in "Octal", or "Hex" or a selectable base?

Not that I know of. But…

<function name="BaseToString" parameters="number, base" type="string"><![CDATA[
  digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  if (number < 0) {
    return ("-" + BaseToString(-number, base))
  }
  else if (number = 0) {
    return ("0")
  }
  result = ""
  while (number > 0) {
    result = result + Mid (digits, number % base + 1, 1)
    number = number / base
  }
  return (result)
]]></function>

<function name="BaseToInt" parameters="txt, base" type="int"><![CDATA[
  digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  txt = UCase(Trim(txt))
  if (Left(txt, 1) = "-") {
    return (0 - BaseToInt (Mid(txt, 2), base))
  }
  result = 0
  while (not txt = "") {
    result = result * base
    i = Left (txt, 1)
    txt = Mid (txt, 2)
    i = Instr (digits, i) - 1
    if (i < 0) {
      error ("Unexpected character in string")
    }
    result = result + i
  }
  return (result)
</function>

That looks like it could convert single digits, but not longer than that (???)
But I bet someone could come up with a function that would convert a decimal number (20) to:
24 Octal,
14 Hex,
32 "Base 6".


That looks like it could convert single digits, but not longer than that (???)

Did I make a mistake? It's just off the top of my head, but I don't see an error. What does it give if you put that function in and do msg (BaseToString(20, 6))?


I looked online, a converter uses recursive math... (math loop)
OH, Looks like your last code, BaseToInt, is recursive...
I guess I didn't read into it enough...


Sorry, I've not replied, work gets in the way of play far too often.

Yes mrangel, I wanted those 5 words or similar to be displayed as part of a description for a room or series of rooms in a maze or cavern system to provide a subtle difference to the location description for the unwary adventurer who stumbles across it. Maybe even to add a random ',' into the location description to really keep them on their toes. eg "narrow dark twisty" or "narrow, dark twisty" or "narrow dark, twisty" but each location would still have to be unique to enable the adventurer to map the maze. I don't like using the built in map; in my early career as a budding text adventurer, maps weren't part of the built-in-game, you had to go exploring.


K.V.

I don't like using the built in map; in my early career as a budding text adventurer, maps weren't part of the built-in-game, you had to go exploring.

Hear! Hear!


That sounds pretty neat.
Here's an alternative version that could be put in your start script. This one might make it a little more obvious to the player what you're doing:

words = Split ("twisty;turny;dark;scary;smelly;fishy;gloomy")
rooms = AllRooms()
options = ListCompact (words)
oldoptions = NewList()
while (ListCount(rooms) + ListCount(words) >= ListCount(options)) {
  foreach (s, ListExclude (options, oldoptions)) {
    list add (oldoptions, s)
    foreach (w, words) {
      if (not Instr (s, w) > 0) {
        list add (options, s+" "+w)
        list add (options, s+", "+w)
      }
    }
  }
  if (ListCount(options) = ListCount(oldoptions)) {
    newwords = NewStringList()
    options = NewStringList()
    oldoptions = NewStringList()
    foreach (v, words) {
      list add (newwords, v)
      foreach (u, words) {
        if (Instr(u, v) + Instr(v, u) = 0) {
          if (not ListContains(newwords, u+v)) {
            list add (newwords, u+v)
            list add (options, u+v)
          }
        }
      }
    }
    words = newwords
  }
}
options = ListExclude (options, words)
foreach (o, rooms) {
  o.wordsequence = PickOneString(options)
  list remove (options, o.wordsequence)
  msg ("Found a "+o.wordsequence+" "+o.name+".")
}

That should give each room a wordsequence attribute. So you can use {this.wordsequence} in the description.
Instead of just making sets of 5 words, it makes sets of 2 or more words and does the thing with the commas as well.

Remove the msg() line after debugging :p
If you're using an older version of Quest, you might not have an AllRooms() function, so change it to AllObjects() or something. It doesn't matter if you give objects a sequence of words as well; just don't use it if you don't need it.
(I included a little bit of silliness in the case that you don't give it enough words for the number of rooms… if the code isn't clear, try it and see)


That last bit of coding is throwing an error mrangel.

Error running script: Error compiling expression 'ListExclude (options, words)': FunctionCallElement: Could find not function 'ListExclude(QuestList1, QuestList1)'
Both 1's in that last bit are preceded by a backwards apostrophe `, but it hasn't shown up when I've copied the error message.


K.V.

Those are two different types of lists.

ListCompact changes it from a string list to a list.

So, words is a string list and options is just a list, and apparently ListExclude needs them to be the same kind of list.


Method 1

Change options = ListExclude (options, words) to options = options - words.


Method 2

Change the first line to this:

words = ListCompact (Split ("twisty;turny;dark;scary;smelly;fishy;gloomy"))

NOTE

To find this out, I added this just before the line of code which was referenced by the error:

msg(TypeOf(options))
msg(TypeOf(words))

I've used method 2 KV, that works great. And thanks again for the coding mrangel, but {this.wordsequence}, doesn't appear to be working. I tried putting that in and all it printed was {this.wordsequence}, however, when I changed this, to the name of the room, it worked great.


Oh; I thought the textprocessor this was set automatically for descriptions. You could see what I mean, though.


yeah, cool, thanks


yeah, cool, thanks


K.V.

You can put this in the script before the msg with the text processor stuff to make "this" work:

game.text_processor_this = this

Hi mrangel, just having a look at your other coding to see which combination I like best, but there seems to be an error with the coding above dated 25 May.
The first block of code produces the following error message:
Error running script: Error compiling expression 'not (a=e or b=e or c=e or d=e)': CompareElement: Operation 'Equal' is not defined for types 'String' and 'Double'
Error running script: Object reference not set to an instance of an object.

And the second block of code produces this when I want to print out the description of the room:
text5, text3, text2, text4, 2.71828182845905

Not sure how to go about fixing either.


Ooops, sorry :p

Change the 5th one from e to f.
I used a/b/c/d/e to match your pseudocode If a=b or a=c or a=d or a=e or b=c or b=d or b=e or c=d or c=e or d=e, because I thought it would be easier to understand.

I completely forgot that Quest uses e to mean Euler's number.


DOH!!! me too.


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums