Maya Grab [Day 22] – Packing and preparing release

It’s time for a release!

Current version is 0.2, still alpha, but fully playable and hopefully without any bugs. I’ve made also a little info program with credits and some basic instructions how to play the game.

Both german and english versions with all the infos will be stored on a single disk (d64) file. Very good CrossDev utilities that I’ve found for the C64 are DirMaster, for manipulating files and organizing disks, and Exomizer, for crunching (packing) programs. I’ve used DirMaster to extract prg files from my current dev disks for crunching and then putting them back (crunched) on a new disk. Exomizer shrinked the program from 36830 bytes (145 blocks) to 15240 (60 blocks), which is amazing and probably due all the nicely crunchable room graphics in the game. It’s also adding a so called “trampoline”; meaning, after loading, the program is not listable (it contains only one SYS command). With DirMaster I’ve included a couple of DEL files to show additional release information when the disk directory is listed – I’ve always wanted to do that!

Enjoy the game!

Maya Grab v0.2.0 (alpha):

Download Link

Source code BASIC
Source code BASIC (EN)
Source code ASM

* * * * *

Maya Grab [Day 21] – Localization

Today I’ve translated the game to english. I’ve assumed that the english version could take more space than the german, so I’ve cleared the rest of the spaces between commands in the whole listing and gained about 500 extra bytes. After testing now both versions again, I’ve found two more bugs I had to fix, and also added a counter in the game statistics, which counts every meaningful command the player types in. Guess it would be interesting at the end of the game to have this info too. Another option I’ve found in the original game is to toggle the keyboard beep sound with the F1 key; added that too.

All these changes are included again in the 0.2.0 version. For the english version I’m including only the localized strings. It turned up that this version is 240 bytes smaller than the german one.

Program size: 38456 bytes, on disk 36830 bytes or 145 blocks
Free BASIC memory: 455 bytes
Used BASIC commands: –

Source code BASIC
Source code BASIC (EN)
Source code ASM

* * * * *

Maya Grab [Day 20] – Interpreter (2) and game statistics

More than a week passed since the last journal entry, but I was everything, but not lazy. Lots of work has been put into this version because I wanted it to be the first fully playable alpha. Along with constant low memory issues, the interpreter is now completely finished, lots of optimization and bug fixing has been done, and game statistics are added as a new feature which is not available in the original game. Let’s see the code changes.

5140 PT=TIME/60 : REM RESET TIMER

16515 A=INT(TIME/60PT)
16520 X=INT(A/60): Y=INT(AX*60)
16525 X$=STR$(X): Y$=STR$(Y)
16530 X$=RIGHT$(X$,LEN(X$)-1): Y$=RIGHT$(Y$,LEN(Y$)-1)
16535 IF X<10 THEN X$=“0”+X$
16540 IF Y<10 THEN Y$=“0”+Y$

When a game starts, the system time in seconds is stored into the PT variable. Then when the player calls the statistics, it’s subracted again from the system time. The seconds and minutes are calculated from the given number and then formatted and converted to a string. The overall progress is calculated by adding points for every room visited (1 point), and for every solved puzzle (1-5 points). It’s a bunch IF THEN calls which are checking every single room flag, and it’s a best example how not to organize data. All the room states could be simply stored like a integer array, one entry for each puzzle. It would perform faster, take less memory, could be tested in a loop for the statistics part. It’s first on the list for refactoring in future versions.

– check for a special command
  these commands are: statistics and new game
– check for look at object command
  only objects that the player has picked up are processed
– check for room specific commands
  is command matching one of the commands for the room the player is in
– check for priority commands
  these commands are: take, go, look, open
– check for spaces in user input
  are there any spaces, if yes extract first two words from user input
– check length of the entered words
  all known words are not longer than 10 characters
– check if entered words are on the known words list
  both lists verbs and nouns are processed for first two words

This is how the interpreter works. Here it’s not about being smart and handle as many words and combinations as possible. It’s about speed and giving the player as soon as possible a meaningful response using BASIC on a C64. After many hours spent testing the whole thing, this is a more-less reasonable solution I’ve came up with, and I think it’s performing a bit slower than the interpreter in the original game.

– execute a special command
  prints the game statistics or restarts the game
– room specific response
  one of the responses for the room the player is in
