Well, I didn’t get all the Seventh Sanctum features implemented last night… but I got most of them.
Masking Entries
Seventh Sanctum vocabulary files give each dictionary entry a bit mask. A selection mask is used to identify which dictionary elements are eligible, then an entry chosen.
For my implementation, I added a Mask
value to all table entries. This is a 32-bit value (though I think I’ll increase it to 64-bits). When I invoke a table, I can give it an optional bit mask to filter the table first. All masks are expressed in hexadecimal.
&SampleTable
|1 Regular entry, unmasked (implicitly 0xffffffff).
:1 001c Masked entry (0x0000001d -- 28)
&InvokingTable
|1 Unmasked &SampleTable
|1 Masked &SampleTable{:1}
When InvokingTable is called,
- If the first entry is chosen, no mask is applied. Both options of SampleTable are possible.
- If the second entry is chosen, SampleTable is called with a mask of 0x00000001. Only the first entry satisfies that (0xffffffff & 0x00000001 = 0x00000001). The second does not (0x0000001c & 0x00000001 = 0x00000000), and is excluded.
This actually was the trickiest to implement. Masking entries wasn’t that bad, I used a different line marker when I wanted to a mask. Changing the roll to be “roll:mask” (remembering that roll is implicitly dTableWeight) was more annoying. Parsing strings in C++ is more trouble than I remember. Certainly more trouble than, say, Perl, or even Python or C#.
Dynamic Tables
This was easier. I added a backup member to Dictionary, holding all the tables as originally loaded. Then I added functions to manipulate the tables member (which is used during resolution).
@resetTable{target}
resets the target table to the original form (copies frombackup
).@replaceTable{target}{source}
replaces the target table with the source table (drawn fromtables
, notbackup
).@appendTable{target}{source}
appends the source table to the target table (drawn fromtables
, notbackup
).
To exercise this, I had some fun.
!Default
&Default
|1 @iter{&e}{20}{\n}\n
&e
|1 Inside E1
|1 Inside E2
|1 Inside E3 APPEND`@appendTable{e}{f}`
&f
|1 Inside F1
|1 Inside F2
|1 Inside F3`@replaceTable{e}{g}`
&g
|1 Inside G1
|1 Inside G2
|1 Inside G3 RESET`@resetTable{e}`
This gave me output like
Inside E1
Inside E3 APPEND
Inside E2
Inside F2
Inside F1
Inside E1
Inside F3 REPLACE
Inside G2
Inside G1
Inside G1
Inside G2
Inside G3 RESET
Inside E2
Inside E1
Inside E3 APPEND
Inside E1
Inside F1
Inside F3 REPLACE
Inside G3 RESET
Inside E3 APPEND
At first I thought I wouldn’t have much use for this. I realized last night I can use it for generating divine trappings. I can concatenate the general tables with a deity’s domain-specific tables to get their random tables.
That is, given a deity with domain1, domain2, domain3, etc. (variables with domain indexes), I can have
&MakeWordTables
|1 @resetTable{ArtifactType}&AppendArtifactType{$domain1}&AppendArtifactType{$domain2}
&AppendArtifactType
|1 @appendTable{ArtifactType}{ArtifactTypeAir}
|1 @appendTable{ArtifactType}{ArtifactTypeArtifice}
And so on. This would no doubt be tedious to maintain by hand, but I can script a lot of it.
Then when I need to call the ArtifactType table, it has the generic and domain-specific artifact types.
Alternatively, if I use masks on the domain-specific tables I can simply concatenate them all and filter as needed. This actually is why I’m thinking of expanding masks to 64 bits — I have more than 32 domains. It’ll still fail when I get to 65 domains, though, so it’s not a real fix.
Internal Tables
Implemented but not relevant. Normally a table is declared using ‘&’. I’ve made it so declaring with ‘&&’ sets a ‘hidden’ flag on the table. This actually has no effect because I don’t use it.
I also made it so comments after a table declaration (i.e. all comments that aren’t before the first table) are added to the table in a Comments member.
I’ll add a ‘show usage’ flag to the program that’ll give me
- Default invocation
- All public tables, with comments.
Given
!GemsDefault
&GemsDefault
# Generates 10 random gems.
|1 \n@iter{&Gem}{10}{\n\n}\n
&Gem
# Generates a gem description formatted like
#
# A _size_ _gem type (base value _cp value_ cp)
# quality _quality_ (_qualityModifier_%)
# size _pennyweight_ pennyweight (_pennyweight*25_%)
|1 @cap{@an{&gemSize}} &gemType (base value $gemBase cp)\n\
quality\t&gemQuality ($GemQuality\%)\n\
size\t&gemWt pennyweight\t(%{$gemWt*25}\%)
&&gemSize d20
# Generates a gem size description and pennyweight (1/400 pound).
< `=gemWt{1}`tiny
> `=gemWt{25}`huge
|1 `=gemWt{1}`tiny
|5 `=gemWt{2}`small
|8 `=gemWt{4}`average
|5 `=gemWt{10}`large
|1 `=gemWt{20}`huge
&&gemType
# Generates a gem type (ultimately 'diamond', 'agate', etc.) and base value in cp.
|11 `=gemBase{400}`&gemOrnamental` [ornamental]`
|4 `=gemBase{2000}`&gemSemiprecious` [semi-precious]`
|2 `=gemBase{4000}`&gemFancy` [fancy]`
|2 `=gemBase{20000}`&gemPrecious` [precious]`
|1 `=gemBase{40000}`&gemGem` [gem]`
> `=gemBase{200000}`&gemJewel` [jewel]`
I can run
$ ./rtr gems.dict --usage
Default Table: GemsDefault
Public Tables:
GemsDefault
Generates 10 random gems
Gem
Generates a gem description formatted like
A _size_ _gem type_ (base value _cp value_ cp)
quality _quality_ (_qualityModifier_%)
size _pennyweight_ pennyweight (_pennyweight*25_%)
The other tables shown (gemSize and gemType are ‘hidden’, so won’t show up in this list. They can still be called, even from the command line, but they’re not the intended entry points.
I had intended to show the table names in alphabetical (ASCIIbetical, actually) order. It might be more useful to have the default at the top, since that’s where I’d start exploring the dictionary.
Closing Comments
I haven’t done anything with ‘categories’ yet. I see value in them, but haven’t decided how I want to implement them.
I also haven’t started using the new facilities above. I only coded them last night!
But they are here, and ready for use.