High-level TADS3 source organization philosophical question(s)

After having spent…however long…working on the WIP by working on nuts and bolts parser stuff, I’m back looking at the “writing the actual game” portion of writing a game.

I’m already in the habit of spinning off large chunks of code into standalone modules when that makes sense (or seems to make sense to me, anyway). So the project makefile already has a huge block of -lib lines to import all the stuff that’s been moved “out of” the main project and into its own module.

Question is: is there any hidden gotcha to doing this generally? That is, right now I’ve got a bunch of things like:

-source src/actors/alice/alice
-source src/actors/alice/aliceAgendas
-source src/actors/alice/aliceDialog
-source src/actors/bob/bob
-source src/actors/bob/bobAgendas
-source src/actors/bob/bobDialog

…and so on. Is there any disadvantage to treating blocks of related code (like NPCs) like an “internal” module, and adding (for example) a src/actors/alice/alice.tl listing all of the files in Alice’s codebase, and then replacing all the -source lines with a

-lib src/actors/alice/alice

…and so on? That is, basically break almost everything into little modules instead of treating the game as a monolithic block?

3 Likes

I don’t
Have any light for you… I’ve only done one game and my source files are large, all in the same directory. No libs of my own.

1 Like

I don’t feel like there is a huge difference, except where you want to glue different things together. I feel like libraries can’t reference anything outside of it (as a symbol) so you can’t say that Alice starts in the bar, if the bar is a different library as well, without a bit of glue code outside of the libraries that modifies Alice so that her initial location is the bar.

This might be absolutely fine. You might have glue.t which has a bunch of statements like:

modify Alice:
    location = bar

Of course this gets messier the more the libraries have to reference each other, and you may have to think about order of inclusion with all the modifications.

In a similar way you also lose some small quality-of-life things like the + notation for locations, if everything is split into submodules etc.

If I’m writing the game for just itself, at most I do a little bit of virtual namespacing with the filenames. The study content is split into study.t, studydesk.t and studybookcase.t because I don’t want to scroll past all the bookcase content if I’m working on the desk. If it got really serious, I might use subdirectory structures, but really, a TADS 3 game is a big ol’ ball of mud in the end.

2 Likes

First and foremost, a major issue in t3Make: what is sorely lacking, is a “comment to end-of-line”, whose negates a major self-documentation, commenting what source file is what

on the filenames, the first letter is always the initial of the project, and I prefer to kept everything on the TLD.

I’m organised mainly by map, having for example, a file for every floor/level/outside aerea, then the main file, whose contain all things like gameMain, intro, about, modifies, and, for small project/experimenting/messing, new verbs &c. then every NPC has his file (or files, thanks to a3Lite’s proxyActor…) and there’s the scenes (at least the major ones…) file(s)

