Wednesday, 13 July 2016

Monkey2 development setup

Today's post will be a bit different. It will not be about retro computing per se, but about my development setup and the tools  I use to create retro styled games. You know, the ones you can download from the My Games page.

This post is by request, but also for myself to remind me later on how I did things. :)

I am using Linux to develop but the idea is the same on all major platforms (Linux, Windows, MacOS)

Development Setup


Monkey2 is the new language by Amiga veteran Mark Sibly. It evolved from the Blitz Basic language and from the multi-platform Monkey language.  Monkey2 code is translated to C++ code and then compiled, and it uses SDL2 for graphics, input, audio, etc so it's perfect to create games!!

Monkey2 is in development, but the 1.0 version is very capable and a lot of fun to use!

The things I make with Monkey2 are located on my Github page, in a new area called mutated monkey. Here you can find my code examples, game framework, sublime text configurations, code snippets and more...

Here's an overview of my development system setup:

  • Operating System: Linux Mint 17.3
  • IDE/Editor: Sublime Text 3
  • Builder: Gradle
  • SmartGit: source repository client
  • Monkey2: language translator
  • Java: to run SmartGit and Gradle

SmartGit


I downloaded the archive from this page and unpacked it in my home folder. I am not going to explain how to set this up, but after starting SmartGit you can clone repositories from Github easily.

Sublime Text 3


ST3 can be downloaded from its website, or you can use the software manager. The website has the newer version, almost all of the time. Install it using the downloaded .deb package. 

Monkey2 Language Definition


I've made a Monkey2 language definition file and handy snippets. This file needs to go into your ST3 Packages/User/ folder.  For Linux, this is under ~/.config/sublime-text3/. This path will differ on other OS types.

Simplest thing to do is to make a monkey2 sub-folder in the User folder and put the contents of the repository in there. Now, when you restart ST3, you can select Monkey2 under View/Syntax/

Monkey2


Monkey2 is free to use and you can download it from the Monkey2 web site. A smarter thing to do is to use SmartGit to clone the github repository to your local drive. I cloned the repository to my home directory, and then ran monkey2/scripts/rebuildall.sh to build the modules, editor (TED2) and docs. You may have to set the execution bit on the scripts to make them executable.

Gradle