– priority command response
  can’t take/go/look/open that now
– known words response
  can’t do that now
– known word response
  what?
– words longer than 10 characters, or unknown words response
  “word” – don’t understand

These are all possible responses from the interpreter. If you really want to test your patience, just enter a 30 character sentence with first two words under 10 characters and not from the known words list. For speeding things up I’ve stored the priority commands (with one space at the end) in separate variables (lines from 3980-3983); it saves many string concatenations. Also the get input routine was extremely slow because it’s located around line 14500, and the play sound subroutine at 51000; I’ve relocated it to line 14950.

Program size: 38688 bytes, on disk 36830 bytes or 145 blocks
Free BASIC memory: 223 bytes
Used BASIC commands: TIME

Source code BASIC
Source code ASM

* * * * *

Maya Grab [Day 19] – Interpreter

Last two days I was working on the first part of the interpreter which filled again almost all remaining bytes. The game is now playable from start to the end; if you type the correct commands, the wrong ones are not handled yet. That leads us to the point how it’s all working.

To have a smart command interpreter I’m immediately thinking, ok, first I’m going to analyze the words for the grammar, then I’ll combine the verbs with objects that are available at this point of the game and check their relations to the room the player is in or other objects in the game… etc. Nice, I’ll do it maybe next time in a object oriented language. Organizing and handling so much data is really painful in BASIC, and going through all lists of possibilities will be also very slow. I’m pretty sure it’s doable, but for this project it isn’t really necessary.

So I’m using the most straight forward dumb system on earth – pure string comparison. When the player enters a command, the first thing to check is, is it a special command. Special commands are inventory, statistics and an option to restart the game; these are not available in the original game. If it isn’t a special command, the interpreter compares the command with all possibilities which are available for the room the player is in. If still there is no match, the parser will try to check the command and give an appropriate response to the player (“unknown command”, “this can’t be done now”… etc). The code that I’ve did for the rooms looks like hell with bunch of IF THEN commands and setting various bit flags for every room.

Next time (after freeing again some space) I hope, I’ll implement the missing special commands and finish the interpreter by adding the parser.

Program size: 38532 bytes, on disk 36576 bytes or 144 blocks
Free BASIC memory: 379 bytes
Used BASIC commands: OR, AND

Source code BASIC
Source code ASM

* * * * *

Maya Grab [Day 18] – Every byte counts

Couple of days passed without journal updates because I was mainly busy on freeing up memory for the interpreter. Various tests had to be done, which parts of the code can be removed or optimized and also how the interpreter will work in the first place and how much space it will need. Practically I’ve learned nothing new, or what’s already mentioned in this very good article here.

First step was to remove all the comments from the code, it was clear already on the first day of coding that they are eating too much space. After that, wrapping up all data for sprites and machine code into less lines; each line can have up to 80 characters, so far I was using only 40 (makes the code easier to read). Same goes for the room drawing, and additionally I’ve added loops while drawing, even if a same line needs to be drawn only two times. It’s amazing who many bytes can be saved by doing a couple of quick fixes. Let’s see some numbers that I’ve written down during the process:

– removing start comments (lines 100-170)
  570 bytes saved
– removing interpreter usage comments (lines 20005-20115)
  426 bytes saved
– removing scope definition comments (eg. lines 55005-55015)
  700 bytes saved (100 bytes per scope)
– removing routine definition comments (eg. lines 55100-55110)
  3200 bytes saved (50 bytes per definition)
– removing double colons for separating chunks of code
  492 bytes saved
– removing adverbs definitions, interpreter has only a verb + noun parser
  694 bytes saved
– removing unused subroutines (wait and print free mem)
  154 bytes saved
– writing sprite data 12 bytes per line (earlier only 6)
  313 bytes saved
– writing machine code data 16 bytes per line (earlier only 4)
  242 bytes saved
– writing room rendering using 1 PRINT per line (earlier 2 or even 3)
  approx. 150 bytes saved per room

With less lines of code now calling subroutines or jumping works also much faster. There is still potential for shrinking the code further (and make it more ugly), but currently I’ve freed enough space for the upcoming things I have on my mind.

Nevertheless, the code that I’ll continue to include here will still have all the comments and colon separated lines, for easier reading and understanding.

* * * * *

