Jump to content
IGNORED

Developing LUDO game for TI-99/4a in C


xahmol

Recommended Posts

(Started a new thread from the GCC Topic)

 

Finished a first working and complete version of the game. See the ZIP attached, unzip contents to DSK1 location of emulator or TIPI.

 

Game is menu driven and controlled by either joystick or cursor keys. Still contemplating if on TI target the cursor keys are handy, as they on TI require two keys pressed (while my other targets have dedicated cursor keys).

 

Gladly hear your feedback and suggestions. Not much room for additions though, as I already encountered before removing some test code that I completely fill the allocated expanded upper memory space. So adding stuff will also cause need to remove or reduce stuff elsewhere.

 

Tested over here in Classic99 and on my original TI-99/4a hardware with sidecar TIPI.

 

Full source code:

https://github.com/xahmol/ludo/tree/main/TI994a
(see upwards tree for the Oric Atmos code and the original Commodore 128 BASIC listing from 1992 that I programmed in my youth and that is the base)

 

ZIP file of latest build:

LudoTI994a-v199-20210412-0946.zip

 

Changelog:

v199-20210412-0946:

  • Optimised window drawing and screen clearing routines with hchar and vchar commands
     

v199-20210411-1322:

  • Optimised window drawing and screen clearing routines with faster native TI methods.

 

v199-20210410-1435:

  • Added speech to support the TI Speech Synthesizer (or the emulation of it using an Emulator)
  • Added boot tracking with fallback to DSK1. Should allow to place binaries everywhere you want on TIPI as long as you have AUTO switched to ON (Automatic mapping of last accessed dir to DSK1). Also supports DSK2 and DSK3 if needed.
  • Small bugfixes
     

- v199-20210326-1856:

  • Changed file operations location to .DSK1 (was .TIPI) to let it work on more machines.

 

Schermafbeelding 2021-03-26 163009.png

Edited by xahmol
Updated build v199-20210412-0946
  • Like 9
Link to comment
Share on other sites

10 minutes ago, Asmusr said:

but which device is it loaded from?

It is written for TIPI. So in Classic99 it needs a drive mapping for DSK0 in which the files are present.

Other emulators: no clue. Files are loaded and saves using TIPI.<file name>.

Welcome for suggestions to make it working more broadly.

 

Noted at least for my to do list:

- clear the music memory first so no random notes are played if no file is found.

- either add TIPI detection, or make a version using DSK1 as seperate target.

- Maybe add also a cartridge version without music and save games.

Edited by xahmol
Link to comment
Share on other sites

There are at least four options for making it work more broadly, including on a real TI with PEB and floppy drive:

  1. Include the music in the main file so no loading is required.
  2. Ask user for the device name.
  3. Use a method called boot tracking* to detect the last used device.
  4. Use DSK1, which is the most common device. That will also work with Classic99 and TIPI.

I would usually go for option 1, but since you need to save games maybe option 4 is better? I have used option 3 before, but the method is not really a standard even though most devices DSRLNKs support it. Option 2 is used by many games, but I have always found it a bit silly to be asked where I loaded a file from.

 