The build system used is Gradle (http://www.gradle.org). Gradle is free and requires Java. Install Java and Gradle according to the installation guide, and make sure you can start Gradle from the command line, from anywhere on the system (meaning: add the Gradle path to the system path variable)


Usage


Now we have all the tools in place, lets get to work. Here's now to setup a ST3 project which will use Gradle to build your application or module.

To achieve this, we need to create 3 files in a project folder:
  • <projectname>.sublime-project
  • build.gradle
  • main source file

sublime-project file


I always work with projects, it makes life so much easier inside ST3. ST3 makes it simple to move between projects and each project can have its own build systems.

To use Gradle for building we use a build system from the Sublime Text Tools menu We create these by adding a build system (or more than one) to a Sublime Text project file.

You create a new project by selecting Add folder to Project from the Project pull down menu. So first create a folder, then use this menu item. Save the project by using Project/Save Project As. I advise you to put the sublime-project file in the project folder.

Now, choose Project/Edit Project and paste the following template in it:

{
"folders": [
{
"path": ".",
"file_exclude_patterns": [ ".git*", "*.i", "*.sublime-workspace"],
"folder_exclude_patterns": [".gradle"]
}
],
    "build_systems": [
   {
            "name": "Gradle: Build and run TEST debug",
            "cmd": "gradle --daemon build_project_debug",
            "working_dir": "${project_path}",
            "shell": "true"
        },
        {
            "name": "Gradle: Build and run TEST debug CLEAN",
            "cmd": "gradle --daemon build_project_debug_clean",
            "working_dir": "${project_path}",
            "shell": "true"
        },
       {
            "name": "Gradle: Build and run TEST release",
            "cmd": "gradle --daemon build_project_release",
            "working_dir": "${project_path}",
            "shell": "true"
        }

    ]

}

This project template has three build systems. The first will create a debug build. The second will also create a debug build, but it will clean the build folder beforehand. The third build system will create a release build.

As you can see, the command to run for each build system is a shell command; each will start Gradle with the required parameters. First --deamon, which will keep Gradle running in the background for faster builds, and then the task to execute, which is defined in the build.gradle file in the same folder. These build systems do NOT call mx2cc! We will let Gradle do that.

Save the project and the build systems will appear in Sublime Text's Tools/Build System menu. You need to select one and then you can use CTRL-B to build.

build.gradle file


Now we need to create the build.gradle file. This file will hold the definitions of the build tasks we want to execute, mainly the entry source file and the command line to build. Create a new file in the project folder and edit it by pasting this text into it:

def PROJECT_NAME = "asteroids.monkey2"

task build_project_debug << {
        println "Building project DEBUG...\n"

        def command = "/home/wiebo/Monkey2/bin/mx2cc_linux makeapp -run -target=desktop -config=debug $PROJECT_NAME"
        print "Running command: '$command'\n"

        def proc = command.execute()
        proc.in.eachLine  {line -> println line}
        proc.err.eachLine {line -> println 'ERROR: ' + line}
        proc.waitFor()

        def exitValue = proc.exitValue()
        if (exitValue != 0) {
                throw new GradleException ("Build project failed with exit code: $exitValue")
        }
}

This is an example build.gradle file, showing only one task, but you'll get the idea, you can add more tasks. Make sure you can call them from the build systems definitions in the ST3 project file.

The PROJECT_NAME variable is set to the main file to build, in this case it is asteroids.monkey2. This file needs to be in the same location as the .gradle file, so make sure to put it there.

A task is then defined called build_project_debug. You saw this definition in the ST3 project file.

I used a hard coded path to my monkey folder inside my Linux home directory to call mx2cc_linux. If you're on windows, you need to change this to your own monkey2 bin folder and use mx2cc_windows.exe. For OSX this is mx2cc_macos. The -config=debug option is used to create a debug build.


Done!



That's it. Once you create the main monkey2 file in the project folder, and add some code to it you should be able to build. Don't forget the select the correct build system!

Happy coding!

Monday, 16 May 2016

Amiga Compact Flash HD corruption

This post talks about an emulated Amiga, with CF cards as hard drive, under Linux Mint. Things work a little bit differently under Linux. This post might help you, or me in the future when I need to re-do steps :)

Corruption!


I use a compact flash (CF) card as hard disk in my Amiga 4000D.  When booting the machine the following error message was displayed: Not a DOS disk in DH0.  When the machine was turned off some time ago it was working just fine, so there must be something off.

Taking the CF card to my PC, which uses Linux, I had the challenge to mount the CF card so I could play with the disk in FS-UAE. Mounting a CF card happens automatically in Windows but not on Linux. So, some work to do!

Turns out that most Linux kernels recognize the Amiga FFS (Fast File System) but not the partition table. So how to inspect the condition of the partitions? I came across this lovely post, tacking the same challenge. Following the procedure mentioned there, using parted, I got the following output:

Disk /dev/sde: 3997MB
Sector size (logical/physical): 512B/512B
Partition Table: amiga

Number  Start   End     Size    File system  Name  Flags
 1      8259kB  277MB   268MB                DH0   boot
 2      277MB   801MB   524MB   affs1        DH1
 3      801MB   3997MB  3196MB               DH2

Oh dear! My boot partition does not show the file system anymore. The partition holding WHDload and games is also not reporting the file system. Can this be fixed? I doubt it, but it sure is apparent that I also need a proper backup methodology for my Amiga CF card, because reinstalling every time a problem occurs is not something I am looking forward to.

Trying to mount /dev/sde or /dev/sde1 (the 1st partition) results in the error message using dmesg to view the driver messages: [3597.260224] affs: No valid root block on device sde