Maya Grab [Day 17] – Using Assembler in BASIC

Task for today was to include the assembler code into the BASIC program and to replace old drawing routines with the newly created ones which run much faster. By the way, it’s been said that assembler code runs from 10 to 1000 times faster than BASIC, depending on what the code is actually doing.

I didn’t wanted to load any external files; the game should be stored in one file. So the assembler code needs to be written from BASIC directly into the desired location in memory and that’s the upper RAM located at address $C000. It’s the same procedure like transferring data for sprites; POKE-ing values to specific address. Meaning, the compiled code should be stored like data. Now, I’m pretty sure that there is a easier way (or some helper tools) to generate BASIC data from a compiled assembler code; but I did it the hard way. KickAssembler compiled the code and build a .prg file with a pointer to store the program at address $C000. So first the .prg file needs to be loaded. Then I used a MONITOR to dump the memory between $C000 and $C0B0 where the assembler code is stored. The code is shown in byte format, but hexadecimal, so I wrote a little tool which converts comma separated hexadecimal values into decimal and voala, there is the data needed for BASIC.

60005 REM *****************************
60010 REM * MACHINE LANGUAGE CODE     *
60015 REM *****************************
60020 :
60025 FOR I=0 TO 183
60030 READ A: POKE M+I,A
60035 NEXT

The whole assembler code fits in 183 bytes, actually 175, but I’ve put some zeroes at the end. This loop reads the data and stores it into address 49152 ($C000). This subroutine needs to be called only once when the game starts.

.label ClearRoom=$c052
.label ClearScreenPart=$c08e
.label ClearItemsDirections=$c07a
.label DrawFrame=$c000
.label ClearInputMessage=$c066

Probably every compiler after building the code is kind enough to provide a symbols file with addresses for every subroutine. The values must be converted to decimal for the SYS command which will be called from BASIC.

SYS 49234: REM CLEAR ROOM
SYS 49274: REM CLEAR ITEMS AND DIRECTIONS
SYS 49152: REM DRAW FRAME
SYS 49254: REM CLEAR INPUT MESSAGE

This is how it looks like at the end in BASIC when calling the assembler subroutines.

While entering the new data I’ve ran out of memory for the first time. Needed to remove old drawing routines first before continuing the work. What’s left to complete the original game is only the interpreter. Before even starting that, I need to make as much space as possible, since only 23 bytes are left.

Program size: 38888 bytes, on disk 37084 bytes or 146 blocks
Free BASIC memory: 23 bytes
Used BASIC commands: –

Source code BASIC
Source code ASM

* * * * *

Maya Grab [Day 16] – Assembler optimizations

Last time I’ve talked about memory issues, and I must think through again how to resolve this problem. The original game fits complete into the basic memory, and even there’s 8kb left free. Before I start kicking some code out, mainly comments, I’ve tried to squeeze in the last string resources for the interpreter response messages, and I’ve succedeed. Now there are only 369 bytes free; terrific, but where I’m going to put the whole interpreter? Switching to dumb strategy: do whatever you’re in the mood for doing, and that surely ain’t writing a text adventure interpreter in BASIC in just 369 bytes.

Therefore, it’s time for some C64 assembler. I’m very excited about this topic; last time I’ve did assembler on PC was 12 years, and on C64 about 20 years ago. The 6502 processor has a very limited instructions set listed here. There are a couple of more things that makes coding even harder, for example, only 3 registers, no instructions for multiplication and division, 8bit data… The good news are that you don’t have to code on a C64 thanks to cross assemblers and they have many useful functions for organizing code, importing data, automatically building and debugging code on a emulator.

After researching a while I’ve decided to use KickAssembler. It has all the options needed, and it can be easily integrated into the text editor I’m using, with syntax highlighting and code building; it even supports variables and java script like language. All the features are listed in the KickAssembler manual here. And for better C64 understanding I would strongly suggest this book here.

The main problem is the UI drawing in the game; it’s slow, but before you draw anything, you also have to clear some parts of the screen. Since the room drawing depends on room state data which can’t be easily passed to assembler subroutines, and same goes for printing items and directions, the thing what could make sense is first to code routines for drawing the frame and clearing stuff on the screen. Items and directions are printed more-less at a descent speed. Time to see some assembler code:

// include basic upstart
.pc = $0801 “BASIC”
    :BasicUpstart($c000)

// code start address
.pc = $c000 “Maya Grab”

The code starts with two .pc directives which tell the compiler where the code should be stored in memory while loading. The first directive has a small convinience method which tells the compiler to include a BASIC startup code to call the machine code at address $c000. I’ve mentioned that on the second day of the journal that most games written complete in assembler have only one basic line with a call to the actual machine code. I’ve included that only for faster testing.

// zeropage pointers
.const screen = $fb
.const param1 = $fd
.const param2 = $fe

// constants
.const CHAR_FRAME = 160
.const CHAR_CLEAR = 32
.const FRAME_COLOR = 2
.const FRAME_WIDTH = 27
.const FRAME_HEIGHT = 18

The zeropage pointers are simply pointers to a memory address on page zero (first 256 bytes). They are used very often because only the low byte must be set when addressing (high byte is always zero in $00fb, $00fd and $00fe). I don’t think that this zeropage space is meant to be used for storing any data (lot’s of kernal stuff is placed there), and I’ve read somewhere that BASIC uses most of the addresses too, but these few at the end are pretty much safe. Other constants are self explanatory.

/*******************************
 * Draw Frame
 *******************************/

DrawFrame:
    ldx #00

!loop_x:
    lda #CHAR_FRAME
    sta $0400,x
    sta $0400+FRAME_HEIGHT*40,x
    lda #FRAME_COLOR
    sta $D800,x
    sta $D800+FRAME_HEIGHT*40,x
    inx
    cpx #FRAME_WIDTH
    bne !loop_x-

This is the first part of the draw frame routine for drawing the horizontal bars at the top and bottom of the frame. While in BASIC you can change the text color directly in the PRINT command, in assembler things are a little bit different. Printing a char on screen is the same as putting a byte into the screen memory, which by default is beginning from address $0400. But there is also a color ram which holds the colors for every char on screen and it’s located at address $D800. So this routine loads the char (reverse space) first and stores it at $0400 and then the char color (red) and stores it at $D800, both with an offset which is stored in register X.

    lda #$04
    sta screen+1
    lda #$28
    sta screen
    lda #$D8
    sta param2
    lda #$28
    sta param1
    ldx #00

The second part of the routine is preparing everything for rendering the side bars. The screen address and the color ram address are loaded as hi/lo bytes and stored into the previously defined pointers. Since we’re dealing with a 8bit machine, we can’t do something like lda #$0428 or lda #$D800.

    ldx #00

!loop_y:
    pha
    lda #CHAR_FRAME
    ldy #00
    sta (screen),y
    ldy #FRAME_WIDTH1
    sta (screen),y
    lda #FRAME_COLOR
    ldy #00
    sta (param1),y
    ldy #FRAME_WIDTH1
    sta (param1),y
    pla

    clc
    adc #40
    bcc !no_carry+
    inc screen+1
    inc param2

!no_carry:
    sta screen
    sta param1
    inx
    cpx #FRAME_HEIGHT1
    bne !loop_y-
    rts

The final part now is a little bit complicated. First the counter in register X is reseted. Next, while writing one char and it’s color left and right for the bars, the register A is needed and therefore it must be saved first on the stack with the PHA command and later reloaded with PLA. Now the 8bit limitation strikes! To render the next line, we can’t just add an offset of 40 bytes and continue looping. The value of the A register can be 240 and adding just 40 would exceed 8 bits. Luckily, the processor has a carry flag which is set when overflow occurs while doing operations. First it needs to be cleared with CLC, then 40 bytes are added to the offset; if the carry is set, we need to update the hi byte in our screen and color ram addresses, otherwise we’re updating only the lo bytes.

/*******************************
 * Clear Screen Part
 * in:  screen = pointer start
 *      param1 = width
 *      param2 = height
 *******************************/

ClearScreenPart:
    ldx #00

!loop_y:
    ldy #00
    pha

!loop_x:
    lda #CHAR_CLEAR
    sta (screen),y
    iny
    cpy param1
    bne !loop_x-
    pla

    inx
    cpx param2
    beq !clear_done+

    clc
    adc #40
    bcc !no_carry+
    inc screen+1

!no_carry:
    sta screen
    jmp !loop_y-

!clear_done:
    rts

