Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - Create adventure games for the SMS using Twine

Reply to topic Goto page Previous  1, 2, 3, 4  Next
Author Message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Thu Jan 01, 2015 7:45 pm
Changing extensions like that is definitely a good way to cause confusion.
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Thu Jan 01, 2015 7:58 pm
Yes, I must have made some "whoopsie" when I adapted SAM to use PSGaiden compression... :P
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sat Jan 03, 2015 7:49 pm
Hey Haroldoop, I'm now starting to put my story together and could really benefit from a few more of the features that are in both Twine and in SAM but not yet handled by the twee2sam.py parser.

The things I could really use are:

1. Print a variable - twine does this either via [i]print $var1[/i] or just [i]$var1[/i]. So I can display number of turns, amount of gold earned, hit points remaining etc.
2. Greater than and less than operators. Does player strength beat monster defence? e.g. [i]is $var1 > $var2[/i].
3. Add and subtract to/from variable. Increment amount of gold, decrement number of turns left until the world is doomed, etc. Twine allows [i]set $var1 = $var1 + 1[/i].


Those 3 additions should really allow a lot more flexibility and complexity in an adventure.

One more thing though, which is not in Twine or SAM is a 'return to' method, so you can set the label of a passage in a variable, jump somewhere else and then call 'return to' returning to perhaps a passage several calls previous without those passages actually having a static link back to the calling passage.

The reasoning for this last one would be that you could have a sequence of fighting/inventory/status screens defined and callable from any passage in the story. This could let you write complex, standalone 'subroutine' passages and call them from anywhere in your adventure.

Any ideas if these would be easily doable?
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sat Jan 03, 2015 9:21 pm
Megatron-UK wrote
Hey Haroldoop, I'm now starting to put my story together and could really benefit from a few more of the features that are in both Twine and in SAM but not yet handled by the twee2sam.py parser.


Hello, nice to see you're having success. ;)

Anyway, about your questions:

Megatron-UK wrote

1. Print a variable - twine does this either via print $var1 or just $var1. So I can display number of turns, amount of gold earned, hit points remaining etc.
2. Greater than and less than operators. Does player strength beat monster defence? e.g. is $var1 > $var2.
3. Add and subtract to/from variable. Increment amount of gold, decrement number of turns left until the world is doomed, etc. Twine allows set $var1 = $var1 + 1.


Well, as you already mentioned SAM already supports 16-bit numbers, and has math and comparison comparators implemented. In order to use those on twee2sam, all it would be necessary to do would be to modify AbstractMacro._parse_expression(), which is currently more of a dummy implementation, to be able actually parse a math expression, and also modify main.out_expr() to be able to actually output the parsed expression as a SAM expression; with that being done, the other subroutines that use expressions (well, set and if, at the moment) should "automagically" work with almost no modification; that should solve items 2 and 3. To solve item 1, implementing a print command should be enough, as long as the expression parser thing is also done. No complex expression parser would be needed; I guess the shunting-yard algorithm would be enough for parsing the expressions that are going to be used.

Megatron-UK wrote
One more thing though, which is not in Twine or SAM is a 'return to' method, so you can set the label of a passage in a variable, jump somewhere else and then call 'return to' returning to perhaps a passage several calls previous without those passages actually having a static link back to the calling passage.


Well, one way to do that would be to use SAM's 'c' (that is, call) command, that pushes the current address into the call stack, and then jumps to the script whose number is in the data stack; complementarly, there's the '$' command, that pops the address at the top of the call stack, and returns to it. It should be relatively simple to add 'call' and 'return' commands to twee2sam in order to use those features.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 11:04 am
Last edited by Megatron-UK on Sun Jan 04, 2015 12:19 pm; edited 1 time in total
Ok, have added the code to parse and recognise a print statement, but can't quite work out from the SAM documentation what the correct syntax is to write out to the script file, given the name of a variable?

Have you got any examples in addition to the command.txt file that comes with SAM?

Edit: As a first test, I'm doing the following for now:

def out_print(expr):
         val = expr
         script.write(variables.get_var(val)[0:-1])
         script.write('. ')
         script.write('"\#"')


This outputs a SAM script of something like:

I. "\#"


But despite what I set the variable to (e.g. 99), it always just displays as 0.

LOL, and I've broken the navigation too, oh well!
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 12:13 pm
Megatron-UK wrote
Ok, have added the code to parse and recognise a print statement, but can't quite work out from the SAM documentation what the correct syntax is to write out to the script file, given the name of a variable?

Have you got any examples in addition to the command.txt file that comes with SAM?


You would have to first push the variable's value into the stack and then use a string containing the escape sequence \# to print it. So:

A: "Blah \#"


Would print out "Blah " followed by the value of variable A. Basically, in this example:

  • 'A' pushes the value 0 into the stack (since A is the first variable)
  • ':' pops the number from the top of the stack, gets the value of the variable corresponding to that number, and then pushes the contents of the variable into the stack;
  • "something" prints something; the escape code \# can be used to print numeric variables popped from the top of the stack.


Similarly, you could use:
123: "\#"

To print the value of the 124th variable. There are also a few (unfortunately, non-documented) example scripts on SAM's repository, such as this one.

Please also take into account that twee2sam variable names may not exactly match SAM variable names; there are already some utility functions available to translate the variable names for you: the class VariableFactory contains methods both to declare variables, and to translate them into their SAM equivalents; the internal funcion out_expr(), inside main(), uses a VariableFactory to output the variables used in expressions.

I know that SAM's script language may be a bit weird, but I designed it more as a compilation target, so that higher-level tools, such as twee2sam, could generate code for it. :P
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 12:25 pm
Megatron-UK wrote
Ok, have added the code to parse and recognise a print statement, but can't quite work out from the SAM documentation what the correct syntax is to write out to the script file, given the name of a variable?

Have you got any examples in addition to the command.txt file that comes with SAM?

Edit: As a first test, I'm doing the following for now:

def out_print(expr):
         val = expr
         script.write(variables.get_var(val)[0:-1])
         script.write('. ')
         script.write('"\#"')


This outputs a SAM script of something like:

I. "\#"


But despite what I set the variable to (e.g. 99), it always just displays as 0.


Well, the problem seems to be that the 'store into variable' command (.) is being used instead of 'read from a variable' (:).

For example, to store 99 into the variable I, you could do:
99 I.


And to print it, you would do:
I: "\#"
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 12:32 pm
Ok, got it. print now works (for numbers) - however it's the set macro that isn't correctly storing the variable.

I have a twine passage (double chevrons not shown - they dont work with bbcode...):
The Sands of Terror

set $turn 99
[[Begin Adventure|a1]]