As expected! :( Mounting sde2 works though: sudo mount -t affs /dev/sde2/ /mnt/cflashcard/

Also as expected! Running nemo (the Linux Mint file manager) as ROOT enables me to copy the stuff from there to my PC:



Reinstall


Having saved the available data, it's time to reinstall. First we are going to remove all partitions from the CF card.

After un-mounting the partition, we remove the partitions from the CF card by starting parted and using rm <partitionnumber> for each partition. Make sure to target the right device :)

So let's go to FS-UAE-LAUNCHER.

Configure the Amiga 4000 emulated machine


NOTE: Start the launcher with root permissions or you will not be able to use the CF card device file.

I have created a new configuration, and selected the A4000/040 as the configuration.

We want to connect the CF card as the hard drive to the Amiga. As everything on Linux is a file, we need to select the file under which our CF card is presented to the system: /dev/sde. Again, this is a file, so press the disk tab in the launcher and select the "Browse for file" icon to the right of the first drive. Browse to /dev and even though you do not see the sde file, type in its name, like so:

Click Open and the device will show up as follows:


Now go to the floppy drive tab and select the Workbench Install disk (and the other disks) to begin installation.

After boot, copy the HDToolbox program to your RAM disk, and change its icon property SCSI_DEVICE_NAME to uaehf.device. You should now be able to see the CF card and partition it and then format it with FFS. Make sure to enter the Advanced Settings and set the max transfer to 0x1fe00. There was no need for me to disable International Mode as required on Windows.

Check out this post I've made earlier for more details.


Saturday, 14 May 2016

Amiga FS-UAE on Linux Mint


The fs-uae emulator is offered through the software repository of Linux Mint, but it is also downloadable from the official website. Most of the time repository versions are running behind in version. But how much?

Let's compare! Start the software manager and search for uae. Both fs-uae and the fs-uae-launcher will show up. Let's install fs-uae first, and then install the launcher. After starting the launcher we are greeted with this screen:


First thing to notice: the launcher version is behind the version shown on the official download pages: version 2.2.3 (software manager, version from 2013) versus 2.6.2, which is the latest stable version on the official site. Three years of development backlog is too much to cope with so let's to this manually.

Installing the latest version is easy: I followed the instructions on this page: https://fs-uae.net/download#ubuntu. I did not install fs-ua-arcade though, so all I did was:
sudo apt-add-repository ppa:fengestad/stable
$ sudo apt-get update
$ sudo apt-get install fs-uae fs-uae-launcher
Starting the launcher then shows me that I need to select a path which contains to the Amiga Kickstart ROMs. After selecting this folder, and clicking Import, the ROMs that are correct show up in green:



NOTE: When you press import, the ROM files will be copied to the folder ~/Documents/FS-UAE/Kickstarts.

NOTE 2: The ROM files are copyrighted, I will not tell you where to find them , but you know ... the internet ...

To create a configuration, type in the name and press the little white box with a red arrow over it to save the configuration. I called mine A1200-1 as I am sure I will tweak this Amiga config from time to time:



Next up is looking to the right, and selecting the A1200 Amiga model. There is not much left to do now but select the folders that contain the Amiga HD files. I was able to copy the folders I've made earlier on Win-UAE.

The creation of these folders I already posted about in this post.

I prefer to use folders with the files in them instead of HD images. It's every easy to copy files to the emulated Amiga this way.

I created a A1200 folder in the path ~/Documents/FS-UAE/Hard Drives/ and I've put my folders containing my OS and Data volume in there. These paths can be selected from the launcher if you select the tab that has a HD image on it:




Let's start it up:


Nice. I am not too happy about the screen scaling though, but I had that problem on Windows as well.

Let's try an old game we've made somewhere in 199x (well, shamelessly cloned) :



Also working!

I had no sound. Turns out there was a setting in advanced settings that was I added when playing with the setting and had misconfigured. The solution was to change uae_sound_output = interrupts to uae_sound_output = normal.