This routine simply draws a rectangle of spaces into the screen memory and it uses the same carry flag principle as the draw frame routine. But before calling it, parameters must be set as follows: screen is pointing already at the position where the rectangle starts and param1 and param2 are used for the width and the height of the rectangle.

ClearRoom:
    lda #25
    sta param1
    lda #17
    sta param2
    lda #$04
    sta screen+1
    lda #$29
    sta screen
    jsr ClearScreenPart
    rts

An example how the ClearScreenPart routine is used for clearing the contents inside the frame.

The complete source code of all routines in assembler is included.

Program size: 38542 bytes, on disk 36322 bytes or 143 blocks
Free BASIC memory: 369 bytes
Used BASIC commands: –

Source code BASIC
Source code ASM

* * * * *

Maya Grab [Day 15] – All rooms finished

Finishing all the rooms had high priority because I wanted to see how many BASIC bytes would remain free.

Well, not so much… 6188 bytes after the game is loaded and only 3524 after first start and all the allocations. While playing the game, and now while drawing all the rooms, I’ve noticed that the room complexity dropped a little bit from the middle of the game to the end probably because of memory issues.

All the missing code to finalize the game will probably fit in, but what’s bothering me are approximately 100 more strings I need to define for all the possible interpreter responses for all the rooms. The plan was also to add some extra features to the game and couple of extra rooms, but it’s going to be very hard, at least for the rooms.

Program size: 35387 bytes, on disk 32766 bytes or 129 blocks
Free BASIC memory: 3524 bytes
Used BASIC commands: –

Source code

* * * * *

Maya Grab [Day 14] – Sprites

Today we have a really interesting topic to cover – sprites. I’ve really enjoyed learning (or re-learning again) how sprites work on the C64, so this would serve also as a little tutorial how to setup everything in just a few steps in BASIC. The C64 supports eight hardware sprites which are totally independent of other graphics or text shown on the screen. They can be positioned and moved; they support collision detection with each other or with the background, they can be single or multi colored, and can be stretched (double-sized) in both X and Y directions. In single color mode, sprites have a size of 24×21 pixels. Therefore only 63 bytes are needed per sprite since only 3 bytes per row are used. Some basic instructions how to deal with sprites can be also found here.

Sprites are controlled by the VIC-II graphics chip, so setting everything up is easy as writing a couple of values to the registers which begin at address 53248 ($D000); this address is also stored in my variable “V” for faster access. Let’s setup the first sprite (sprite 0):

POKE 2043,13
        set sprite 0 data pointer
FOR I=0 TO 62: READ A: POKE 832+I,A: NEXT
        read 63 bytes from the data block and store it from address 832

Sprite data pointers are registers which tell the VIC where are those 63 bytes of sprite data located. These registers are always behind the screen area, beginning at 2040 ($07F8) for the first to 2047 ($07FF) for the last sprite. The VIC can “find” sprite data only in his current 16kb memory bank; by default everything is in the first bank; there are four banks in total since there is only 64kb of ram available.

The first POKE is setting the sprite 0 data pointer to the 13th memory block of the first bank (memory block size is 64 bytes). The loop is reading data from the data block and storing the data from address 832 which is exactly the 13th block (64*13). If you look at the C64 memory map, there is a gap of 192 bytes between addresses 828 and 1019 for the Datasette buffer. The only problem is that there is only space for three sprites and the other five must be stored somewhere in the first 16kb; tricky in BASIC since the program can easily overwrite the sprite data.

POKE 53269,1
        sprite 0 enabled

Sprites can be enabled by setting different bits of the 53269 register (0 for none, 1 for sprite 0… 255 for all eight sprites).

POKE 53248, 50
        sprite 0 x position
POKE 53249, 50
        sprite 0 y position

Sprites can be positioned by setting value pairs to addresses from 53248 for sprite 0 to 53263 for sprite 7. There is one extra register at address 53264 for storing sprite x coordinates beyond 255 pixels.

POKE 53277,1
        sprite 0 double width
POKE 53271,1
        sprite 0 double height

Sprites can be stretched (double sized) in both directions by setting different bits of the 53277 and 53271 registers (0 for none, 1 for sprite 0… 255 for all eight sprites).

POKE 53287,0
        sprite 0 color black