(I've tried set var value, set var = value, set var to value)

This gets translated into the sam code:

"The Sands of Terror"
G:F.
"Begin Adventure"
!
"Begin Adventure
"
?A.
0B.
A:B:=[3j]B:1+B.


i.e, it's missing the setting of the turn variable (which, in this case, is referred to by variable H in my passage which prints it).

If I manually insert the code :
99 H.
in the above sam code, then the print routine works correctly on subsequent pages, correctly displaying the value of 'turn'.

It's getting there now, I'll take another look this afternoon.
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 4:19 pm
Good to see you're making progress! ^_^

The problem that you're having with using 'set' with numeric variables is due to the fact that twee2sam's expression parser is currently more of a unfinished placeholder, and only handles boolean expressions, and only very simple ones at that.

To be more precise, the offending routines would be AbstractMacro._parse_expression(), that is responsible for parsing an expression, and main.out_expr(), that is responsible for generating the SAM code corresponding to the parsed expression. As you can see, those routines currently only support boolean constants. Taking a quick peek at the two routines, it seems _parse_expression() is accepting the numeric constant, but out_expr() is incorrectly treating it as if it was a variable named '99', instead of being a constant of value '99'; that's why the 'set' code is being output as 'G:F.' when it should have been '99F.'; correcting that, the numeric constants should start working okay.

The next step, after making the constants work, would be to make the expressions support math and comparison operations, such as 'var1 + 1' or 'var2 < var1'; that should prove to be more of a challenge, but not very difficult, as long as the supported expressions are kept simple.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 4:38 pm
Yep, I think I've got a hang of it now - I'm almost there updating out_expr to output the storing of literals (i.e. when no operator is found, it will output something like 99 F. in the case of set turn = 99).

Printing numeric variables now works, so I'll have a method of testing the display of some of the arithmetic functions, which, of course, will be a little trickier :)
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 4:49 pm
Keep going, you're almost there! ;)
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3762
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sun Jan 04, 2015 6:41 pm
haroldoop wrote
SAM uses the background both for displaying the text, and for displaying the images. It uses the first 128 tiles to represent the standard ASCII character set (that is, the letters), leaving free 320 tiles for the image.


ASCII chars from 0 to 31 and char 127 are not printable, so you might save some 33 more tiles here, that could remain available for the images.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 7:25 pm
Well, that was easier than I thought!

I'm attaching the modified twee2sam.py and twparser.py files - they now support the following standard Twine syntax:

print $var1 - Displays the contents of numeric variable within a Twine passage.

set $var1 = $var2 + 99 - Set one variable using either two literals, or up to two variables. Both addition and subtraction are supported, but compound statements (in parethesis) and multiplication/division are not (at the moment - it should be relatively easy to do so).

I have not extensively tested the modifications, but they seem to work in my use-case so far (a simple page counter - 'you have read N pages', type of thing).
changes.zip (6.34 KB)
Add print and basic math function support

  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 9:04 pm
Megatron-UK wrote
Well, that was easier than I thought!

I'm attaching the modified twee2sam.py and twparser.py files - they now support the following standard Twine syntax:

print $var1 - Displays the contents of numeric variable within a Twine passage.

set $var1 = $var2 + 99 - Set one variable using either two literals, or up to two variables. Both addition and subtraction are supported, but compound statements (in parethesis) and multiplication/division are not (at the moment - it should be relatively easy to do so).

I have not extensively tested the modifications, but they seem to work in my use-case so far (a simple page counter - 'you have read N pages', type of thing).


Okay, thanks for your contribution! ;)

I've sent the modifications to the git repository and also generated the new Windows binaries.
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 9:09 pm
sverx wrote

ASCII chars from 0 to 31 and char 127 are not printable, so you might save some 33 more tiles here, that could remain available for the images.


Yes, looking at SAM's source code, it seems the first 32 characters are already being shifted; Some small changes on a few constants at SAM_DisplayImage should be enough to use the extra, currently unused, tiles.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 9:53 pm
Ok, here we go, twparser.py and twee2sam.py now support the Twine greater than (gt) and less than (lt) operators:

Some sample Twine code (again, chevrons missing):

if $timer gt $max_time
    "You have run out of time!"
endif



if $hp lt 5
    "You only have"
     print $hp
     "left"
endif

changes.zip (6.6 KB)
Includes greater than and lessthan operators

  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Sun Jan 04, 2015 10:07 pm
I think I need to clean up my local working checkout of twee2sam and create a branch for myself so that merging these changes back in is easier.

Once I've done that I'll look to start work on the call/return mechanism I mentioned. That, along with the print and additional comparison operators should allow me (and others!) to get started with more interactive adventures that have inventories, scores, status tracking and much more :-)
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Sun Jan 04, 2015 10:48 pm
Megatron-UK wrote
I think I need to clean up my local working checkout of twee2sam and create a branch for myself so that merging these changes back in is easier.

Once I've done that I'll look to start work on the call/return mechanism I mentioned. That, along with the print and additional comparison operators should allow me (and others!) to get started with more interactive adventures that have inventories, scores, status tracking and much more :-)


