• Skip to main content
  • Skip to header right navigation
  • Skip to site footer
Retro Game Coders

Retro Game Coders

Retro computer/console game + dev community

  • Home
  • About
  • Blog
  • Retro Resources
    • Retro Gaming Timeline
    • Browser C64 Emulator
    • Best Retro YouTube Channels
    • New Retro Books
    • Raspberry Pi Amiga Emulation
    • MiSTer FPGA Tutorial
    • BMC64 C64 Pi
  • Contact
Home » Programming

Low-Level and Assembly Programming in Turbo Rascal (TRSE)

getting-started-with-trse

Low-Level / Assembly in Turbo Rascal: TRSE Programming Tutorial Part 4

Turbo Rascal uses a Pascal-like syntax that allows you to develop programs that target a whole bunch of retro machines. That Pascal is compiled down using the built-in Orgasm, or third party assemblers.

But did you know you can use TRSE as a high-level macro assembler?

Missed earlier Turb0 Rascal posts?

Part 1 – Getting started with TRSE

Part 2 – The Structure of Turbo Rascal programs

Part 3 – Hardware Programming with TRSE

Programming Low-Level So I Can Code High-Level

Even though TRSE can do amazing things on individual machines, as the brilliant demos from Nicolaas prove, my goal is usually to do as much cross-platform as possible even if my end-project only appears on one machine (Eg. PETFrog).

This is because the more switching around I do, the less “in the zone” I become, and the less productive I will be. It is, for example, why I do so much day to day with Python and C++, even when there might be a “better” tool.

To further my goal of doing cross-platform work on TRSE, I set about developing a cross-platform “Text Mode” input and output unit (library, basically), and that meant digging deep into each of my chosen target platforms, starting with 6502.

Using the Text Mode Unit

Most of my future projects will involve this unit but we need to discuss how to use it here just so you have context for how things happen under the hood.

program hello_world;
@projectsettings "system" "C64"
@use "text/txt"

To start with, after naming your program in your .RAS file, you can specify in the code which target system you will be compiling for right now. You can also change this in Project Settings, but I prefer doing this on a per-file level and keeping the project agnostic.

Then we tell TRSE we are “using” the text unit.

After this we need to clear the screen. Right now I have packed a bit more than just clearing the screen into the cls() function so it is more a “screen initialize” than just clear screen.

We can then do things like turn off the flashing cursor and print ‘hello world’:

	txt::cls();
	txt::cursor_off();
	txt::move_to(2,20);
	txt::print_string("HELLO WORLD!", True);		

Easy enough.

The True in the print is that , yes, we want to output a carriage return to start a new line after the print.

Under the Hood

The challenge of making all this work across different machines is each achieve the same things (eg. text on screen) in very different ways.

On Commodore machines, if you know where screen memory is located, you can simply put bytes into that memory area.

The Apple II has screen memory, but to save chip count (but not make the machine less expensive for the buyer), Steve “Woz” Wozniak made a bizarre, possibly drug-fueled, memory map.

The BBC Micro has Mode 7 which is a nice and handy low-memory using “Teletext” mode, but that is missing on it’s sibling the cost-reduced Acorn Electron, which doesn’t have a text mode but a text-written-to-bitmap mode.

Annnnd … Yes, there might also be some translation from ASCII into whatever passes as screen character codes too.

So what looked easy is bloody hard. How do I achieve this?

Tapping the Operating System

Fortunately each machine has routines built in to ROM to do what we need.

Where they live, and how they work, will be different for each, but they all have to have ways to, say, output text for them to be usable.

On the Atari 800 therefore, to print a single text character on screen at the current cursor location, we call the operating system routine that lives at $F6A4.

In TRSE we have helpful variables mapped to the main registers, and before calling the print we need to put the chosen character into the _A register, so for Atari 800 the function becomes:

	// Put a character at current cursor location
	procedure put_ch(CH:byte);
	begin
		_A:=CH;
		call(^$F6A4);
	end;