This page helps with finding settings that can fix (or break :) ) your configuration:



Sunday, 1 May 2016

Koala Paint picture ripping

The Carelessness of Youth


I've made a lot of 8 bit pixel art in the 80's and 90's. This post will include some of these at the end, I hope you'll enjoy them.

For a lot of these pictures I've kept the original Koala Painter files, but quite a few of the files got lost due to my carelessness. So I was very surprised when someone uploaded a long lost demo of mine to the CSDB and it contained pictures I had forgotten all about.  And even better, the demo included the original Koala Painter files! Joy!

But then there are also demos out there for which I do not have the source file anymore. It's time to rectify that by using some hacking skillz, and retrieve the pictures from the demos!

This article will document the retrieval of one picture.

The Setup


We will be using VICE as this makes life so much easier. To view the koala files on PC, we use Droid64. It's a java based, multi-platform D64 file editor, but it also can show koala files! Really handy. It hasn't been updated for ages, but the program works just fine for my needs.

We start with looking at this demo: http://csdb.dk/release/?id=36209. It was made by the Supersonics and it contains a picture I've made for them using the Impossible Mission 2 advert.

The Mission Sonic demo by The Supersonics

So how to rip it? First we need to know how Koala Painter on the Commodore 64 works. It uses multi-colour bitmap mode.  By looking at the demo startup code, we should be able to deduce where in the Commodore 64 memory the data for the image resides.

After starting the demo and going to the 3rd part, I use CRTL-H to go into the VICE monitor.

Multi colour bitmap mode is set by setting two registers: $D011 and $D016. So lets hunt for code that uses these registers. We can forget about the range from $D000 to $FFFF:

(C:$30a8) hunt 0000 cfff d011
1018
3043
3046
310e
3144
8a44
8a62
b504

I find a list of possible locations.  Going through them I see this code at $3000 onward:

.C:3000  A9 00       LDA #$00
.C:3002  20 DC 19    JSR $19DC
.C:3005  AD 02 DD    LDA $DD02
.C:3008  09 03       ORA #$03
.C:300a  8D 02 DD    STA $DD02
.C:300d  AD 00 DD    LDA $DD00
.C:3010  29 FC       AND #$FC
.C:3012  09 02       ORA #$02
.C:3014  8D 00 DD    STA $DD00
.C:3017  A9 84       LDA #$84
.C:3019  8D 88 02    STA $0288
.C:301c  AD 18 D0    LDA $D018
.C:301f  29 0F       AND #$0F
.C:3021  09 10       ORA #$10
.C:3023  8D 18 D0    STA $D018
.C:3026  A9 0F       LDA #$0F
.C:3028  8D 20 D0    STA $D020
.C:302b  A9 0F       LDA #$0F
.C:302d  8D 21 D0    STA $D021
.C:3030  8D 86 02    STA $0286
.C:3033  A9 93       LDA #$93
.C:3035  20 D2 FF    JSR $FFD2
.C:3038  A9 08       LDA #$08
.C:303a  0D 18 D0    ORA $D018
.C:303d  8D 18 D0    STA $D018
.C:3040  A9 20       LDA #$20
.C:3042  0D 11 D0    ORA $D011
.C:3045  8D 11 D0    STA $D011
.C:3048  A9 10       LDA #$10
.C:304a  0D 16 D0    ORA $D016
.C:304d  8D 16 D0    STA $D016

Jackpot. Let's find out how the picture data is distributed. Demo coders sometimes mix up the layout of WHERE the data is kept. Sometimes this is done because of clashes with other code or perhaps a music file that uses the same space.

$DD00 controls where the VIC chip is looking for its 16K block of video RAM. Here are the lines that change that address:

.C:300d  AD 00 DD    LDA $DD00
.C:3010  29 FC       AND #$FC
.C:3012  09 02       ORA #$02
.C:3014  8D 00 DD    STA $DD00