Keep up the good work! ;)
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Mon Jan 05, 2015 11:38 am
Ok, my changes are now in https://github.com/megatron-uk/twee2sam and I've made a pull request for all the changes so far - I've added in the necessary external files from tiddlywiki in order to run twee2sam, as I found them difficult to track down (pip and easy_install couldn't find them).

Back to work today :( ... but hopefully I'll get started on the call/return additions at some point.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Mon Jan 05, 2015 1:10 pm
Quick question over lunchtime!

The operation of the j and c commands in SAM - does the operand refer to the script listed in Script.txt.inc?

For example, if I have a file Script.txt.inc with the following entries:
script1.txt
script2.txt
script3.txt


And in script1.txt I have a line that says:
1c


.. does that then jump to the SAM script held in the file script2.txt?
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Mon Jan 05, 2015 8:46 pm
Megatron-UK wrote
Quick question over lunchtime!

The operation of the j and c commands in SAM - does the operand refer to the script listed in Script.txt.inc?

For example, if I have a file Script.txt.inc with the following entries:
script1.txt
script2.txt
script3.txt


And in script1.txt I have a line that says:
1c


.. does that then jump to the SAM script held in the file script2.txt?


Exactly; they're numbered by the order that they were declared on Script.list.txt, starting at zero. To make it more clear, if you have those scripts:
foo.txt
bar.txt
baz.txt



  • 0c would call foo.txt
  • 1c would call bar.txt
  • 2c would call baz.txt


Inside twee2sam's main() function, there's a dictionary named passage_indexes, , that does this translation; take a look at the comments that say '# Number the passages' and '# Builds the menu from the links'.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Mon Jan 05, 2015 9:05 pm
That's great - I'm sure I can put something together to get a Twine script to use that!
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Mon Jan 05, 2015 10:41 pm
Ok, that's done!

We now have the following two commands available in any Twine story you write (again, chevrons would normally be wrapped around both of these commands):
call text_passage_name


and
return


The former takes one argument - the name of the Twine text passage or subroutine, to call out to. This is effectively the same as a normal menu link, but is essentially instant, requiring no user input; it's activated as soon as the SAM engine parses it - so you may want to hide it until after a pause command, for example.

The latter returns to the original parent, once the subroutine has finished.

It means you can have things like your normal story/adventure text and call out to a set of common subroutines for displaying player progress, scores or showing inventory from anywhere in your adventure, and can then jump back, without having to construct all the links back to the correct locations.

Will submit a pull request to the master repo soon, for now, the updated commands are in my own Github project.
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Mon Jan 05, 2015 11:44 pm
Tick the <disable HTML> box to make chevrons pass through unmolested.
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Mon Jan 05, 2015 11:49 pm
Okay, all of Megatron-UK's and mstea's implementations have been merged with the repository.

The new Windows binaries are available here.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Tue Jan 06, 2015 9:08 am
About the only thing left that I would potentially want to include now is:

1. A dice rolling/random number function
2. Alternate screen layout tags

The former would map closely to the Twine function random(x, y) and allow the simulation of dice rolls with a lower and upper bound. I think this will require some Z80 assembly support in the SAM library though.

The second would allow, for instance, a full screen text layout for those passages that are text-heavy and would benefit more from the 'image' section of the screen being used for text, or to flip the position of the image and text so that the text appears at the top - you could imagine the layout tag being the first element in a Twine passage, if it's not found, then the default layout is used. Again, this is going to need support in the SAM libraries.

The latter would really be more of a 'nice to have', but the former would be incredibly useful and have the ability to introduce a bit of randomness into our games rather than being quite deterministic.

There's a couple of Z80 asm functions I've found that simulate a pseudo random number generator, but I'm not sure where to start. Any ideas?
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Tue Jan 06, 2015 12:03 pm
Random numbers in range are hard to do because it requires multiplication for a naive implementation. Random number generators are hard to do well on consoles, you need to hook into them in the game core to add entropy from user inputs.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Oct 2014
  • Posts: 89
  • Location: Chicagoland, USA
Reply with quote
Post Posted: Tue Jan 06, 2015 12:19 pm
If it's just something like a dice roll, couldn't the code just increment some particular value (specifically for the purpose of a random number) every millisecond/frame, and when it gets to that point in the code (when the user "rolls"), it does some manipulations on that number to create a "random" roll? The odds are slim that people can skew the results on purpose if it's incrementing once per frame and the results are visible on screen even (most people can't trigger on something faster than 30-40ms). If the current value isn't displayed somewhere, and someone's sitting there reading text for an indeterminate amount of time (multiple frames) before some input that depends on the "random" value, this can be very effective and not very computationally intensive. We're not making SHA hashes here ;)
  View user's profile Send private message
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Tue Jan 06, 2015 12:55 pm
Looking through the WLA-DX documentation it looks like there is a macro for just this thing:

----------------
.DBRND 20, 0, 10
----------------

Defines bytes, just like .DSB does, only this time they are filled with
(pseudo) random numbers. We use the integrated Mersenne Twister to generate
the random numbers. If you want to seed the random number generator,
use .SEED.

The first parameter (20 in the example) defines the number of random
numbers we want to generate. The next two tell the range of the random
numbers, i.e. min and max.

Here's how it works:

.DBRND A, B, C

for (i = 0; i < A; i++)
  output_data((rand() % (C-B+1)) + B);

This is not a compulsory directive.


So something like:
.DBRND 1, 1, 10


Would simulate the rolling of a 10-sided die.

I don't know whether that would be available in SAM though, nor how to get the generated byte back (I know zero Z80 assembly)...
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3762
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Jan 06, 2015 1:48 pm
I would start with the simpler approach: use the R register mod (max-min+1) plus min.
If the result isn't satisfactory enough you could use precompiled 128-entry tables and index it with R.
I bet nothing more complex is really needed.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Oct 2014
  • Posts: 89
  • Location: Chicagoland, USA
Reply with quote
Post Posted: Tue Jan 06, 2015 2:20 pm
sverx wrote
I would start with the simpler approach: use the R register mod (max-min+1) plus min.
If the result isn't satisfactory enough you could use precompiled 128-entry tables and index it with R.
I bet nothing more complex is really needed.

*thumbs over Z80 spec* Yeah, that'd do the trick too.
  View user's profile Send private message
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Tue Jan 06, 2015 2:41 pm
Megatron-UK wrote
Looking through the WLA-DX documentation looks like there is a macro for just this thing:

(...)

So something like:
.DBRND 1, 1, 10


Would simulate the rolling of a 10-sided die.


I wouldn't recommend it; 'DBRND 1, 1, 10' will generate a fixed table of 10 fixed random numbers at compile time; not exactly what you need.

ishiyakazuo wrote
sverx wrote
I would start with the simpler approach: use the R register mod (max-min+1) plus min.
If the result isn't satisfactory enough you could use precompiled 128-entry tables and index it with R.
I bet nothing more complex is really needed.

*thumbs over Z80 spec* Yeah, that'd do the trick too.


Using the R register could be a good idea. Another option would be to use a linear congruential generator; the fact that it uses multiplication and modulus shouldn't be a big obstacle, given the fact that SAM already has functions that do that. Another option would be to use an LFSR; it requires no multiplication/modulus, and produces a quite good sequence of random numbers; you may also want to check these other two.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Tue Jan 06, 2015 3:02 pm
haroldoop wrote
Megatron-UK wrote
Looking through the WLA-DX documentation looks like there is a macro for just this thing:

(...)

So something like:
.DBRND 1, 1, 10


Would simulate the rolling of a 10-sided die.


I wouldn't recommend it; 'DBRND 1, 1, 10' will generate a fixed table of 10 fixed random numbers at compile time; not exactly what you need.


That will teach me for not reading what .DSB does!
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Tue Jan 06, 2015 3:32 pm
Assuming you have a random number generator, you need to seed it with entropy, which can mean something as simple as throwing away numbers in a way linked to the user inputs, so that when you get the one you really need, it's not always the same sequence.

