Showing posts with label c. Show all posts
Showing posts with label c. Show all posts

Saturday, 23 July 2016

Fort Django

A little reflection on the making of a Commodore 64 game. It's the first C64 machine-code game project I've really finished. All right, it was made with cc65 C with inline assembler but what I mean it's not made in BASIC. Significantly, it's the first 8-bit game I've made public in some way.

I guess making an 8-bit game is something I would have liked to do for a long time. Fort Django was started in 2014, and after a long pause I found the energy to make it into a release.

Djangooooooooohh....!
The game

You guide the character with a joystick in port 2. You can run, climb, crouch, jump and shoot. Shoot down the baddies, collect money bags and find the exit. The descending bonus timer means the faster you can collect the next bag the more $ you can get.

The game is very short and not at all hard. The only challenge comes from trying to be faster, for example it's possible to break the $10000 barrier on completion.

Get that bag, shoot that baddie
Inspired by Saboteur! from Durell, I at first thought about making a beat 'em up oriented game. As I needed to scale down the project I found it would be simpler to turn the game into a shooter with a western theme. The map is very small, but then again I like short games such as Saboteur! and Bruce Lee, as they have a strange kind of replay value. Much like with Saboteur!, I wanted to ensure there was a definite ending to the game and score could not be milked forever.

Making of

I created the game with cc65 C compiler, using inline-assembler for speed critical parts such as the sprite routines. The C64 has eight sprites, eight 8-bit addresses for the horizontal coordinate (0-255) and one address that holds the highest bits for all the X coordinates, so the sprites can also reach the right hand part of the screen. (256-320)

For a beginner it can be a bit tricky to decode these 9-bit sprite coordinates, especially in pure assembler. I have eight separate 16-bit memory locations for storing the X coordinates. These are then broken down into the hardware sprite coordinate values. This approach is a compromise between ease of use and speed.

The figure below shows how the 16-bit X coordinates are stored in $C000/$C001, $C008/$C009, $C010/$C018 byte pairs (the grey stuff in the middle). The less significant byte of these 16-bit values can be copied directly to the $D000, $D002, $D004... but the high bit is taken from the lowest bit of the most significant byte of the 16-bit values and combined into a value that is stored in $D010.


This is done once in a frame, so the high-bit issue can be forgotten in other parts of the code. The handiness of this is only really apparent in C, where you can then move the sprite x coordinate around with:

*(unsigned*)0xc000=*(unsigned*)0xc000+1;

Comparisons between coordinates become easier, too.  This checks if sprite 7 is right of the sprite 0:

if(*(unsigned*)0xc038>*(unsigned*)0xc000){do_stuff();}

Basically all the "collision detection" is built from this type of statements instead of the hardware sprite collision address, and as such is not pixel perfect. In these box-collision cases it's better to be lenient toward the player and a bit biased against the enemies.

The "16-bit" coordinate ought not to exceed 511, because only one bit is taken from the more significant byte.

The sprite Y coordinates are 8-bit values anyway and can be handled directly with the $D001, $D003, $D005... hardware addresses.

The whole X coordinate copying is achieved with the code below, starting from the less significant byte copying and ending with the high-bit construction. Obviously other locations than $C000- can be used for the coordinate storage.

     lda $C038
     sta $D00E
     lda $C030
     sta $D00C
     lda $C028
     sta $D00A
     lda $C020
     sta $D008
     lda $C018
     sta $D006
     lda $C010
     sta $D004
     lda $C008
     sta $D002
     lda $C000
     sta $D000

     lda $C039
     clc
     rol a
     ora $C031
     rol a
     ora $C029
     rol a
     ora $C021
     rol a
     ora $C019
     rol a
     ora $C011
     rol a
     ora $C009
     rol a
     ora $C001
     sta $D010

This is a starting point for a fairly generic solution, but of course it can be adapted for any particular needs. For example, the top sprite coordinates of the dudes in the game are copied and transformed from the bottom part coordinates, which changes the above routine a bit. Also, if less sprites are used why bother going through all the eight?

About the graphics

The graphics are made with PETSCII editor. Not only the background tiles, but the game map and even the sprites have been edited there. It goes to show that the PETSCII editor multiframe-editing is surprisingly powerful way for controlling this type of game "assets".
The game map tiles.
Another PETSCII screen was wasted for defining the movement rules for the above tiles. These are used for building a movement table every time the player enters a room, just as the room is drawn from tiles. How all these sprite, tile and movement table elements exactly relate to each other is a bit too intense to explain here, especially as they are not that well thought out.
The tile movement rules. @=space, A=block, B=platform, C=ladder
The map was also edited in the PETSCII editor. The 40x25 area is divided into 5x3 tile elements, giving 64 simple screens. Coloring indicates enemies and money bags. Not everything is absolutely visible in the picture below, as some spaces can be "colored" too. The game engine allows only certain combinations and positions for the enemies and objects.
The game map.
The map memory area is also used for indicating whether objects or enemies are removed, from the 1 (white=enemy) and 2(red=bag) status into 255 and 254. After the game is over these are changed back into 1 and 2.