Sprites can be single colored by setting first four bits to addresses from 53287 for sprite 0 to 53294 for sprite 7. For multi colored sprites two additional colors are shared between all sprites.

And that’s it. With the basics covered we can take a look at the code changes:

1515 V=53248: S=54272: C=646: B=832

There is a new variable B for holding the start address of the buffer where the sprite data will be stored.

5125 GOSUB 55100: REM INIT SPRITES

Since reading data from a data block and writing to the memory takes some time, the wait call on the title screen has been replaced by a call to initialize the sprites. Why wait, when we can do something useful while the title screen is showing.

55100 :
55105 REM – INIT SPRITES ————–
55110 :
55115 RESTORE
55120 GOSUB 56000
55125 GOSUB 57000
55130 GOSUB 58000
55135 RETURN

This routine rewinds the pointer to the data block with the command RESTORE and then calls the initialization routines for the first three sprites that are going to be used in the game, since there is no more free memory in the first bank. The original game uses four sprites in total, so the last one will be initialized later during the game, as soon as one of the currently loaded sprites is not needed anymore.

55500 :
55505 REM – DRAW SPRITE —————
55510 :
55515 POKE V+29,7
55520 POKE V+23,15
55525 POKE V+21,A
55530 RETURN
55535 :
55600 POKE V+29,6
55605 POKE V+23,14
55610 POKE V+21,1
55615 RETURN

There are two subroutines for drawing sprites. The first one draws a default scaled sprite specified in the variable A and the second one draws unscaled the first sprite.

56000 :
56005 REM – INIT GUARD —————-
56010 :
56015 POKE 2040,13
56020 FOR I=0 TO 62
56025 READ A: POKE B+I,A
56030 NEXT
56035 :
56040 POKE V+0,50
56045 POKE V+1,50
56050 POKE V+39,9
56055 RETURN
56060 :
56100 DATA   0, 28,  0,  0,127,  0
56105 DATA   0,107,  0,  0,255,128
56110 DATA   0, 99,  0,  0, 62,  0
56115 DATA   0, 28,  0,  0,255,128
56120 DATA   1,223,192,  3,111, 96
56125 DATA   6,119, 48, 12,123, 24
56130 DATA  24,125, 48,112, 82,224
56135 DATA   0,127,128,  0,127,  0
56140 DATA   0, 54,  0,  0, 99,  0
56145 DATA   0, 99,  0,  0,192,128
56150 DATA   3,128,224

Here’s how the initialization routine looks like for the first sprite. The funny thing is, when I was looking at the Maya Grab memory dump two weeks ago, I’ve seen that from the address $1DB0 to $20A8 a large amount of comma separated values is stored. I’ve suspected that this might be the data for the sprites. So I’ve wrote a simple program to read the data into four sprites and I was right. It saved me a lot of time; I don’t need to calculate all this data manually.

Program size: 27505 bytes, on disk 24892 bytes or 98 blocks
Free BASIC memory: 11406 bytes
Used BASIC commands: READ, DATA, RESTORE

Source code

* * * * *

Maya Grab [Day 13] – Drawing even more PETSCII

Trying to redraw every room from the original game is really fun. It’s amazing what can be achieved only with the C64 special characters in text mode. I have to admit also Maya Grab has really nice and colorful graphics; Thomas did a great job.

Today, four more rooms are completed with all their states. If you play the original game you can see that some rooms are drawn in two passes. For example, if you pickup an item, or open a door and then you leave the room, when you go back, first the room is drawn at it original state, and then the item is removed or open door is drawn. Not so good since drawing is slow. I’m organizing my room drawing routines to draw the correct state in just one pass. In case of picking up small items on a clear background, I check the room state even before rendering and set the appropriate item color. So, the item is drawn in either case, only if you’ve picked it up it’s drawn in the same color as the background and you can’t see it. With this trick branching the code is avoided. Branching is needed only when bigger portions of the screen needs to be modified.

Again, there are no changes in the code, except for the rooms which are not included in the source code. I’m also not posting any screenshots of the rooms; no one likes spoilers. Instead of that, there are couple of screenshots how it looks like to do PETSCII.

Program size: 25564 bytes, on disk 23114 bytes or 91 blocks
Free BASIC memory: 13347 bytes
Used BASIC commands: –

Source code

* * * * *