It appears that VIC bank 2 is selected. This means that the base of the 16K of VIC RAM is located at $4000 and it runs to $7FFF. That's one part of the puzzle found. Now to find out where inside that range the bitmap and screen character layout is placed.

Note: multi-colour mode is weird: It uses the characters in the screen memory for additional colour information, in addition to the screen color memory at $D800.

To find the bitmap and colour information we need to look at $D018. Here is the code that modifies that address:

.C:301c  AD 18 D0    LDA $D018
.C:301f  29 0F       AND #$0F
.C:3021  09 10       ORA #$10
.C:3023  8D 18 D0    STA $D018

.C:3038  A9 08       LDA #$08
.C:303a  0D 18 D0    ORA $D018
.C:303d  8D 18 D0    STA $D018

$D018 is divided in two nibbles (4 bit elements). The most significant nibble controls where the VIC chip is looking for colour information. The least significant nibble tells the chip where to look for the actual bitmap data.

The first part of the code clears the most significant nibble.  Bit 4 is then set, being bit 0 in the nibble. Do you still follow me? We need to add 1K to the VIC base address for the number in this nibble. As the nibble now holds the value 1, the color data is located at $4000 + $400 = $4400.

The second part ensures that only bit 3 in the least significant nibble is set, meaning that the bitmap data is located on VIC base address + $2000 = $6000. Oh how do I know what those bits mean? C64 memory map FTW.

The Commodore 64 documentation tells us that bitmap data is 8000 bytes, colour data is 1000 bytes as that is all the characters that fit on the screen (40 lines * 25 rows)

We now know where the data is and how much there is:

Bitmap data: $6000 - $7f3f
Multi colour data: $4400 - $4800
Screen color: $d800 - $dc00

We can easily save this with the VICE monitor:

s "1.bitmap" 0 6000 7800
s "2.screen" 0 4400 4801
s "3.colour" 0 d800 dc00

Using 0 as a device will save the data to your PC drive. Handy!

Putting it all together


There is a nice article (linked at the beginning of this page) talking about Koala Painter. It also mentions how a Koala file is structured. It is really straightforward:

The Commodore 64 version of Koala Painter used a fairly simple file format corresponding directly to the way bitmapped graphics are handled on the computer: A two-byte load address, followed immediately by 8000 bytes of raw bitmap data, 1000 bytes of raw "Video Matrix" data, 1000 bytes of raw "Color RAM" data, and a one-byte Background Color field.

By looking at other Koala files using Droid64 I see that the first 2 bytes normally contain 00 and 60, meaning that the koala file is normally loaded to address $6000.

So let's write down where we need the data to go:

6000 - 6001  2 bytes, start address
6002 - 7f41  bitmap
7f42 - 8329  screen
832a - 8711  color
8712 - background color byte

I can tell you know that using this method gives the wrong results:



It looks like the colour data is shifted to the right, and it appear to be two positions. Bitmap data also does not look right. I got the suspicion that the 2 byte offset at the beginning was causing issues. Getting rid of the two bytes at the beginning results in this new table:

6000 - 7f3f - bitmap
7f40 - 8327 - screen
8328 - 870f - color
8710 - background color byte

We open VICE and enter the monitor and then we load the data back into the correct memory areas:

l "1.bitmap" 0 6000
l "2.screen" 0 7f40
l "3.colour" 0 8328

The background color information is located at the end of the file and we need light grey, so closing the monitor we type

POKE 34576,15

and we hit ENTER. Back into the monitor, we save the complete file to disk by using

s "koala" 0 6000 8712

Using Droid64, we view this file:

The new file, saved as PNG!

Success!!!!

I hope you liked this little hackery post. As a bonus for reading this all the way to the end, here are some other newly discovered 8 bit pixels:

Jean Michel Jarre, I think from the Oxygene LP back cover

8 bit heroes we all know

4x5 multi colour character set :)


Sunday, 13 March 2016

Tetris in 6502 Assembler - part 12