Editing a multicolor sprite in PETSCII editor. The sprite export is not a standard feature!

What next?

I dropped many game elements I toyed with at some point. For instance, the chests could have contained items, and the doors could have potentially led to other areas in the fort.

The gold bags were a fairly late addition when I felt that only shooting bad guys would be far too minimal. From adding the bags it was a small leap to increase the jumping elements in the game. The bags also inspired the time-based "bonus dollars" mechanic. Still there might have been a bit more to do.

Code-wise, a music routine would have been nice but in the beginning I was a bit scared to sync the animation with a music interrupt. The sound effects are also sparse due to my inexperience with SID.

Yet, all additions would have also expanded the complexity of the game and the testing time exponentially. I'm glad I could finish it as it is. With this experience I already have some ideas about how to approach this type of project better.

Links

You'll need a Commodore 64 or an emulator to play the game.

Fort Django v1.1 at CSDb
Read the blog post about the v1.1

1.0 (older version):
Direct download
Alternative link
Page at CSDb
A cracked version in a T64 format

Monday, 18 January 2016

Developing for C64

This is an idiot's guide for developing C64 software on Linux. (That idiot is me.) I need these instructions each time I get a new linux computer in which I want to install the development software.

This means that using a compiler within a PC/Linux environment, the code is tested on a C64-emulator and then transferred to a real machine. cc65 is the compiler and I'll go through how it has been set up.

Why not assembler/machine code? Although using C is quite problematic for C64, it is handy for planning and testing ideas. The program can be first written in C which is easier to grasp, then later the code can be changed into in-line assembler (supported by cc65) and further optimized.


Installing the cc65 cross-compiler

It's a bit depressing that the first step in coding for c64 is to compile the cc65 compiler for your system. But it's not a big deal.

It's possible to download the cc65 packages and compile them, but Subversion also works here. (for example sudo apt-get install subversion)

Then copy the SVN-link from https://github.com/cc65/cc65 and svn checkout [link].

Edit: Just get the cc65-master.zip, extract it and continue.