On top of that, you need to be careful with the range calculation, a simple modulo will give you a biased result. The correct way to do it is to scale the number range with multiplication and division to retain the uniform distribution.

Statistical analysis of the randomness of these results are likely to be quite poor, it depends how random you need the result to be.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Oct 2014
  • Posts: 89
  • Location: Chicagoland, USA
Reply with quote
Post Posted: Tue Jan 06, 2015 3:59 pm
The timing that a particular user input is received is quite likely to be sufficient entropy for the case of dice rolls.

For relatively small ranges, the bias caused by modulo can be pretty minor. For example, an 8 bit value, modulo 6, means that there are 43 chances you'll get 0-3 and 42 chances for 4-5. So the chances for any number 0-3 will be 16.796875% and 4-5 is 16.40625%. Probably not that much worse than a die with some physical impurities. If the count used for modulo is larger, then this becomes even less statistically significant.

However, if the die being simulated is larger, then using modulo does get significantly worse quickly. For example, a 20-sided die would mean 0-11 would have 13 chances and 12-19 would get 12... so
~5.078125% for 0-11
~4.705882% for 12-19
That's a much more significant chance for bias. (But just make the dice with power-of-2 sides and you're golden!)
  View user's profile Send private message
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Tue Jan 06, 2015 4:17 pm
ishiyakazuo wrote
However, if the die being simulated is larger, then using modulo does get significantly worse quickly. For example, a 20-sided die would mean 0-11 would have 13 chances and 12-19 would get 12... so
~5.078125% for 0-11
~4.705882% for 12-19
That's a much more significant chance for bias. (But just make the dice with power-of-2 sides and you're golden!)


I'm not sure how many folks would want to try and recreate Call of Cthulhu using Twine/SAM ;-)

D6 and D10 are probably sufficient for most things you'd want to model in a tool like this...

At the end of the day, I would guess most people will use it (if they choose to build and adventure with Twine/SAM!) to choose either the dark forest path or the rickety rope bridge or model a hit or miss against a goblin. With a 'gameplay' modifier setting chosen upon starting the adventure (say, -1, 0, +1 to rolls, depending on selecting easy/normal/hard) it should be enough to give a give a less predictable feel to a game.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Oct 2014
  • Posts: 89
  • Location: Chicagoland, USA
Reply with quote
Post Posted: Tue Jan 06, 2015 4:36 pm
Megatron-UK wrote
I'm not sure how many folks would want to try and recreate Call of Cthulhu using Twine/SAM ;-)

*crumples up design docs...*

Megatron-UK wrote
D6 and D10 are probably sufficient for most things you'd want to model in a tool like this...

Yep, agreed, in which case, modulo is probably good enough that people won't notice much bias. And if you're worried about the bias, there are ways to mitigate that too (if the value > X such that you're in the last few values where you'll get the extra instance of some numbers, use super secret secondary randomization routine).
  View user's profile Send private message
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Tue Jan 06, 2015 5:14 pm
Ok, so does anyone want to have a go at this? :-)
  View user's profile Send private message Visit poster's website
  • Joined: 04 Sep 2014
  • Posts: 37
Reply with quote
Post Posted: Tue Jan 06, 2015 6:41 pm
Bounded random numbers is actually something Dragon Crystal does all of the time. It uses a random number generator with the R register that's very similar to the one from Phantasy Star over in Code Snippets:

.define RNGSeed $C016

GetRandomNumber:
   push hl
   ld hl, (RNGSeed)
   ld a, h
   rrca
   rrca
   xor h
   rrca
   xor l
   rrca
   rrca
   rrca
   rrca
   xor l
   rrca
   adc hl, hl
   jr nz, ReseedRNG
   ld hl, $733C
ReseedRNG:
   ld a, r
   xor l
   ld (RNGSeed), hl
   pop hl
   ret


This yields a random number in register a, and it's bounded like so:

.define Range   $0F
.define Minimum $01