* Boot tracking is utilizing that DSRLNK (I think it's DSRLNK and not the DSR) saves the CRU base in >83D0 and the ROM address in >83D2. Using that information you can read the device name from the DSR ROM.

Edited by Asmusr
Added details about boot tracking
  • Like 1
Link to comment
Share on other sites

Well, including the music in the main file is not an option as I enabled shuffling through 3 tracks and they won’t fit all. Struggled with space already. And as you said, will not work for save games anyway.

DSK1 is certainly possible, probably will go for that. Should be a quick fix just replacing TIPI. by DSK1. I guess.
 

But boot tracking also sounds nice if I can fit the code for it in (noted that I could literally not add another line of code).

Edited by xahmol
Reaction on boot strapping
Link to comment
Share on other sites

1 hour ago, xahmol said:

But boot tracking also sounds nice if I can fit the code for it in (noted that I could literally not add another line of code).

*********************************************************************
*
* Boot tracking
*
* Code copied more or less verbatim from:
* 1.7. The Art Of Assembly Part 7. Why A Duck?
* By Bruce Harrison 1991
*
* THE SECTION HERE AT LABEL OPEN PERFORMS "BOOT TRACKING"
* THAT IS, IT TELLS OUR PROGRAM WHICH DRIVE IT WAS LOADED FROM
*
BOOTTR MOV  @>83D0,R12          * GET THE CRU BASE IN R12
       MOV  @>83D2,R9           * GET THE ROM ADDRESS FOR DEVICE
       SBO  0                   * ENABLE THE ROM
       AI   R9,4                * ADDING FOUR PUTS US AT THE LENGTH BYTE
       MOVB *R9+,R4             * PLACE THAT IN R4 AND INCREMENT R9
       SRL  R4,8                * RIGHT JUSTIFY LENGTH IN R4
       LI   R10,FILEDV          * POINT TO TEXT BUFFER
MOVIT  MOVB *R9+,*R10+          * MOV ONE BYTE FROM ROM TO TEXT BUFFER
       DEC  R4                  * FINISHED?
       JNE  MOVIT               * NO, DO ANOTHER BYTE
       SBZ 0                    * DISABLE THE ROM (R4 IS ZERO AT THIS POINT)
       B   *R11                 * BRANCH TO NEXT SECTION OF CODE
FILEDV TEXT "DSK1."             * File device

This is the code I used in the game Road Hunter.

  • Like 2
Link to comment
Share on other sites

1 hour ago, Asmusr said:

*********************************************************************
*
* Boot tracking
*
* Code copied more or less verbatim from:
* 1.7. The Art Of Assembly Part 7. Why A Duck?
* By Bruce Harrison 1991
*
* THE SECTION HERE AT LABEL OPEN PERFORMS "BOOT TRACKING"
* THAT IS, IT TELLS OUR PROGRAM WHICH DRIVE IT WAS LOADED FROM
*
BOOTTR MOV  @>83D0,R12          * GET THE CRU BASE IN R12
       MOV  @>83D2,R9           * GET THE ROM ADDRESS FOR DEVICE
       SBO  0                   * ENABLE THE ROM
       AI   R9,4                * ADDING FOUR PUTS US AT THE LENGTH BYTE
       MOVB *R9+,R4             * PLACE THAT IN R4 AND INCREMENT R9
       SRL  R4,8                * RIGHT JUSTIFY LENGTH IN R4
       LI   R10,FILEDV          * POINT TO TEXT BUFFER
MOVIT  MOVB *R9+,*R10+          * MOV ONE BYTE FROM ROM TO TEXT BUFFER
       DEC  R4                  * FINISHED?
       JNE  MOVIT               * NO, DO ANOTHER BYTE
       SBZ 0                    * DISABLE THE ROM (R4 IS ZERO AT THIS POINT)
       B   *R11                 * BRANCH TO NEXT SECTION OF CODE
FILEDV TEXT "DSK1."             * File device

This is the code I used in the game Road Hunter.

Thanks for this ASMUSR.  I have more homework. :)

 

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...
On 3/26/2021 at 6:42 PM, Asmusr said:

*********************************************************************
*
* Boot tracking
*
* Code copied more or less verbatim from:
* 1.7. The Art Of Assembly Part 7. Why A Duck?
* By Bruce Harrison 1991
*
* THE SECTION HERE AT LABEL OPEN PERFORMS "BOOT TRACKING"
* THAT IS, IT TELLS OUR PROGRAM WHICH DRIVE IT WAS LOADED FROM
*
BOOTTR MOV  @>83D0,R12          * GET THE CRU BASE IN R12
       MOV  @>83D2,R9           * GET THE ROM ADDRESS FOR DEVICE
       SBO  0                   * ENABLE THE ROM
       AI   R9,4                * ADDING FOUR PUTS US AT THE LENGTH BYTE
       MOVB *R9+,R4             * PLACE THAT IN R4 AND INCREMENT R9
       SRL  R4,8                * RIGHT JUSTIFY LENGTH IN R4
       LI   R10,FILEDV          * POINT TO TEXT BUFFER
MOVIT  MOVB *R9+,*R10+          * MOV ONE BYTE FROM ROM TO TEXT BUFFER
       DEC  R4                  * FINISHED?
       JNE  MOVIT               * NO, DO ANOTHER BYTE
       SBZ 0                    * DISABLE THE ROM (R4 IS ZERO AT THIS POINT)
       B   *R11                 * BRANCH TO NEXT SECTION OF CODE
FILEDV TEXT "DSK1."             * File device

This is the code I used in the game Road Hunter.

Thanks again! I took the liberty of being inspired by this code, combining with code from TIPICFG from Jedimatt42. I made this function that converts a filename to a filename with the active DSR name in front of it (DSK1, DSK2, TIPI, etc).

Over here that seems to work now. But please feel free to comment.

 

#define CRU_ENABLE(c) __asm__("mov %0,r12\n\tsbo 0" : : "r"(c) : "r12")
#define CRU_DISABLE(c) __asm__("mov %0,r12\n\tsbz 0" : : "r"(c) : "r12")