Then make. (If for some reason there's no such command then sudo apt-get install make it is)

After the binaries have been created, the commands only work from the folder they are in. A simplistic solution: go to the directory with the compilers (ar65 ca65 cc65 and so on) and copy the compiler binaries to "bin" folder, for example sudo cp * /bin. (Might be /etc/bin or something different also.) (Edit: usr/local/bin should do.) Check the file permissions too, chmod +x filename.

Now command cc65 ought to bring a response "cc65: No input files" even when not in the folder where the binaries are.

So far, so good...
The additional include files go to the include folder under the cc65-master folder. This is also where the usual stdio.h, string.h etc. are found.

Setting the C64 emulator

You need a C64-emulator for running the code, and Vice emulator is the most comprehensive and also the most universally available.

sudo apt-get install vice.

Note that the Vice will need ROMs and Kernal files and ROMs for the Floppy Drives too (1541). You can download a different Vice package that contains the files and copy them to the respective directories. If you only want to code for C64 you can copy just that one directory contents.

After a successful installation

x64

...ought to bring up the emulator window, and x64 programname.prg should run an emulator file in that format.

From Vice, it is wise to disable the "confirm quiting VICE" parameter, disable saving parameters on exit (and then save these parameters manually.)


Which editor for code?

Some kind of text editor is needed for writing the C code. Both Gedit and Kate handle code and matching bracket highlighting well enough and are pretty user-friendly. Komodo Edit has a handy sidebar for easily adjustable direct terminal command sets, but it's not as readily available for all Linux distributions.

If you decide on Kate, then it's possible you have to fix the kdelibs too and install the oxygen icon theme.

sudo apt-get install kate
sudo apt-get install -y kdelibs-bin kdelibs5-data kdelibs5-plugins
sudo apt-get install oxygen-icon-theme

You need to Settings/Configure Kate and display the build plugin in "plugins". Display the Build Output at the bottom of the editor window and insert "make run" for the default target. From now on the Build Default Target will run the project, and the menu can be assigned a key shortcut such as CTRL+R.

The main thing is that you get whatever editor to do the compilation with a simple keypress, otherwise it becomes a chore. I use Control-R in Kate, because it is also used in Processing. Especially for a beginner it's useful to have a rapid trial-and-error cycle. Some might vouch for better pre-planning, but they are wrong.

Edit: I've learned to use xed and make from a terminal window, it's nearly as fast. Kate just seemed to need more and more effort to work.


Compilation and Makefile

The sources could be compiled with cc65 from the commandline (cc65 test.c and then cl65 test.s) but a Makefile comes in handy. Any text editor can be simply told to employ the "make" command in the project folder.  The Makefile becomes sort of glue between the different programs: whatever compilers and text editors are always invoked in the same way.

The image below shows an example directory tree for locating your c64 sources, in this case two different projects, test and game. Note that the .c files have the same base name as the folders.
Good makefiles can be pretty daunting to do if you're simply interested in doing c64 code. It's also easy to create a makefile that fails to work in someone else's system. I'm hardly an expert so I've tried to keep the makefile simple. I've worked with the one below, ripping off stuff from here and there.

Edit: The indentations need to be real TABs, so copy/pasting from this blog won't work directly :(


CFLAGS = -Oi

LDFLAGS =

# Project basename extracted from the current directory name
PROJ = $(shell basename `pwd`)

# Sources
OBJ = $(PROJ).o

$(PROJ).prg: $(OBJ)
 cl65 $(OBJ) $(LDFLAGS) 
 cp $(PROJ) $(PROJ).prg

%.o: %.c
 cl65 $(CFLAGS) -c $< -o $@ $(LDFLAGS) 

%.o: %.s
 cl65 -c $< -o $@ $(LDFLAGS) 

run:  $(PROJ).prg
 x64 $(PROJ).prg

clean:
 -rm *.o $(PROJ) *.prg

As long as the Makefile is positioned in a correctly-named folder with a correctly-named source all should be ok when running make.  make clean will remove all previous compilation outcomes and make run will compile and run the Vice emulator. (x64) These will need to be "teached" to the text editor. In my example I've not included any libraries or linked files so the LDFLAGS is left empty.

Added assembler sources (not inline) can be added to the source line, e.g. if you have sourcea.s and sourceb.s then add sourcea.o sourceb.o after OBJ = $(PROJ).o and so on. (Apparently it's not very simple to have make generally link all sources present.)

So, if I have the following test.c source inside a folder called test, and my Makefile is also there, I can make run and have the emulator run the resulting program. It only turns the border black (using inline assembler) and the background red (using C).

void main()
{
__asm__("lda #$00");
__asm__("sta $d020");
  *(char*)0xd021=2;
}



Thursday, 23 July 2015

The Quest for the MSX cross-compilation


My innocent hope was to get MSX cross compiling c/inline z80 assembler working on my Linux.
I thought it would be fairly simple as I already had it running on the Macintosh, and the Linux is supposed to be the coder's friend.

How wrong I was. Well, I didn't destroy anything in a fit of rage, nor did I have to sleep over this, so in a sense this was pretty successful. Also, what I've done here is comparatively simple to actually having done the groundwork, I'm simply trying to get someone else's solutions to work.

Admittedly, some woes came out of my broken Makefile which I had fiddled to get it work on the Mac, making it less general-purpose in the process.

This is not a how-to, just a recollection of how many annoying stages I had to go through.

The evening went something like this:

-Install sdcc from the repositiories. Find out the inline assembler does not compile.
-Get various z80 compilers and fiddle around with the makefile
-Find out that the sdcc version is too new to handle inline z80 in this way
-Remove sdcc
-Get old sdcc sources which won't compile.
-Mess around trying to configure and build the sources, installing stuff like bison in the process.
-Find out the version is too old or inappropriate to build anyways or something.
-Remove sdcc
-Find out there's a ready made version 2.9 that works.
-Download and copy the stuff to /usr/local/bin
-The MSX stuff starts to compile but needs hex2bin
-Get hex2bin sources
-Compile hex2bin
-Copy the stuff to /usr/local/bin
-Now I get the .com out of it but the .dsk outputting does not work. It requires a small thing called wrdisk.
-Find and get wrdisk
-Compile wrdisk
-Copy the stuff to /usr/local/bin
-Install openmsx from the repository
-Find out it does not load anything from disk without a proper MSX system rom.
-Copy the system rom from Macintosh.
-Spend quarter of an hour trying to figure out how openmsx accepts the system rom. From the unnecessarily complex manuals I find it copies into ~/.openmsx/systemroms, but nobody says how to invoke it
-Make several guesses as to what to pass onto the -machine parameter of openmsx.
-Guess correctly that openmsx -machine Toshiba_HX-10 works, even though that is not the filename.
-Find out that as the Toshiba HX-10 does not have a f*cking disk rom, it won't load the .dsk image.
-Copy the MSX2 FS-A1WSX roms but find out the openmsx won't run it as the files are lacking.
-Find out I've actually used a FS-A1WX rom variant, copy them instead.
-openmsx -machine Panasonic_FS-A1WX finally runs an MSX with a disk rom.
-Triumphantly run openmsx -machine Panasonic_FS-A1WX jaa.dsk to run the compiled file...
...to find out the autoexec expects a different name.
-Fiddle some more with the Makefile to get the proper autoexec from the project folder.
-Wait an eternity for the openmsx to boot up and load the command.com and the autoexec and execute the damn file. Curse the entire platform.
-Profit!

I probably can't even remember all the phases. Add to that the constant figuring out of proper terminal syntax and the appropriate folders. Now I'm spent and can't bother to even make the small piece of code adjustment I was supposed to do...

Wow, it was really worth it...