Hi there and welcome to a new part of this series. We are going to add sound!!

As usual, I remind you of the repository location for this game. You can get all the code and source from this location on GitHub.



Adding a SID file to the program source


Creating music and sound effects is an art I do not master, so it would've taken me a lot of time to create. I was really happy when I was offered assistance. Hedning from Genesis Project PMed me on CSDB and offered his assistance on getting me sounds for the game. Shortly thereafter I received a .SID file, made by Vanje Utne. You may know her from DeviantArt, and all kinds of demo's on the C64. Here is her page.  Many thanks to both of you!!

A .SID file is a standardized way of distributing sound files, programs and definitions. Because of the standardization they can be used in players, emulators and on real Commodore 64's. The HSVC collection consists of only SID files, so in theory I could have taken any song in that library and use it for this game. But I chose to have at least one original bit in this remake :)

First, we need to add the music file to our source code. Loading a .SID file into KickAssembler is easy, I added this to the file main.asm:

.var music = LoadSid("audio.sid")

The variable music now points to the data, and we can use it as an object, like in an object oriented language. Loading the SID file does not mean we can use it straight away. For this, we need to place its' data into memory. We use another KickAssembler macro to do that:

.pc = music.location
.fill music.size, music.getData(i)

We get the music location from the object, point the assembler to that location, and then we write that data. NOW we can use the SID file from our program.

Usually, a raster IRQ is set up to run the music independently from the program. The IRQ is called once a frame, the play subroutine is called, and then the main program is continued. As we are already calling the game code once a frame (remember that the main loop is waiting for a specific raster position) we don't need to setup an interrupt to run the music. Let's not complicate things when we don't have to. In the main loop, we add this at the end:

loopend:
jsr music.play
jmp loopstart

This will call the play routine each frame, at the end of the main loop. Now all we need to do is add the music track set definitions. I created a new file, called sound.asm and I put this in it:

// music definitions in sound file

.const SND_MOVE_BLOCK = 0
.const SND_ROTATE_BLOCK = 1
.const SND_DROP_BLOCK = 2
.const SND_LINE = 3
.const SND_TETRIS = 4
.const SND_PAUSE_ON = 5
.const SND_PAUSE_OFF = 6
.const SND_OPTION = 7
.const SND_MUSIC_TITLE = 9
.const SND_MUSIC_GAMEOVER = 8

We can play the required sound (or music) by calling the following subroutine:

// set accumulator before calling this
playsound:
        ldx #0
        ldy #0
        jsr music.init

How to use this: Load the accumulator with any of the defined values, and call this subroutine. The main loop call to music.play will take care of the rest.

Music


We start the music when a mode starts. For the title screen, the call...

lda #SND_MUSIC_TITLE
jsr playsound

...is added to attract.asm, into the subroutine StartAttractMode. Makes sense: start the music when this mode starts.  I really like working with modes.

Sound Effects


Sound effects need to be played when something happens, but where to add the calls? After some playing around, I settled for the file play.asm. Here is an example: After using a control, the correct sound is played:

doControls:
cpx #LEFT
bne !skipControl+
jsr BlockLeft

lda #SND_MOVE_BLOCK
jsr playsound

jmp doLogic

Above you see the first addition, but that file now plays sound for each action. Here is another example from the pause section of the same file. The correct sound is played for pausing or un-pausing the game:

TogglePause:
lda pauseFlag // get the current pause flag
eor #000001 // flip between 0 and 1
sta pauseFlag // store it

cmp #$01 // pause mode?
beq !skip+ // yes

lda #SND_PAUSE_OFF
jsr playsound

jmp RestorePlayArea         // no, restore the screen
!skip:
// game is paused. so clear the screen

lda #$01 // set the erase flag
sta playAreaErase // so area gets cleared as well
jsr SavePlayArea // save and clear the play area

lda #SND_PAUSE_ON
jsr playsound

jmp PrintPaused

I'm not going to add all the sound instructions I've added to the program but the principle should be clear now, right? :)