But on Apple II it looks like:

	// Put a character at current cursor location
	procedure put_ch(CH:byte);
	begin
		togglebit(CH,7,1);
		_A:=CH;
		call(^$FDED);
	end;

If we don’t toggle the high bit on Apple II the text outputs, but reversed black on white or even stranger things happen!

TRSE also has PEEK and POKE for writing individual bytes of memory, just like in BASIC.

For example, to see which screen mode we are currently using on BBC we can do peek(^$355,0).

Unlike BASIC we can shove around more than one byte at a time:

We can set up an array (array[1000] of byte) to represent the screen data we wish to push to screen and then simply use MemCpy or CopyFullScreen

		petscii_pointer:=#petscii2;
		CopyFullScreen(petscii_pointer, #$8000);

What About Assembler Programming in TRSE?

What about assembly?

Here is how we (Colin actually) output a string on Amstrad in Z80 Assembler using TRSE:

// Prints the string pointed to by p at the current cursor position.
// Control chars are obeyed, not printed.
procedure Print(p: global pointer);
begin
    asm("
      ld hl, [Text_p]
    text_print_loop:
      ld a, (hl)
      cp 0
      jr z, text_print_finished
      call #bb5a
      inc hl
      jp text_print_loop
    text_print_finished:
    ");
end;

As you can see, the Turbo Rascal function is a wrapper around inline assembler, much the same as you might do in C.

So how does this knowledge impact the game we are building?

Cross-Platform to Custom Per Machine

Just because the core of the game is cross-platform does not mean we need to neglect the abilities of each target machine.

For example, while the Commodore PET has monochrome character-only output, VGA-equipped PCs have 256 colour bitmap output which would be a waste to ignore.

Other machines, such as Vic 20 and C64, allow us to customize the characters to give a graphic feel but still using text functions.

If you saw my C64 BASIC tutorials then you will know what is coming next!

	// Set to use the new characterset
	CopyCharsetFromRom(^$3000);
	SetCharsetLocation(^$3000); 
	// Destination address
	dest:=$3000;
	temp_s:=#alpha;
	MemCpy(temp_s, 0, dest,27*8);

This loads the default characters for us to then customize, and we copy 8-bytes per character from an Array of the alphabetical font + @ symbol. This seems to happen instantly, unlike when we did the same thing in BASIC!

Making Maps

TRSE Map Editor
TRSE Map Editor

TRSE does have a level editor but for now let’s use a simple Array to keep things easy.

All we need is an Array 20×20 big. Unfortunately at the time of writing, TRSE doesn’t have two-dimensional arrays, but we can build our map as a string, and the math to turn 2D into a straight list of bytes is easy!

map: string = ("                                                 ==========          =        =          #      < #          =        =          =        =          =        =          =     =  =          =     =  =          = >   =  =          ========#=                                                                                                                                                                ");
// Location of screen memory
	screen:=$400;
// Pointer to our map array/string
	temp_s:=#map;
// Loop through each 20-byte row of data
	for y:=0 to 20 do
	begin
		MemCpyFast(temp_s, 0, screen,20);
		screen:=screen+40;
		temp_s:=temp_s+20;
	end;

Yay! Now we have our little adventurous dude in a semi-recognizable dungeon locale we can start to add game features.

But now we are deviating from code-once-compile-many, how do we avoid having to re-write large parts of our program?

We will look at that next time as we build out our game!

If you like it, share it! (Please - because it really helps)

  • Tweet
Category: ProgrammingTag: 6502 assembler, 68000 assembler, assembly, TRSE (Turbo Rascal) Programming, z80 assembler
Previous Post: « getting-started-with-trse TRSE + 8-Bit Guy SNES Controller Adapter:Turbo Rascal Hardware Programming
Next Post: C64 DOS Commands – Commodore BASIC Programming Part 5 »

Retro Game Coders

Retro computer/console game + dev community by Chris Garrett

  • Facebook
  • Twitter
  • Instagram
  • YouTube

Maker Hacks ・ Geeky Game Master

© Copyright 2021 Chris Garrett

Privacy ﹒ Terms of Service

Return to top