/* PEEK POKE */
#define peek(address)		( *((unsigned char*)address) )
#define poke(address,value)	( *((unsigned char*)address)=(unsigned char)value )
#define deek(address)		( *((unsigned int*)address) )
#define doke(address,value)	( *((unsigned int*)address)=(unsigned int)value )

unsigned char* dsr_active(unsigned char* filename)
{
    /*  Get the name of the active DSR  */

    unsigned char dsrname[6] = { 0,0,0,0,'.',0 };
    unsigned char x;
    unsigned int crubase;
    unsigned int romaddress;
    unsigned char buffername[25];

    strcpy(buffername,filename);

    crubase = deek(0x83d0);
    romaddress = deek(0x83d2);
    romaddress += 5;

    CRU_ENABLE(crubase);

    for(x=0;x<4;x++)
    {
        dsrname[x] = peek(romaddress+x);
    }

    CRU_DISABLE(crubase);

    strcpy(filename,dsrname);
    strcat(filename,buffername);

    return filename;
}

I use by the way strcat() which is not standard in Libti99. As my original project was using strcat() and I was too lazy to replace that everywhere, I use this code:

unsigned char* strcat(unsigned char *dest, unsigned const char *src)
{
    /*  Append SRC on the end of DEST.
        Source: https://code.woboq.org/userspace/glibc/string/strcat.c.html */

    strcpy (dest + strlen (dest), src);
    return dest;
}

 

Edited by xahmol
  • Like 2
Link to comment
Share on other sites

Update: encountered an issue.

 

On my own TIPI, I have stored the game in a LUDO dir, to which I mapped DSK1.

 

The routine above does work in an emulator (js99er at least). Also on original hardware via TIPI and dir mapped to DSK1 if you load the program via EA or ForceCommand with DSK1.LUDO. But alas not if you load via call tipi("DSK1.LUDO"), it then registres TIPI as DSR name instead of DSK1 and the resulting filenames are like TIPI.LUDOCFG instead of DSK1.LUCOCFG or TIPI.LUDO.LUDOCFG.

 

So I guess I have to detect if TIPI is the DSR name and do something based on that instead. Just thinking now on what that something should be.

 

What I now can think of is either:

  • change from TIPI.LUDO to TIPI.LUDO.LUDO, with the disadvantage that the game absolutely has to be in the LUDO dir under root dir on the TIPI and nowhere else;
  • change from TIPI.LUDO to DSK1.LUDO, with the disadvantage that the game does not work without the proper DSK1 mapping on the TIPI.
  • implement a getcwd() (get current work directory) command if a TIPI is detected. Which would be the most elegant, but requires work as getcwd() is not implemented by Libti99 nor the other soures I studied so far. I do see that ForceCommand implements a PWD (print working directory) command, so I guess I will be looking at that source next.

But any comments/ideas on these three options, or maybe even smarter options, is appreciated.

Edited by xahmol
Link to comment
Share on other sites

29 minutes ago, Vorticon said:

Is there an AI player or is it just human players?

For each player you can choose between a computer or human player. So yes, there is 'AI'. You can even just watch the game with only AIs if you should want to.

Not a very complex AI, but it is not solely random placing as well. There is an algorithm inside to calculate the optimal pawn to choose for the AI. If you are interested, see the function computerchoosepawn() in the source at https://github.com/xahmol/ludo/blob/main/TI994a/main.c

Edited by xahmol
  • Like 2
Link to comment
Share on other sites

Hmm. Unless I am mistaken, in Force Command the PWD function is derived from the currentPath variable. This variable in turn is, as far as I can see, not read from the TIPI, but just holds the last CD (change dir) command string.

Is there no way to read current working dir on TIPI with C code?

Link to comment
Share on other sites

4 hours ago, xahmol said:

I use by the way strcat() which is not standard in Libti99. As my original project was using strcat() and I was too lazy to replace that everywhere, I use this code

 

Yeah, libti99 was not meant to be a stdio replacement, but a lot of stuff slipped in there just because I needed it. ;) Thus, that sort of support is spotty, except for conio which was a request, so is fairly complete.

 

  • Like 1
Link to comment
Share on other sites

2 hours ago, xahmol said:

The routine above does work in an emulator (js99er at least). Also on original hardware via TIPI and dir mapped to DSK1 if you load the program via EA or ForceCommand with DSK1.LUDO. But alas not if you load via call tipi("DSK1.LUDO"), it then registres TIPI as DSR name instead of DSK1 and the resulting filenames are like TIPI.LUDOCFG instead of DSK1.LUCOCFG or TIPI.LUDO.LUDOCFG.