call GetRandomNumber ; Get pseudo-random number in a
and Range            ; Use a mask for the range
add a, Minimum       ; Add the minimum value


If you want to be able to do a positive or negative difference, perhaps you could just check the odd/evenness of the r register directly?

Here's an overambitious attempt at a bounded random number offset (eg your +/- 1, but instead of always one a variable number stored in RAM) with a floor and ceiling:

.define Offset      $C020

.define Range       $0F
.define Minimum     $01
.define Maximum     $14

GetBoundedRandomWithOffset:
   ; Get a random number and save it in b
   call GetRandomNumber ; Get pseudo-random number in a
   and Range            ; Use a mask for the range
   ld b, a              ; Save it in b

   ; Positive or negative based on odd/even of value in r
   ld a, r              ; Get r value into c

   bit 0, a             ; If r value is even, go directly to doing Offset, otherwise flip sign on a
   jr z, DoAddition
   jr DoSubtraction

DoAddition:
   ld a, b              ; Retrieve random number
   add a, Minimum       ; Add minimum
   
   ld h, Offset         ; Load the offset from RAM to h
   add a, h             ; Add the offset to a
   
   cp Maximum           ; Check for overflow beyond maximum- if greater than max carry set
   jr c, Overflow
   ret

Overflow:
   ld a, Maximum
   ret

DoSubtraction:
   ld a, b              ; Retrieve random number
   add a, Minimum       ; Add minimum
   
   ld h, Offset         ; Load the offset from RAM to h
   sub h                ; Subtract the offset
   
   cp Minimum           ; Check for underflow less than minimum- if less than min c reset
   jr nc, Underflow
   ret

Underflow:
   ld a, Minimum
   ret