where I diverge, at least in one WIP, is that there’s a file for the THINK topics, and an interesting file where I collect all long texts (> 20*80 characters), lightening the source files from the “walls of texts” (the mechanism is a container object, whose is currently tested in the main file:

// LONG TEXTS; tb moved later into an ilngtxt.t

lngtxt: object

// testing with our poor testing die.. WORKS !!!

dietxt = "you successfully read this poor abused die !"

all is needed is that the output is called as, for example, lngtxt.dietest, ending with sleeker location/actors &c. files, example:

die: Thing 'testing die; testing tiny; die' @etuye
"This tiny six-face (d6) die is a mere testing tool. Please remove before 
release !"
// [other testing/messing abuses mercifully snipped out...]
readDesc = lngtxt.dietxt //testing the "long text" object
;

other divergences is a notes and a scrapbook file, the former keeps, in a monolithic comment the dev notes & general story notes and the latter keep the coded, then scrapped, ideas, and some tests, for reference.

in the end, for comparision, there’s the current state of my major TADS3 WIP:

ls -l i*.t
-rw-r--r-- 1 pigi pigi  9537  9 ago 17.31 iazuj.t
-rw-r--r-- 1 pigi pigi 38329  9 ago 17.03 ietuye.t
-rw-r--r-- 1 pigi pigi 71407 11 ago 12.29 ilower.t
-rw-r--r-- 1 pigi pigi  4890 13 lug 15.19 imiyai.t
-rw-r--r-- 1 pigi pigi 12706 16 mag 22.41 inotes.t
-rw-r--r-- 1 pigi pigi  2074 13 lug 16.11 ioutside.t
-rw-r--r-- 1 pigi pigi 21349 25 lug 17.31 iscenes.t
-rw-r--r-- 1 pigi pigi 36034  8 lug 19.23 iscrpboke.t
-rw-rw-r-- 1 pigi pigi 13082  9 ago 13.29 isekai.t
-rw-r--r-- 1 pigi pigi  1734 14 ago 15.22 ithink.t
-rw-r--r-- 1 pigi pigi  9686 13 lug 13.59 itower.t
-rw-r--r-- 1 pigi pigi 69458 26 lug 12.05 iupper.t

after what I have described above, I guess that everyone should understand what is in the various filenames; incidentally this shows the usefulness of having all files starting with the same letter…

(Yes, I’m in a quescent phase of my slow coding; but I’m sure that at least Sophia will understand: there’s the newest and very young member of the meower community living with me, sleeping peacefully on this table, after a full hour of antics…)

Best regards from Italy,
dott. Piergiorgio.

3 Likes

I strongly agree with this:

For that reason alone, I would be reluctant to add a level of indirection with encapsulated gameplay libraries. I tend to reserve .tl for capability-adding libraries and extensions. Like others here, I use file naming conventions as the gameplay file organization scheme. Code organization wise, I am eerily similar to

The gotcha that @BrettW mentioned, while not fatal, is enough to steer me away.

2 Likes

That’s the major limitation I can think of as well. In my case this isn’t that much of an inconvenience as there’s already a bunch of object/actor placement code, because there are various time shifts and other events in the game that shuffle things around. And instead of handling initial placement one way and subsequent placements another way, I’m just treating initialization of everything at the start of the game the same way I’m treating each subsequent “re-initialization” following a major game event.

I was more worried about hidden “gotchas”, like a hard limit on the number of external libraries that can be loaded, or some difference in how the interpreter handles/stores objects that would make it undesirable to use too many modules, or that kind of thing.

This works:

# 
# This is a commented makefile
#
# These are preprocessor flags
-D LANGUAGE=en_us
-D MESSAGESTYLE=neu
# 
# Symbol/object directories
-Fy obj -Fo obj
-o game.t3
#
# Load the standard libraries
-lib system
-lib adv3/adv3
#
# The actual game source
-source sample
2 Likes

I might have misunderstood and missed it if this was already answered here, but is there a way to have a directory of reusable libraries, and link them into the current project with the makefile? Any time I start a new project, I’d like to quickly add a custom foundation library I have, but I’m a bit tired of using symlinks or just straight-up copy-pasting.

2 Likes

If I use libraries as is, they remain in …wrk/tads3/contrib, but if is modified (not in the TADS sense) for a WIP, are copied (and modified) in …WIP/ext

In TADS3 this is what I consider an appropriate use of .t3m files :wink:

Best regards from Italy,
dott. Piergiorgio.

1 Like

Oh okay, so we don’t need to use a path defined by t3make? We can define arbitrary paths in the makefile?

yes.

From the adv3 version of a WIP:

-source ext/scenes.t

(scenes.t is a contrib library by Eric Eve; I suspect that was the starting point of a3Lite’s scene.t) whose was slightly modified

-source extensions/custmsg.t

it’s an adv3 extension, so in this case, because there is

-lib ../adv3/adv3

above, t3Make understand that the extension dir is in the adv3 dir tree

lastly, if I don’t have haved modded scenes.t, I have done this:

-source ../contrib/scenes.t

as you see, T3make indeed does a good work as a makefile(1)-like.

HTH, and
Best regards from Italy,
dott. Piergiorgio.

1 Like

You can also use -Fs [path] to add a directory to the source search path, and -I [path] to do add a directory to the include search path.

1 Like

Ohhh so for *.t vs *.h?

Kinda. If you use -Fs it’ll find both *.t (T3 source) and *.tl (T3 library) files.

So for example I’m putting together a little module that implements simple random maps (n x n square of rooms with random connections) for testing purposes. A map:

/home/tads/git/simpleRandomMap/
        ./demo/
                ./games/
                makefile.t3m
                ./obj/
                ./src/
                        sample.t
        ./doc/
        LICENSE.txt
        simpleRandomMap.h
        simpleRandomMap.t
        simpleRandomMap.tl

So the main module code is in /home/tads/git/simpleRandomMap/
and the test cases (in this case only one) live in ./demo/, with the makefile being ./demo/makefile.t3m and the source for the test case in ./demo/src/sample.t.

The expectation is that t3make will be run from the ./demo/ directory.

The basic makefile looks like:

-D LANGUAGE=en_us
-D MESSAGESTYLE=neu
-Fy obj -Fo obj
-o games/game.t3
-lib system
-lib adv3/adv3
-lib ../simpleRandomMap
-source src/sample.t

Going more or less line-by-line:

-D LANGUAGE=en_us
-D MESSAGESTYLE=neu

These are standard preprocessor flags for language settings.

-Fy obj -Fo obj

Tells the complier to output object (-Fo) and symbol (-Fy) files to the obj directory. Since we’re going to be running t3make from /home/tads/git/simpleRandomMap/demo/, that means symbol and object files will end up in /home/tads/git/simpleRandomMap/demo/obj/.

-o games/game.t3

This tells t3make to write the compiled story file to the ./games/ subdirectory. Which, again assuming we’re compiling from /home/tads/git/simpleRandomMap/demo/, means the story file will end up as /home/tads/git/simpleRandomMap/demo/games/game.t3.

-lib system
-lib adv3/adv3

Tells the compiler to load the system.tl and adv3/adv3.tl from somewhere in the search path. For my FrobTADS install, that’s /usr/local/share/frobtads/tads3/, so the loaded versions will be /usr/local/share/frobtads/tads3/lib/system.tl and /usr/local/share/frobtads/tads3/lib/adv3/adv3.tl. Note that you can specify a different system library path via -FL [path] at compile time, if you want/need to.

-lib ../simpleRandomMap

This tells the compiler to look for simpleRandomMap.tl in ... And because we’re compiling from /home/tads/git/simpleRandomMap/demo/, it’ll look in /home/tads/git/simpleRandomMap/demo/../simpleRandomMap.tl, or (equivalently) /home/tads/git/simpleRandomMap/simpleRandomMap.tl.

-source src/sample.t

Finally, we look for the source we’re compiling in ./src/sample.t. Compiling from /home/tads/git/simpleRandomMap/demo/ this will be /home/tads/git/simpleRandomMap/demo/src/sample.t.

Right. Now let’s say we want to include an external module. I frequently add my debugTool module to projects I’m working on to help with debugging. Following the same standard layout, the debugTool module lives in /home/tads/git/debugTool/.

So to update the simpleRandomMap makefile discussed above to include this module, we can just add

-lib ../../debugTool/debugTool

Compiling from /home/tads/git/simpleRandomMap/demo/ this will look for debugTool.tl in /home/tads/git/simpleRandomMap/demo/../../debugTool/debugTool.tl, which is to say /home/tads/git/debugTool/debugTool.tl, which is what we need.

Alternately we could add:

-Fs ../../

…to tell the compiler to add ../../to the search path. If we’re compiling from /home/tads/git/simpleRandomMap/demo/ that’s going to work out to be /home/tads/git/. So then we can just add:

-lib debugTool/debugTool

Note that the order of options matters, and you have to put all the “F” options (like -Fs) before any -lib or -source options. So the final makefile could look something like:

#
# simpleRandomMap.t3m
#
# Preprocessor flags
-D LANGUAGE=en_us
-D MESSAGESTYLE=neu
#
# Path options
-Fy obj -Fo obj
-Fs ../../
#
# Story file location
-o games/game.t3
#
# System libraries
-lib system
-lib adv3/adv3
#
# Contrib libraries
-lib debugTool/debugTool
#
# This module
-lib ../simpleRandomMap
#
# The demo code
-source src/sample.t
4 Likes

Ah, excellent! Thank you so much! :grin:

Excellent explanation. copied in toto here in ~/if/doc/eng/tads3/notes/

whose raise another philosophy of organisation: people will note that I use eng(ine) instead of lng (language) for the dir name, this is because often language and 'terp source (and docs) are tarballed/zipped together (frobTADS, whose you obviously use, as myself, is a case in point…) “engine” as combination of compiler+'terp is more consistent re. the archive file’s content.

of course, after successfull compilation & testing, the first thing first (cit.) is cp -a all documentation in if/doc/eng, the example source in ~/if/src/[engine name/exa, the standard libraries in ~/if/lib/[engine name]/[version] (master copy) and ~/if/work/[engine name]/[library] (working copy), after mv the earlier version in ~/if/wrk/oldlib/[library].[version]

sorry for the long OT and thanks for the useful doc contrib file, on par with the two periplus !

Best regards from Italy,
dott. Piergiorgio.

1 Like