Concluding


Adding the sound and music isn't that hard, especially when you have someone with experience creating the sounds for you, haha!

There are still some small things that need fixing, like overlapping sound effects when hitting controls too fast, but overall I'm quite happy.

There is also something strange going on with timing when I use the joystick controls. This needs investigation, especially since I've moved my development system over to Linux Mint. I am running Linux Vice now and I need to be sure that the problem is not coming from changing Vice versions, or a configuration setting in the emulator...

We'll fix those things later on in another post. I wanted to get this post up because it has been a long time ago since the last part in the series.

So, until next time: happy coding, and check out the repository for the code changes and details.



Sunday, 28 February 2016

VICE on Linux Mint

A little simple blog post here to document for myself and maybe help you with emulating Commodore machines on a Linux machine.

I am migrating from Windows to Linux Mint 17.3, Cinnamon edition. A big part of the reason to do so is the lack of control over my own machine when I run Windows 10 and the constant information gathering from Microsoft.

A big thing is VICE: I need it :) Installing VICE is easy; it is located in the Linux software repositories, so run the software installer, search for vice and install it. Good to go? No. There is one snag: the installation does not include the ROM files needed to actually start the emulated machines.

A solution for this is to download the Windows version of VICE and then move the needed ROMs in the right folders.

First thing to do is to create a folder in your home folder, and call it .vice. In that folder you create another folder called C64. In that folder you move the following files from the Windows Vice archive: basic, chargen, kernal. These are located in the C64 folder in Windows Vice. Also copy the file d1541II from the DRIVES folder. The folder now looks like this:


You should now be able to start the C64. Adding a disk file should also work:


Excellent. The same thing can be done for the other machines. Apart from the C64 I use the C128, and here is the list of files you need to copy in a C128 folder in the .vice folder: basic64, basichi, basiclo, chargde, chargen, chargfe, chargse, kernal, kernal64, kernalde, kernalfi, kernalfr, kernalit, kernalno, kernalse.

Also copy the file d1571cr from the DRIVES folder. The C128 should now work:





Saturday, 5 December 2015

Tetris in 6502 Assembler - part 11

Welcome back people! In this part we will continue on our quest to complete a full game on the C64. 


Last post finished with me talking about the hi-scores, but I decided against doing that in this post. As we have most of the game play up and running now it's time to start working on the front end of the game. We will enable the game to cycle through an attract mode, after which a difficulty level can be selected and then the game is started.

A LOT has changed in the structure of the code while I was writing this post. Here are the changes:

  • Modified the input routines to set a control value instead of performing the controls themselves
  • Edited play.asm to read the control value and do the movement, also needed a rearrangement of the logic
  • Moved the block movement routines to blocks.asm
  • Added the attract mode screens
  • Modied the pause screen to use the new screen data movement
  • Added the difficulty select screen
This post does not contain all those changes because that would mean a massive post with only screenshots... And it's already a big post. So instead, I give you the highlights!

We need the new input control method to avoid rewriting these for the difficulty select screen updates... So again, a necessary change to standardise a bit of code.

As always: keep up to date with the code changes and additions in the GitHub respository available here: https://github.com/wiebow/tetris.c64

Input Changes


In order to re-use the input routines we need to make some changes. Instead of checking input and performing the action straight away, we make a change: the update routines will set a byte according to the detected input. This byte can be read by the requesting code. We reuse the keyboard values for the control codes.

This way we can use the same input routines and do different stuff with the results. The change to input.asm is simple:


inputResult is the byte that will hold the registered input. Block movement code is moved to blocks.asm and what is left is the simple routine shown above. We add the NOINPUT constant so we can also register when there was no input.

The joystick routine is slightly different. It sets the control code according to the direction pressed. We re-use the keyboard values as they now also represent input result. Here is the relevant bit from GetJoyInput:


The same is done for all the joystick bits we need to test. So now we can use the same input code in all the modes, and we have moved the block movement code to blocks.asm, where they belong. Great!

