Random number sequence

Doctor Agon
23 May 2018, 21:29Hi, 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.
mrangel
23 May 2018, 23:29I 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>
mrangel
23 May 2018, 23:34(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)
hegemonkhan
24 May 2018, 01:20obviously, 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:
-
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)
-
"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)
-
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
hegemonkhan
24 May 2018, 01:46I 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.
24 May 2018, 01:50Have 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

Doctor Agon
24 May 2018, 07:20Thanks guys,
Do those functions mrangel, just generate the one set of 5 numbers, or do the generate all eight sets of 5 numbers.
mrangel
24 May 2018, 08:28@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)

Doctor Agon
24 May 2018, 17:51KV, mrangel. PM'd. Hopefully that explains less cryptically what I'd like to do.
Any thoughts, get back to me.

Doctor Agon
25 May 2018, 06:01So 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.
25 May 2018, 06:26I'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.
JenniferCampbell
25 May 2018, 11:22"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!
mrangel
25 May 2018, 12:12So, 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)

Dcoder
26 May 2018, 01:32If I'm understanding this correctly, mrangel is creating a list of every possible combination (string of 5 texts) of texts. Brilliant coding!
mrangel
26 May 2018, 01:41If 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)

DarkLizerd
26 May 2018, 03:36And 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…
mrangel
26 May 2018, 08:36As 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"><
DarkLizerd
26 May 2018, 20:11That 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".
mrangel
26 May 2018, 23:26That 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))
?

DarkLizerd
27 May 2018, 01:42I 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...

Doctor Agon
27 May 2018, 06:14Sorry, 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.
27 May 2018, 06:40I 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!
mrangel
27 May 2018, 07:54That 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)

Doctor Agon
27 May 2018, 22:27That 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, QuestList
1)'
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.
28 May 2018, 00:12Those 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))

Doctor Agon
28 May 2018, 14:14I'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.
mrangel
28 May 2018, 15:01Oh; I thought the textprocessor this was set automatically for descriptions. You could see what I mean, though.

Doctor Agon
28 May 2018, 15:50yeah, cool, thanks

Doctor Agon
28 May 2018, 15:50yeah, cool, thanks

K.V.
28 May 2018, 17:11You can put this in the script before the msg with the text processor stuff to make "this" work:
game.text_processor_this = this

Doctor Agon
28 May 2018, 19:50Hi 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.
mrangel
28 May 2018, 20:32Ooops, 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.

Doctor Agon
28 May 2018, 22:48DOH!!! me too.