How the game text in Revs is tokenised to save space
Memory is tight in the unexpanded BBC Micro, and Revs has a number of tricks up its sleeve to tackle this. One of the more interesting is the text token system, which takes all the text used in the game, and compresses it.
The compression system is relatively simple. Revs contains 55 text tokens, that are numbered from 0 to 54. Each of these tokens (apart from token 54, which is a special case) has an associated string of text, labelled token0 through token53 in the source code. This string contains not only the text for that token, but it can contain other tokens too, as well as arbitrary numbers of spaces. Given that most of the strings are printed in mode 7 and can contain teletext control codes, this system enables us to create some pretty sophisticated tokens with very few bytes.
Let's take a deeper look at how text tokenisation is implemented in Revs.
Text token strings
------------------
Each token's string is a sequence of bytes. They have the following meanings:
0-159 | Print character n (where n is ASCII or a teletext control code) |
160-199 | Print n - 160 spaces (i.e. 0 to 39 spaces) |
200-254 | Print token n - 200 (i.e. token 0 to 54) |
255 | End of token |
Let's look at a typical token definition to see how this works:
.token30 EQUB 31, 5, 24 \ Move text cursor to column 5, row 24 EQUB 134 \ Set foreground colour to cyan alphanumeric EQUB 200 + 17 \ Print token 17 ("PRESS ") EQUS "SPACE BAR " \ Print "SPACE BAR " EQUS "TO CONTINUE" \ Print "TO CONTINUE" EQUB 255 \ End token
We can print this token by calling the PrintToken routine with X = 30. It is designed to be printed in screen mode 7, and is part of the menu system shown before the race starts. The first two parts write the standard VDU commands to move the text cursor to column 5, row 24, and then to set the foreground colour to cyan (see the BBC User Guide for details of how the mode 7 teletext control codes work).
Next up we have a byte in the token string that has a value in the range 200 to 254, which indicates we should print another token. In this case it's token 17, which just contains the string "PRESS ", but as all tokens can contain other tokens, it's possible to create tokens with pretty sophisticated recursive content (token 28, for example, clears the screen and prints an entire menu, along with header and prompt, all using embedded tokens).
Next, we have the text "SPACE BAR " and "TO CONTINUE", and finally we have a value of 255, which denotes the end of the token. So in all, token 30 prints "PRESS SPACE BAR TO CONTINUE" in cyan at column 5 on row 24.
Tokens 0 to 53 all work in this way, but token 54 doesn't have an associated text string. Instead, when printing token 54, the PrintToken routine calls the PrintHeader routine with X = 0, which prints out token 0 ("FORMULA 3 CHAMPIONSHIP") as a double-height header.
Note that not all tokens are designed for screen mode 7. Tokens 40-45, 48 and 53 are designed to be printed along the top of the driving screen, and are laid out at the exact width of the screen (though token 44 is wider, for some reason). However, as most of the game text is shown in mode 7 before or after the race, most of the tokens are built with teletext control codes in mind.
Text routines
-------------
Here's a list of all the related routines:
- PrintToken prints the text token given in X. It looks up the address of the relevant token string in the (tokenHi tokenLo) table, and prints the token one byte at a time, either printing text with PrintCharacter (for bytes 0-159), printing spaces with PrintSpaces (for bytes 160-199), or recursively printing any embedded text tokens with PrintToken (for bytes 200-253) or PrintHeader (for byte 254).
- PrintHeader prints a token as a double-height header, with the position and colours given in the header tables, and a specific number of spaces between the top and bottom parts of the double-height text (to ensure they line up). Seven tokens are supported (tokens 0 to 6). See the next section on configurable tokens for details of how this works.
- PrintHeaderChecks prints a chequered line above and below the mode 7 header. This is only called once, at the start of the main game loop in MainLoop, just after PrintHeader is called with X = 4 to print "REVS" four times in multicoloured letters. The call to PrintHeaderChecks decorates the multicoloured REVS banner with two lines of checks.
- PrintSpaces prints the specified number of spaces.
- PrintCharacter either prints a character using the normal OSWRCH routine (when we are in mode 7), or pokes the character bitmap directly into screen memory (when we are in the custom screen mode).
Let's take a quick look now at the PrintHeader routine, before moving on to the tokens themselves.
Configurable text tokens
------------------------
Text tokens 31, 33 and 34 are special, in that they are configurable. They are configured by poking values directly into the strings at token31, token33 and token34.
The main example of this is PrintHeader, which looks up values from the following tables, depending on which header is being printed:
These determine the position, layout and colours of the double-height header that is printed, and the routine works by poking these values directly into tokens 33 and 34, and then printing token 33 to display the final result (which itself prints token 34, as token 33 contains token 34, twice).
Token 31 is also configured with colour information before it is printed, this time by direct poking into token31.
All the text tokens in Revs
---------------------------
The table below lists all the text tokens in Revs. The following syntax is used to denote each token's content:
- (#) = change to the specified foreground colour, e.g. (cyan) = change to cyan text
- (#/#) = change to the specified foreground/background colour, e.g. (yellow/red) = change to yellow on red
- (/#) = change to the specified background colour, e.g. (/black) = change to a black background
- [#] = print the specified embedded token, e.g. [54] = print token 54
- (#, #) = move the cursor to specified screen location, e.g. (2, 10) = move to character column 2 on character row 10
- ">" denotes the teletext right-arrow character
- Tokens denoted as "race text" are only shown when driving
- Tokens denoted as "menu" tokens (e.g. Menu [35-37]) denote menus that are only shown in mode 7, with the numbers denoting which tokens are used to show the menu options
The following syntax is used for tokens whose contents are set by modifying the token definitions before PrintToken is called:
- {#/#} = configurable foreground/background colours
- {#} = configurable embedded token
- {# spaces} = configurable number of spaces
Click on the link in the first column to see the token definition in the source code.
# | Summary | Contents |
---|---|---|
0 | Text | "FORMULA 3 CHAMPIONSHIP" |
1 | Text | " POINTS " |
2 | Text | "GRID POSITIONS" |
3 | Text | "ACCUMULATED POINTS" |
4 | Text | "REVS REVS REVS " Each "REVS" is magenta/yellow/cyan/green [52] |
5 | Text | "THE PITS" |
6 | Text | " BEST LAP TIMES " |
7 | Text | "Novice" |
8 | Text | "Amateur" |
9 | Text | "Professional" |
10 | Text | "SELECT " |
11 | Text | "ENTER " |
12 | Text | " DRIVER" |
13 | Text | " mins" |
14 | Text | " laps" |
15 | Text | " RACE" |
16 | Text | " > " |
17 | Text | "PRESS " |
18 | Text | " 5" |
19 | Text | "10" |
20 | Text | "20" |
21 | F3 class menu | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] Menu [36-38]: (flashing cyan) "PRESS " "1 Novice" "2 Amateur" "3 Professional" (2, 10) " SELECT THE CLASS OF RACE" |
22 | F3 duration menu | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] Menu [36-38]: (flashing cyan) "PRESS " "1 5 mins" "2 10 mins" "3 20 mins" (2, 10) (cyan) "SELECT DURATION OF QUALIFYING LAPS" |
23 | F3 driver name | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] (2, 10) (cyan) " ENTER NAME OF DRIVER" (12, 17) (yellow) "------------" (9, 16) (magenta) " > " |
24 | Wing settings 1 | (2, 10) cyan "SELECT WING SETTINGS > range 0 to 40" (14, 16) "rear " (magenta) " > " |
25 | Wing settings 2 | (13, 18) "front " (magenta) " > " |
26 | F3 race header | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] (10, 12) (yellow) "STANDARD OF RACE" (14, 14) |
27 | F3 driver menu | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] Menu [36-37]: "1 ENTER ANOTHER DRIVER" "2 START RACE" |
28 | F3 laps menu | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] (2, 10) (cyan) " SELECT NUMBER OF LAPS" Menu [36-38]: (flashing cyan) "PRESS " "1 5 laps" "2 10 laps" "3 20 laps" |
29 | F3 driver prompt | Clear screen "FORMULA 3 CHAMPIONSHIP" header [54] (2, 10) (cyan) " " (green) " DRIVER > " |
30 | Press space | (5, 24) (cyan) "PRESS SPACE BAR TO CONTINUE" |
31 | Configurable colours | " " (/black) {foreground/background} |
32 | Spaces and backspaces | " " (/black) backspace backspace [31] |
33 | Configurable header 1 | Clear screen (4, 3) [34] {# spaces} [34] (36, 2) |
34 | Configurable header 2 | double-height {foreground/background} {token} " " (/black) |
35 | Cyan prompt | (2, 10) (cyan) |
36 | Menu option 1 | (4, 14) (flashing cyan) "PRESS " (5, 16) (cyan/blue) "1 " (/black) " " (yellow) |
37 | Menu option 2 | (5, 18) (cyan/blue) "2 " (/black) " " (yellow) |
38 | Menu option 3 | (5, 20) (cyan/blue) "3 " (/black) " " (yellow) |
39 | Initial menu | Menu [36-37]: (flashing cyan) "PRESS " "1 PRACTICE" "2 COMPETITION" |
40 | Race text | "Lap Time : Best Time " |
41 | Race text | " Less than one minute to go " |
42 | Race text | " YOUR TIME IS UP! " |
43 | Race text | "Position In front: " |
44 | Race text | "Laps to go Behind: " |
45 | Race text | " " |
46 | Screen mode 7 | Screen mode 7 Disable cursor |
47 | Unused | - |
48 | Race text | " PLEASE WAIT " |
49 | Cursor to heading | (9, 2) |
50 | Configurable text for number of laps | (24, 2) " 5 laps" |
51 | Text | " POINTS" |
52 | Text | (magenta) "R" (yellow) "E" (cyan) "V" (green) "S" |
53 | Race text | " FINISHED " |
54 | F3 header | Call PrintHeader with X = 0: Clear screen Double-height (yellow/red) "FORMULA 3 CHAMPIONSHIP" |