Attract Mode


Presentation is important and almost every arcade game out there has an attract mode. This is a tradition from the arcade hall era where a game needed to attract the players to the cabinet and make them want to play it, resulting in the insertion of a coin! I remember watching the Donkey Kong attract mode for ages. Heh.

The Nintendo Tetris version is showing a series of screens and it will be fun to replicate those. The attract mode consists of these screens (we will call them steps):
  • Title Screen
  • Credits
  • Controls
Char Pad is used to create the screens and we will add this data to the game in the same way as we did earlier for the play screen and game over text. When I created the game in 1992 I added a character set that replicated all the graphics from the Game Boy version. As I was able to unearth the set I have a head start! Here's the title screen I've made:


I decided not to put the Nintendo logo in there this time. These are different times you know :) We export it to .raw format and import it into main.asm:


Then, we create the attract.asm file and we define the steps, as well as a byte to hold a delay timer. Between each step there is a delay of 5 seconds. We know the game is updated 50 times per second. So if we decrement a timer value each update, then a start time of 50 is 'worth' one second. We need to do this 5 times. 250 fits nicely into a single byte:


With each step the next screen is printed. Re-writing the screen draw routine has been a good decision: we can easily draw this screen in the first step:


The attract mode also checks for the pressing of the RETURN key or the FIRE button. This check is now easy to incorporate as we changed the input.asm file to set the inputResult byte to the input that was detected:


attractStep is incremented and checked when the delay has passed. According to the step value, the screen to render is selected and performed. We repeat the same steps for the controls and credits screen. The cycle is repeated when 3 screens have been shown.

EndAttractMode sets up the new mode and starts it:


When we press a key or a joystick button in attract mode, the game goes to MODE_SELECTLEVEL
This is a simple check in the UpdateAttractMode routine.

Selecting Difficulty


That is the attract mode done. We can now start the game but before that we must be able to select the difficulty level. The game will display a screen with the levels. The player can use left and right to select the level he/she wants to play and then a hit fire or press return to start the game.

For this we create the level select screen shown below:


This .raw file is also imported in the main source file. We now have this complete list of screens:


Adding all this screen data causes assembly to go over $CFFF, resulting in a crash when running the program, so I moved the screen data to $4000, right behind the character set data:

Memory Map
----------
$3800-$3f3f character data
$4000-$4964 screen data
$c000-$cb23 code

And no worries, I will add the Char Pad project files to the repository as well.

We create levelselect.asm and start coding! StartLevelSelectMode sets up the screen and resets the essential values:


We also need a byte to hold a counter and we need to keep track of the previous level so we can make sure that we are able to redraw anything we leave behind when moving the level 'cursor'. FLASH_DELAY is a constant we define at the top of levelselect.asm.

We now need to define which screen locations correspond to which level. For this we create a lookup table with the X and Y positions of each level indicator:


We can easily select the x,y coordinates by using the currentLevel value as an offset. For example: level 4 digit is located on column 24, row 7:


The coordinates are read and the cursor is placed. After that, the flag is flipped and depending on the state a space is printed or the actual number.

All we now need to do is to read the input, and change the currentLevel value accordingly:


Some boundary checks are in there to stay within the allowed level values. As we change the location of the flashing number, we ensure that the previous number is drawn.

And we're almost done! We need to calculate the new drop delay for the game and print the current level into the screen when we start the game. This is done in the StartPlayMode function of play.asm:


AddLevel is called as many times needed. From here on the game can be played on the selected level.

Now we can start the game, watch an attract mode, start the game, select a level, and play.
We also have a game over screen. What's left is a high score entry screen and code, and then we have a complete game...

Conclusion


This was a hefty post, and a lot has been changed. But for the better: we have a complete game loop, and we did a lot of cleaning up in the code.

Next up is really about the high scores. That will be interesting: loading, saving, sorting scores and storing them in memory.

Until next time, happy coding!!