So I guess I have to detect if TIPI is the DSR name and do something based on that instead. Just thinking now on what that something should be.

Boot tracking has always been based on artifacts and thus is rather fragile. It relies on knowledge of the DSR and/or the DSRLNK function, so when either changes, problems can arise. For the most part, DSRs are forced to implement some of those artifacts to support it.

 

TIPI has an automatic mapping function - it can be set to automap DSK1 to the last disk that loaded a PROGRAM image. Back in the day requiring DSK1 was pretty standard, so you could do that -- try the path suggested by your tracking, and if it fails, fall back on DSK1 and trust the user to have set it up.

 

https://github.com/jedimatt42/tipi/wiki/Auto-mapping

 

  • Like 3
Link to comment
Share on other sites

The 4A disk subsystem has no concept of current..  Each file request is meant to be explicit. You cannot implement a getcwd that works on any sort of standard. 

 

CALL TIPI failed because it doesn't use a DSRLNK. It lives in the same ROM as TIPI and can only access TIPI or TIPI hosted devices. So, it skips any of the code that might have enabled "boot tracking" 

 

Authoring for DSK1. is the easiest approach. Some programs prompt the user and rewrite part of themselves back to disk to save the preferred device name. Other programs going back to Adventure and Tunnels of Doom require the user to specify when they load and save. 

 

Other large storage devices offered some form of DSK1. support as well.

 

Leaving it up to the user is not much of a burden on the user until you grow beyond what one 90k floppy will hold. 

 

The 4A disk system does have another mechanism... The volume name. LVL3 io like open,read,write to record based files and LOAD or SAVE for program image files supports paths of the form:

 

DSK.<volume name>.MYFILE

 

So, you could require that the support files are on a disk named LUDO. You program would access the files as DSK.LUDO.SAV or whatever.. If someone uses a nanopeb and loads from DSK2 that's fine as long as the volume name/disk label in DSK2 is LUDO. TIPI supports this for mapped drives, including auto mapped drives. In TIPI the directory name will serve as the volume name. If you distribute as a disk image, and users drop the image on the tipi, it extracts to a directory using the images volume name. 

 

 

  • Like 2
Link to comment
Share on other sites

1 hour ago, Tursi said:

Yeah, libti99 was not meant to be a stdio replacement, but a lot of stuff slipped in there just because I needed it. ;) Thus, that sort of support is spotty, except for conio which was a request, so is fairly complete.

 

No problem, it covered almost all my needs.

Added only strcat() and strchr() from the GCC sources, next to the file support functions and wait() I got from the TIPICFG sources.

The only function I had to use a workaround for has been sprintf() that proved beyond my ability to quickly redo myself for TI.

Link to comment
Share on other sites

Oh, and the other workaround I needed is that cputc() and cputcxy() do not work for non-printable characters (values under 32).

Not sure how it is meant to work in ANSI C, but in CC65 for the targets I coded for that does work for all values between 0 and 255.

Needed that as I do use patterns with codes from 0 to 31, so used a direct vdpchar() from Libti99 instead.

Edited by xahmol
  • Like 1
Link to comment
Share on other sites

12 minutes ago, jedimatt42 said:

I would recommend falling back on DSK1 if there is anything fishy about results from boot-tracking. The failure mode is not isolated to TIPI.

Ok. Then of course the question is how to detect something fishy. Wil detecting anything other than DSKx do?

Link to comment
Share on other sites

One method I sometimes use is to set the load path to a common device, like DSK1. as suggested earlier.  If the file isn't found by boot tracking or by the default load path, I ask the user for the path/file.  This allows me (the programmer) to set a few options, relying upon user intervention only when an error condition occurs. In some cases I also save the path within a the program file that is part of the initial load sequence - though such measures may not be feasible or desirable for your game.  (Disclaimer - most of my programs are not games)

Link to comment
Share on other sites

6 minutes ago, InsaneMultitasker said:

 If the file isn't found by boot tracking or by the default load path, I ask the user for the path/file.

Would make sense, but then the question is how to detect if it does not find anything.

Noticed the C routine I use does not give an error on file not found, it just does not load anything but returns a zero error value.

I could of course add an identifier to the config file and check for that.

Link to comment
Share on other sites

Update: found this in the Force Command source that looks usable to detect if a file exists:

unsigned int existsFile(struct DeviceServiceRoutine* dsr, const char* pathname) {
  struct PAB pab;
  initPab(&pab);
  pab.pName = (char*) pathname;
  return dsr_status(dsr, &pab) != 0x0080;
}

https://github.com/jedimatt42/fcmd/blob/master/b2_dsrutil.c
 

So will try that.

  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...