Closed eriktorbjorn closed 4 months ago
It would be cool if we could emulate the exact random number generation used by MDL. Is https://github.com/PDP-10/its/blob/master/src/mudsys/arith.94 the right place to look? If so, is it really just this?
MFUNCTION RANDOM,SUBR
ENTRY
HLRE A,AB
CAMGE A,[-4] ;At most two arguments to random to set seeds
JRST TMA
JRST RANDGO(A)
MOVE B,VAL2 ;Set second seed
MOVEM B,RLOW
MOVE A,VAL1 ;Set first seed
MOVEM A,RHI
RANDGO: PUSHJ P,CRAND
JRST FINIS
CRAND: MOVE A,RHI
MOVE B,RLOW
MOVEM A,RLOW ;Update Low seed
LSHC A,-1 ;Shift both right one bit
XORB B,RHI ;Generate output and update High seed
MOVSI A,TFIX
POPJ P,
RHI: 267762113337
RLOW: 155256071112
If so, I guess it shouldn't be that difficult. I don't speak the language, though.
Resources of PDP-10 machine language: http://www.hakmem.org/pdp-10.html http://pdp10.nocrew.org/docs/instruction-set/pdp-10.html
Should be possible the rewrite the above code in C with that help.
RHI
and RLOW
are expressed in octal and represents the high and low of a 72-bit word. This word is shifted right one position (shifting in a zero) and then RHI
are XORB
(XOR
with complement, I think) with RLOW
and stored in RHI
. RHI
is returned.
Random-generator converted to C# (desktop application with two text-boxes and a button).
private void btnRandom_Click(object sender, EventArgs e)
{
if (txtRHI.Text.Trim() == "" || txtRLOW.Text.Trim() == "")
{
txtRHI.Text =Convert.ToInt64("267762113337", 8).ToString(); // Octal-->Decimal
txtRLOW.Text = Convert.ToInt64("155256071112", 8).ToString(); // Octal-->Decimal
return;
}
long A = long.Parse(txtRHI.Text);
long B = long.Parse(txtRLOW.Text);
if (A < 0) A = A + 0x1000000000; // Convert 64 bits to 36 bits
B = (B >> 1); // Shift low right one bit
if (A % 2 != 0) B = B | 0x800000000; // Shift in bit 0 from high to low
txtRLOW.Text = A.ToString(); // Update low seed
A = A ^ B; // XOR
if (A > 0x7FFFFFFFF) A = A - 0x1000000000; // Use only 36 bits
txtRHI.Text = A.ToString(); // Update high seed / result
}
I tried adapting that to Confusion (I don't have a C# compiler at hand), but I wasn't able to reproduce the series of random numbers given as an example in https://github.com/heasm66/mdlzork/issues/48#issuecomment-1482721909
I'm not sure where I went wrong.
Made a quick hack:
MDL_INT RHI = 0267762113337;
MDL_INT RLOW = 0155256071112;
mdl_value_t *mdl_builtin_eval_random(mdl_value_t *form, mdl_value_t *args)
/* SUBR */
{
mdl_value_t *cursor;
mdl_value_t *seed1, *seed2;
MDL_INT rvalue;
unsigned short rseed[3];
OARGSETUP(args, cursor);
OGETNEXTARG(seed1, cursor);
OGETNEXTARG(seed2, cursor);
if (cursor) mdl_error("Too many args to RANDOM");
if (seed1 && seed1->type != MDL_TYPE_FIX)
mdl_error("RANDOM seeds must be type FIX");
if (seed2 && seed2->type != MDL_TYPE_FIX)
mdl_error("RANDOM seeds must be type FIX");
if (seed1 && seed2)
{
#ifdef MDL32
uint64_t tot_seed = ((uint64_t)seed1->v.w << 16) ^ ((uint64_t)seed2->v.w);
rseed[0] = tot_seed >> 32;
rseed[1] = tot_seed >> 16;
rseed[2] = tot_seed;
#else
// treat the seed as two 36-bit numbers; XOR together for 48 bits
uint64_t tot_seed = ((MDL_UINT)seed1->v.w << 12) ^ ((MDL_UINT)seed2->v.w);
rseed[0] = tot_seed >> 32;
rseed[1] = tot_seed >> 16;
rseed[2] = tot_seed;
#endif
seed48(rseed);
RHI = (MDL_INT)seed1->v.w;
RLOW = (MDL_INT)seed2->v.w;
}
else if (seed1)
{
#ifdef MDL32
rseed[0] = seed1->v.w >> 16;
rseed[1] = seed1->v.w;
rseed[2] = 1;
#else
// 36-bit seed
rseed[0] = seed1->v.w >> 20;
rseed[1] = seed1->v.w >> 4;
rseed[2] = seed1->v.w & 0xF;
#endif
seed48(rseed);
RHI = (MDL_INT)seed1->v.w;
}
#ifdef MDL32
rvalue = mrand48();
#else
rvalue = (MDL_INT)(((MDL_UINT)mrand48() << 32) ^ ((MDL_UINT)mrand48()));
MDL_INT A = RHI;
MDL_INT B = RLOW;
if (A < 0) A = A + 0x1000000000;
B = B >> 1;
if (A % 2 != 0) B = B | 0x800000000;
RLOW = A;
A = A ^ B;
if (A > 0x7ffffffff) A = A - 0x1000000000;
RHI = A;
rvalue = A;
#endif
return mdl_new_fix(rvalue);
}
This test-function produces the same series on both Confusion and ITS/MDL:
<DEFINE TEST ()
<PRIN1 <RANDOM 359 569>>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
<PRIN1 <RANDOM>> <CRLF>
>
TEST
<TEST>
-34359738245200
17179869429
-17179869039
25769804011
-34359738205
12884902102
30064771207
-6442450708
-12884901713
9663676633
T
I think one mistake - possibly one of several - I made was accidentally using an unsigned type for the seeds.
Anyway, if this matches ITS/MDL, I think that would be a great thing to have for the non-MDL32 case. (And come to think of it, since this is a fork, do we even need the MDL32 case if it won't run Zork?) All we need is to add a verb to set the random seed (Infocom called this #RANDOM), and then we could have at least semi-automated regression testing. It might be useful to also have at least the #RECORD / #UNRECORD commands to save a lot of the player's actions.
I don't know about #COMMAND since it's It's already possible to pipe a text file into Confusion. Though at least when I tried it it would complain about an unexpected EOF at the end.
Whether or not it's useful is, of course, still open to debate.
diff --git a/mdlzork_810722/patched_confusion/dung.mud b/mdlzork_810722/patched_confusion/dung.mud
index 9498983..eac0e14 100644
--- a/mdlzork_810722/patched_confusion/dung.mud
+++ b/mdlzork_810722/patched_confusion/dung.mud
@@ -3744,6 +3744,11 @@ L;"funny verbs"
<SADD-ACTION "GO-IN" TIME> ;"funny verb for room actions when entering"
+;"Debug commands"
+
+<SADD-ACTION "#RECO" DO-SCRIPT>
+<SADD-ACTION "#UNRE" DO-UNSCRIPT>
+
;"ZORK game commands"
<SADD-ACTION "BRIEF" BRIEF>
diff --git a/mdlzork_810722/patched_confusion/rooms.mud b/mdlzork_810722/patched_confusion/rooms.mud
index 2df516b..03f1176 100644
--- a/mdlzork_810722/patched_confusion/rooms.mud
+++ b/mdlzork_810722/patched_confusion/rooms.mud
@@ -402,38 +402,48 @@
<FINISH <>>>
<FINISH <>>>>
-<DEFINE DO-SCRIPT ("AUX" (CH <>) (UNM ,XUNM) (MUDDLE ,MUDDLE))
- #DECL ((CH) <OR CHANNEL FALSE> (UNM) STRING (MUDDLE) FIX)
+<DEFINE DO-SCRIPT ("AUX" (CH <>) (UNM ,XUNM) (MUDDLE ,MUDDLE) (ACT "script")
+ (CACT "Script") (EXT "SCRIPT"))
+ #DECL ((CH) <OR CHANNEL FALSE> (UNM ACT CACT EXT) STRING (MUDDLE) FIX)
+ <COND (<VERB? "#RECO">
+ <SET ACT "record">
+ <SET CACT "Record">
+ <SET EXT "RECORD">)>
<COND (,MY-SCRIPT
<DO-UNSCRIPT <>>)>
<COND (,SCRIPT-CHANNEL
- <TELL "You are already scripting.">)
+ <TELL "You are already " ,POST-CRLF .ACT "ing.">)
(<AND
<OR <G? .MUDDLE 100>
<AND <SET CH <OPEN "READ" ".FILE." "(DIR)" "DSK" .UNM>>
<=? <10 .CH> .UNM>
<CLOSE .CH>>>
- <SET CH <OPEN "PRINT" "ZORK" "SCRIPT" "DSK" .UNM>>>
+ <SET CH <OPEN "PRINT" "ZORK" .EXT "DSK" .UNM>>>
<PUT <TOP ,INCHAN> 1 (.CH)>
- <PUT <TOP ,OUTCHAN> 1 (.CH)>
+ <COND (<VERB? "SCRIP">
+ <PUT <TOP ,OUTCHAN> 1 (.CH)>)>
<SETG SCRIPT-CHANNEL .CH>
<COND (<L? ,MUDDLE 100>
- <TELL "Scripting to " ,POST-CRLF ,XUNM ";ZORK SCRIPT">)
+ <TELL .CACT 0 "ing to " ,XUNM>
+ <TELL ";ZORK " ,POST-CRLF .EXT>)
(T
- <TELL "Scripting to <" ,POST-CRLF ,XUNM ">ZORK.SCRIPT">)>)
+ <TELL .CACT 0 "ing to <" ,XUNM>
+ <TELL ">ZORK." ,POST-CRLF .EXT>)>)
(T
<COND (.CH <CLOSE .CH>)>
- <TELL "I can't open the script channel.">)>>
+ <TELL "I can't open the " ,POST-CRLF .ACT " channel.">)>>
-<DEFINE DO-UNSCRIPT ("OPTIONAL" (VERBOSE T))
- #DECL ((VERBOSE) <OR ATOM FALSE>)
+<DEFINE DO-UNSCRIPT ("OPTIONAL" (VERBOSE T) "AUX" (CACT "Script"))
+ #DECL ((VERBOSE) <OR ATOM FALSE> (CACT) STRING)
+ <COND (<VERB? "#UNRE">
+ <SET CACT "Record">)>
<COND (,SCRIPT-CHANNEL
<PUT <TOP ,INCHAN> 1 ()>
<PUT <TOP ,OUTCHAN> 1 ()>
<CLOSE ,SCRIPT-CHANNEL>
<SETG SCRIPT-CHANNEL <>>
- <AND .VERBOSE <TELL "Scripting off.">>)
- (<AND .VERBOSE <TELL "Scripting wasn't on.">>)>>
+ <AND .VERBOSE <TELL .CACT ,POST-CRLF "ing off.">>)
+ (<AND .VERBOSE <TELL .CACT ,POST-CRLF "ing wasn't on.">>)>>
<GDECL (THEN) FIX>
I've split out #RECORD/#UNRECORD to new issue, #60.
XUNM
are both used as a seed for RANDOM
, and for directory (needs to already exist) name where save- and scripting-files are saved.
It would be nice to allow the user to specify this (and the full name from
As discussed in https://github.com/heasm66/mdlzork/issues/48
When the Dungeon Master quizzes you, the questions aren't selected quite as randomly as you may first think. The RNG is re-seeded based on your user name and (optionally) the incantation you may have used to skip straight to the endgame. I'm guessing the reasoning was that even with a large number of players, they each get their own customized quiz, making it harder to share clues with others.
With Confusion, the user name is hard-coded:
So every player who plays from the start gets the same three questions (to the extent that they all use the same RNG implementation, at least), and the only way to get other ones is to use an incantation.
While this does match the original behavior for the individual player, should Confusion provide a way to change this? I don't know. What I'll probably end up doing myself, just to check that the other questions work, is to figure out a set of incantations. But I don't know if the RNG is predictable enough for those to then work on other systems. I kind of doubt it.