That _should_ give a random number from 1 to 16, adjusted by a buff/handicap stored in RAM (I've given it $C020 arbitrarily), with a total upper bound of 20. With some adjustment I think you could store the spread in RAM as well.

I suspect the subtraction side needs work, though, at least to handle the case where the value goes below 0, eg if the offset is 4 but your minimum is only 3 and a 0 was generated. I couldn't find good information on how sub/cp handled underflow and am not by an emulator to check.
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Tue Jan 06, 2015 6:56 pm
Bounding with bitwise and only works for ranges that are 2^n - 1. Offsetting with addition is of course trivial.

I'd say the best approach is to take a decent generator, mask the bits to the next highest suitable bit mask, then discard results that are out of range. This then takes a variable length of time to find an in range value, though. For some generators, it may be better to prefer the lower or higher bits.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3762
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Tue Jan 06, 2015 7:16 pm
I believe when you're going to use the random function not very often (with 'often' I mean several times each second) then anything will do. If you're just going to roll a dice sometimes with many seconds between tosses... well, I would just give this function the value taken straight from R into D, give E value 6 and add 1 to A after that. I wonder after how many thousand dice tosses you would be ever able to calculate it's not good enough ;)
  View user's profile Send private message Visit poster's website
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14687
  • Location: London
Reply with quote
Post Posted: Tue Jan 06, 2015 8:06 pm
Many older emulators don't emulate the r register so it's bad to rely on it. Hopefully modern emulators are OK though. You would notice the bias on a modulo 6 calculation, assuming the value is shown. Better to hide the values and define a threshold for random actions, e.g. the monster attacks if the random number is more than 100 (so chance = 155/256, assuming uniform RNG in the range 0..255). Then there's no modulo and a fair chance with plenty of granularity.
  View user's profile Send private message Visit poster's website
  • Joined: 29 Oct 2014
  • Posts: 89
  • Location: Chicagoland, USA
Reply with quote
Post Posted: Tue Jan 06, 2015 9:59 pm
I have serious doubts that anyone would notice the bias on modulo 6, like you say. 4 and 5 would show up a whole 2.4% less than 0-3. As sverx says, you'd need literally thousands of dice rolls to bear that out. (And then the question would become "what's the impact on the game?" If it's relatively minor, then it becomes a "who cares")

If using the R register is a concern, you could just increment some value every Vsync or something along those lines, and for a game that depends on text and reading, the entropy provided by the length of reading time (in frames) and time to input would probably be sufficient. Could even make the value being incremented more than 8 bit to make the modulo bias infinitesimal.

There's the old adage: Try it and see how well it works ;) Implement some solution, see if it works well enough for the usage, and worry about improvements if you run into problems.
  View user's profile Send private message
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Tue Jan 06, 2015 11:06 pm
Okay, I implemented a new instruction on SAM, named 'r'.
It pushes a number between 0 and 32767 on the stack.

So, if, for example you want to print a number between 1 and 6, you can use:

r6\1+ "This is a dice roll: \#"!


That is, random modulus 6 plus 1, to give the 1-6 range.

Now, all that's left to do, is to implement support for that on twee2sam. ;)

BTW, I'm using this 16-bit RNG for generate the numbers, and it is also being called on the VBL interrupt, to shuffle it a bit.
  View user's profile Send private message Visit poster's website
  • Joined: 14 Oct 2006
  • Posts: 256
  • Location: NYC
Reply with quote
Post Posted: Wed Jan 07, 2015 4:39 am
This would be nice to use to backport Snatcher to the SMS.
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Wed Jan 07, 2015 8:55 am
haroldoop wrote
Okay, I implemented a new instruction on SAM, named 'r'.
It pushes a number between 0 and 32767 on the stack.

So, if, for example you want to print a number between 1 and 6, you can use:

r6\1+ "This is a dice roll: \#"!


That is, random modulus 6 plus 1, to give the 1-6 range.

Now, all that's left to do, is to implement support for that on twee2sam. ;)


I can have a look at that, if you want?
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Wed Jan 07, 2015 2:54 pm
Megatron-UK wrote
haroldoop wrote
Okay, I implemented a new instruction on SAM, named 'r'.
It pushes a number between 0 and 32767 on the stack.

So, if, for example you want to print a number between 1 and 6, you can use:

r6\1+ "This is a dice roll: \#"!


That is, random modulus 6 plus 1, to give the 1-6 range.

Now, all that's left to do, is to implement support for that on twee2sam. ;)


I can have a look at that, if you want?


No problem, it's on SAM's github repository
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Fri Jan 09, 2015 1:26 am
Hello again,

I've just updated twee2sam to support expressions of arbitrary complexity, so, for example you could write:

<<set $something to $aaa + $bbb - $ccc * $ddd >>
<<if $something + 3 < $anotherThing / 3 and $gotTheKey>>
    <<print $something % 3 + 1>>
<<endif>>


The expressions also have support for random numbers:
<<if random(1,6) + $bonus < $enemyDefense>>
Critical hit!!!
<<endif>>


The new Windows binaries are available here
  View user's profile Send private message Visit poster's website
  • Joined: 27 May 2012
  • Posts: 30
  • Location: UK
Reply with quote
Post Posted: Fri Jan 09, 2015 8:51 am
Was just about to commit my randm() patch... your update is clearly better though - I was still limiting expressions to a target, operator and two operands :-)

This is great news though, with all these new functions the scope is much larger for creating more complex adventures.

Ok, so here starts 'The Sands of Terror' in earnest now :-D
  View user's profile Send private message Visit poster's website
  • Joined: 25 Feb 2006
  • Posts: 863
  • Location: Belo Horizonte, MG, Brazil
Reply with quote
Post Posted: Fri Jan 09, 2015 2:24 pm
Megatron-UK wrote
Was just about to commit my randm() patch... your update is clearly better though - I was still limiting expressions to a target, operator and two operands :-)

This is great news though, with all these new functions the scope is much larger for creating more complex adventures.

Ok, so here starts 'The Sands of Terror' in earnest now :-D


Glad to be of service; good luck on your projects! ;)
  View user's profile Send private message Visit poster's website
Reply to topic Goto page Previous  1, 2, 3, 4  Next



Back to the top of this page

Back to SMS Power!