Sunday, November 17, 2013

Serial port communication on Apple IIc.

Apple II was a very good and quite popular 8-bit machine in its time. It features a hardware UART for RS-232 port communication that can still be used today. Even from the BASIC language level, it is quite simple to make the use of that feature. I happen to own Apple IIc, which I did not own in the 80-s. I bought the set of the computer, monitor, printer and software on e-bay out of curiosity. I have to admit I also like the look of the little machine which was advertised as a portable Apple II. It would no pass the standards of portability today, however in the early to mid 1980-s it was enough if the computer had a carrying handle and/or some sort of a case. Apple IIc has an UART/RS-232 built in permanently as it does not feature the expansion bus as its bigger brother Apple II does.

The basics are as follows:

On the PC side setup port to 300 baud (default apple 2c speed), 8 bit, no parity, 1 stop bit, no flow control. Your terminal may or may not offer customization of the parameters listed below. I used the Hyper Terminal Private Edition. Other emulators may offer the customization of the listed below properties in the different form. Follow you terminal emulator's documentation:
Terminal setup: ANSI
ASCII setup: append line feed to incoming line ends, force incoming data to 7-bit ASCII.

Apple IIc side - issue command PR#2.

Now all the output goes via serial wire to a PC.

Alternative baud setup:

Apple 2c:

After PR#2 command, press CTRL-A and then the baud code:

CODE BAUD
1B   50
2B   75
3B   110
4B   135
5B   150
6B   300
7B   600
8B   1200
9B   1800
10B  2400
11B  3600
12B  4800
13B  7200
14B  9600
15B  19200
16B  115k

To setup the serial port speed from basic, use print chr$(1) followed by the speed code, e.g:

10 pr# 2
15 print chr$(1);"15b"
20 print "hello world!"
30 goto 20

Above program will redirect output to serial port and setup transmission speed to 19200 baud.

PC side - follow manual of your terminal emulator software to match the baud rate of the Apple 2c.

The automatic line trace in BASIC is automatically on (seems to be a feature) when the output is redirected to a serial line. Perhaps because such mode of working was intended for development/debugging purpose. Anyway, it is good to include NOTRACE just after PR#2 in your program to turn this feature off. There are several other (than baud rate) parameters that can be setup for serial port after IN#2 or PR#2. Just use PRINT CHR$(1);"{command_code}" to set them up.
Here are the command codes:

Data format
Code Data Length Stop bits
0D   8           1
1D   7           1
2D   6           1
3D   5           1
4D   8           2
5D   7           2
6D   6           2
7D   5           2

ASCII setup:
Code Effect
I echo output to the screen
K do not send line feed after carriage return
L send line feed after carriage return

Parity:
Code Parity Check Mode
0P   none
1P   odd
2P   none
3P   even
4P   none
5P   MARK(1)
6P   none
7P   SPACE(0)

Example BASIC program setting up 9600 baud, 8-N-1, send LF after CR:

20 PR#2
30 NOTRACE
40 PRINT CHR$(1);"14b"
50 PRINT CHR$(1);"0d"
60 PRINT CHR$(1);"0p"
70 PRINT CHR$(1);"l":rem it is small "L".

For some reason, I have to setup 7 bit data length, 1 stop bit on the PC side, even though Apple works in 8-1 mode. I will have to look into this later, for now I don't understand why the discrepancy.

On Linux (R-Pi) I used 'putty' or just plain "cat /dev/ttyUSB0" to receive data from apple.

To redirect input and output to serial port, so you can work on apple from remote terminal, type on apple:

IN#2
{CTRL-A)14b
PR#2

From this moment, whatever you type in the terminal emulator on the PC workstation is really happening on Apple. The PC workstation acts a a dumb terminal for the Apple computer. Pretty cool. You can actually create a boot diskette for the Apple to load up and execute all necessary serial port initialization commands at the boot time and with a PC/laptop connected to the Apple via serial port (or USB with USB to serial converter) you do not need a display monitor with the Apple computer. This way of working with Apple II of course may have some limitations, which I am sure user would discover very quickly. However it is possible to work with BASIC and text based programs in this manner.

Happy retro-computing!

Thank you for reading.


Marek Karcz, 11/17/2013

Saturday, November 16, 2013

Apple IIc as a dumb text terminal.

Using you old computer hardware with modern equipment.

If you happen to have Apple II (IIc) computer and you think it is just taking space, do not put it in the trash, sell or donate just yet. The thing is, you own it because perhaps you purchased it long time ago, or got it as a gift and at some time liked it. It gave you hours of fun and helped to introduce you into the computing world. Whatever the reason, it may make your heart heavy to let it go. Well, you can still use it! Consider this - Apple IIc has a good serial port hardware driver and can be easily interfaced with modern equipment that is also equipped with a serial or USB ports (with USB to serial converter). All that is needed on the Apple side is a terminal emulation software and a serial null-modem cable adaptor that converts the Apple's mini-din to the DB-9 connector seen on modern PC computers. All the information I needed in order to turn my Apple IIc into the serial dumb terminal I found here:


and here:


First I prepared 3 blank floppy disks for Modem MGR software. I used AppleWorks software which I happen to have purchased with my Apple IIc. To transfer the Modem MGR images to Apple, you need ADT Pro client on the Apple side and a server on the PC side. Both are available on the internet as well as detailed instructions how to install and configure them, so I will not duplicate it here. Just check out the web site:


After the transfer of Modem MGR software, I had 3 disks: Installation, Utilities and Work. First I inserted and booted the Installation disk on my Apple and follow the instructions. I configured the software to work in 80-column mode (menu option 2) video driver, non-smart modem (menu option 10) and saved the configuration back to the work disk.
Next I connected the Apple's serial port to my DSL (Damn Small Linux) server. The server runs a script:

#!/bin/bash

while true
do
/sbin/getty -L 9600 ttyS1 vt100
done

The connection via standard serial cable and the mini-din to DB-9 adapter requires no null-modem connector, since the mini-din to DB-9 adapter cable is already a NULL modem. However I used the null modem serial cable and wondered for some time why this did not work. Then I had to add extra null-modem adapter.

Once computers were connected, I booted Modem MGR work disk on the Apple. It displays a bunch of options that can be accessed by pressing ESC key followed by the letter. At any time, I can press ESC and Shift-? to re-display the list of available options.

The first step is to setup the modem baud rate, which in my case is 9600. Next, the data format - number of bits, the parity and the number of stop bits: 8-N-1. The last step is to enter the Modem MGR into the terminal emulation mode, which is achieved by pressing ESC and ':' (colon) and then pressing V for loading the VT220 terminal emulation mode. Then I pressed enter and voila! - the login prompt appeared. Even though on the server side 'getty' runs in vt100 mode, I experienced no issues. This of course is easily adjusted on the server side.


Pictures:

Image 1: Transferring Modem MGR images to Apple 2c diskettes - client side.

Image 2: Transferring disk images - server side.


Image 3: A working text terminal emulator on Apple IIc.

Image 4: The output from 'top' command - it ran well and there were no emulation issues.
It was a fun little project. Now whenever I want to  have retro computing experience I can connect to my DSL server with Apple IIc and run Z-machine emulator to play one of the Infocom's text adventures.

Thank you for reading.

Marek Karcz, 11/16/2013

Saturday, September 14, 2013

Conway's Game Of Life on a Commodore 64.

I remember when I learned for the 1st time about Conway's Game Of Life – a Cellular Automata model in a Polish equivalent of Popular Mechanics for youth: “Mlody Technik” (Young Technician) in 1984. I have been at the time a technical high school freshman. I immediately recognized how great a simulation platform a computer would be for this model. Unfortunately I did not have a computer or even an access to one. I spent long hours simulating colonies on a piece of a graph paper.
In the early 2000-s when I was learning C#/.NET I did a C# implementation as an exercise. The GUI is a bit awkward, but simulation is fairly fast and can emulate quite big arrays of the cells. My son loves to play with this program even though I never had time to refine it or get rid of few bugs. It has edit/load/save abilities, user can set, unset cells with a mouse click, zoom in and out and enable grid lines. My 9-year old has no problem with that, so I guess everybody could get used to it.

Modern GUI C# implementation of Game of Life.
C# Game of Life - program setup.
C# Game of Life - with elevated zoom and grid lines enabled for easy editing.
In the meantime I made numerous console based ports in C language for DOS and Linux. It is always fun. Now, while this is quite easy with modern computers and programming languages, it is not the same on some old slow CPU and limited memory platform using low level machine language or slow interpreted language like BASIC for example.
Recently I realized that I have never did a C-64 port. Being a Commodore aficionado, I found this a great oversight, so I got to work.
My first tries using BASIC and Power-C for C-64 and C-128 were disappointing. My algorithm was very slow. I realize that with some clever coding I could do a fast simulation in higher level language. Perhaps one day I will try to invent some clever GOL simulation algorithm in C which will be fast enough for practical application. Even though I like to work directly on a retro hardware, (emulator does not emulate look and feel), this time I wanted to try the cross-platform IDE called C64 Studio (I love the demo helicopter game that comes with it). I installed it, made sure it points to my installation of Vice emulator and got to work. I researched some GOL implementations for CBM platform on the internet and have been impressed by this great piece of 6502/C64 code by 'Ruk':

Although Ruk's goal was minimum size and maximum speed, so the program is not really packed with features, it is still very impressive what you can do with this ancient by today's standard hardware. He does some pretty clever encoding of the colony so the calculating of the next generation is real fast and code is extremely compact. It fits in less than 256 bytes with full screen emulation/presentation and sound.
I did not match Ruk's implementation of GOL speed and tiny size, however these were not my goals (well, maybe the speed was, but I lack the skill at the moment). Instead my program is packed with practical features and makes up for slower speed and (much) bigger code size with numerous functions like: built-in demo colonies, random colony generator, colony full screen screen editor and disk I/O. Although program takes 16 blocks on a disk, the colony files are only one block in size (binary encoded, 40*25/8 = 125 bytes is all it takes to encode 1000-cell colony) and save and load pretty fast. The simulation speed is also satisfactory with about 2 ¼ generations per second.

Conway's Game Of Life – the rules.

The world of Game of Life (GOL) consists of a 2-dimensional array of cells, which can have a dot (live cell) or be empty. The next generation of dots is calculated by counting the live neighbors on adjacent cells. E.g: please refer to illustration below, where the central cell is live (has a dot) and has 8 adjacent cells, which contain a total of 3 live neighbors.

A cell with adjacent cells.
The rules of death and life are as follows:

- Death -
  • Each dot that has 1 or no live neighbors dies out of loneliness.
  • Each dot that has 4 or more live neighbors, dies out of the overcrowding.
- Life -
  • On each empty cell that has exactly 3 live neighbors, a new dot is born.
  • Each dot that has 2 or 3 live neighbors, remains in its live status.


Computer algorithm.

On today's fast hardware, the directly implemented algorithm causes no speed concerns. The cells can be encoded in bit fields or directly in a BOOLEAN or character array. The cells of the arrays are iterated sequentially and the neighbors number is calculated for each cell separately and the next generation of the colony can be generated to the separate memory buffer and then presented in whatever form to the user.
However working with the 1 MHz 8-bit CPU with limited RAM is different. There are memory and performance considerations. We want to avoid too much of the data copying operations (internal colony representation vs. screen presentation, copying data between buffers etc.) and really huge arrays. In my implementation I decided to compute the next generation directly in the text screen memory (the world is a 1000 cells array, 25 rows 40 columns each). To avoid artifacts on the screen while the next generation is calculated, double buffering technique could be used. However I chose a technique to encode the screen codes for dying and born cells of the next generation to be as closely represented on the screen to the live and empty cells of a current generation as possible. For example the dot (live cell) is presented with screen code $51, which is a big filled oval dot. The dying cell is represented with code $57, which is a very similar semi-graphical character to the one represented by code $51, except it just has a small not filled with color hole inside (donut like) while remaining the same size. Therefore when the next generation calculating algorithm is marking the cells for dying, their screen presentation do not change significantly. Just a little hole appears briefly in the middle of each dying cell, which has a nice side effect for the presentation function anyway. Even better screen code match was found for a new-born cell. Note that it is not desired to have new born dots appear on the screen before the whole array is calculated. The screen code $60 is represented on the screen as empty space, just as the empty cell is represented by code $20, also resulting with an empty space on the screen. For the computer algorithm these are 2 different codes (desirable feature). However for the user, there is no change on the screen when the algorithm marks cells where a new dot is born in the next generation. Thanks to that I avoided necessity of performing double buffering and switching screen memory banks. After next generation is calculated, quick routine converts the died and new born screen codes to the empty and dot codes (fully filled) and the next generation is again calculated in the endless loop. The end-user presentation in effect is quite pleasant and appears as a seamless generation after generation display.

Apparently the assembly routine calculating the next generation of the colony was the most challenging for me. The performance of the simulation depends on it the most. I used direct GOL rules implementation with few tricks and optimization techniques. The resulting binary image is under 4 kB in size. Considering the amount of features packed, I consider this a moderate achievement. Thanks to the speed of the machine language, I achieved satisfactory performance without affecting the readability of the code or necessity to use advanced colony encoding techniques. Although it is my desire in the future to do just that and make the code blazing fast, for now I will settle for 2 generations per second speed as I need some more 6502 assembly language practice to achieve better results in code.
Zero page indexing addressing mode and various counters are used by the algorithm to cycle through screen memory cells from left upper corner to right-lower corner of the screen (YIND – column counter is used to iterate through cells in a row and then ZPG vector is increased by 40 and YIND zeroed at the beginning of each next row).

Here is the routine which is the heart of the GOL simulation algorithm:

; calculate the next generation    
; Theory of operation:
; The screen memory is scanned from start to end.
; Each row is iterated with pointer ZPGA1 and index Y.
; Addressing mode (ZPGA1),Y is used. Register Y is
; reloaded with column counter yind, which is incremented
; accordingly at the end of the loop,
; then ZPGA1 is increased by 40 at the start of the next row.
; The number of neighbors is calculated for each cell.
; Depending on the # of neighbors, the cell is encoded
; to either die (DCELL), be born (BCELL) or remain unchanged.
; After this procedure, the 'disp' routine should be called
; to convert died cells to empty spaces and born cells (BCELL)
; to live cells (LCELL).
next
    ldx #0
    stx xind  ; initialize row index
    stx yind  ; initialize column index
    stx nbct  ; initialize neighbor cells counter
    lda #<MSCR  ; initialize ZPG address buffer
    sta ZPGA1   ; and current cell address counter
    sta smct
    lda #>MSCR
    sta ZPGA1+1
    sta smct+1
nl0 ; left neighbor check    
    ldy yind
    beq nl1         ; skip left neighbor check if column=0
    dey             ; decrement ZPGA1 pointer (left side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl000
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl00        ; jump to nl00
nl000    
    inc nbct        ; increment neighbors count
nl00    
    ldy yind
nl1 ; right neighbor check
    cpy #MAXCOL     ; if cell at the far right side
    beq nl2         ; then skip right neighbor check
    iny             ; increment ZPGA1 pointer (right side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl111
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl2        ; jump to nl2
nl111    
    inc nbct        ; increment neighbors count
nl2 ; north neighbors check
    lda xind
    beq nl4         ; skip north neighbors check if row=0
    jsr sb40zpga1   ; subtract 40 from ZPGA1 (north neighbor)
    ldy yind
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl222
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl22        ; jump to nl22
nl222    
    inc nbct        ; increment neighbors count
nl22  ; north-west (NW) neighbor check
    ldy yind
    beq nl3         ; skip NW neighbor check if column=0
    dey             ; decrement ZPGA1 pointer (NW side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl2220
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl220       ; jump to nl220
nl2220    
    inc nbct        ; increment neighbors count
nl220
    iny             ; return ZPGA1 pointer to north neighbor's cell
nl3
    cpy #MAXCOL     ; if cell at the far right side
    beq nl33        ; then skip the right neighbor check
    iny             ; increment ZPGA1 (right side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl333
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl33        ; jump to nl33
nl333    
    inc nbct        ; increment neighbors count
nl33
    jsr rldsm2zpga1 ; reload smct to ZPGA1
nl4 ; south neighbor check
    lda xind
    cmp #MAXROW     ; if row at maximum,
    beq nl6         ; skip south neighbors check
    jsr add40zpga1  ; add 40 to ZPGA1 (now ZPGA1+Y -> south neighbor's cell)
    ldy yind
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl444
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl44        ; jump to nl44
nl444    
    inc nbct        ; increment neighbors count
nl44  ; south-west (SW) neighbor check
    ldy yind
    beq nl5         ; skip SW neighbor check if column=0
    dey             ; decrement ZPGA1 (SW side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl4440
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl440       ; jump to nl440
nl4440    
    inc nbct        ; increment neighbors count
nl440
    iny             ; return ZPGA1 pointer to south neighbor's cell
nl5
    cpy #MAXCOL     ; if cell at the far right side
    beq nl6         ; then skip the right neighbor check
    iny             ; increment ZPGA1 (SE side neighbor)
    lda (ZPGA1),y   ; load cell's value
    cmp #LCELL      ; if not a live cell
    beq nl55
    cmp #DCELL      ; or not a 'died' cell (in this iteration)
    bne nl6         ; jump to nl6
nl55    
    inc nbct        ; increment neighbors count    
nl6
    jsr rldsm2zpga1 ; reload smct to ZPGA1 (restore address of the row)
    lda yind        ; store old column# before incrementing
    sta yold        ; for further calculations
    inc yind        ; increment column#
    lda yind
    cmp #(MAXCOL+1)
    bne nl66
    lda #0
    sta yind
    jsr add40smct   ; increment smct (point to the next row of cells)
    inc xind        ; increment row#
nl66  ; now mark the cells to live/die/be born
    ldy yold
    lda (ZPGA1),y   ; load current cell
    cmp #LCELL      ; check if a live one
    beq nl666
    cmp #DCELL
    bne nl7         ; not a live one, jump to nl7
nl666 ; live cell, determine its fate
    lda nbct        ; load neighbors counter
    cmp #0          ; if nbct<2, cell dies
    beq nldie
    cmp #1
    beq nldie
    cmp #2
    beq nlnext
    cmp #3          ; if nbct==3, cell does not change
    beq nlnext      ; if nbct>3, cell dies
nldie
    lda #DCELL
    ldy yold
    sta (ZPGA1),y   ; update cell with 'died' code
    clc
    bcc nlnext      ; prepare for next cell
nl7 ; not a live cell, determine its fate
    lda nbct        ; load neighbors counter
    cmp #3          ; only if nbct==3, a new one is born
    bne nlnext
    lda #BCELL
    ldy yold
    sta (ZPGA1),y   ; update cell with 'born' status.
nlnext
    lda xind        ; check if the end
    cmp #(MAXROW+1)
    beq nlend       ; yes, jump to nlend
    jsr rldsm2zpga1 ; reload smct to ZPGA1
    lda #0          ; initialize neighbors counter
    sta nbct
    jmp nl0         ; do the next cell
nlend

    rts

Before saving, colony's editor screen is encoded into binary form (so each byte holds information about 8 cells) into buffer with this routine:

; convert screen (editor's format) into binary format
; store in colb buffer
encodeed2colb
    lda #<MSCR
    sta ZPGA1
    lda #>MSCR
    sta ZPGA1+1
    lda #0
    sta gpct        ; gpct - encoded byte
    sta gpct+1      ; index to colb buffer (encoded colony)
    lda #8
    sta gpbyt       ; gpbyt - bit counter
ee2cloop000
    ldy #0
    lda (ZPGA1),y   ; load byte from screen memory
    cmp #'*'        ; is it live cell?
    bne ee2cloop002 ; no, branch to ee2cloop002
    lda gpct        ; load encoded byte to Acc
    ldx gpbyt       ; load bit counter to X
    ora encodearr,x ; set the bit in Acc masked by encodearr,X
    sta gpct        ; store updated encoded byte
ee2cloop002    
    dec gpbyt       ; decrement bit counter
    lda gpbyt
    beq ee2cloop001 ; if byte counter is 0, branch to ee2cloop001
ee2cloop000a    
    jsr SystemZone.inczpga1 ; increment ZPG1 (screen memory)
    lda ZPGA1       ; check if end of screen memory
    cmp #<MESCR
    bne ee2cloop000
    lda ZPGA1+1
    cmp #>MESCR
    beq ee2cfinished
    clc
    bcc ee2cloop000 ; do next bit
ee2cloop001         ; single byte encoding finished
    ldx gpct+1      ; load encoded colony buffer index
    lda gpct        ; load encoded byte
    sta colb,x      ; store byte in the encoded colony buffer
    inc gpct+1      ; increment index to encoded colony buffer
    lda #8          ; reload bit counter
    sta gpbyt
    lda #0          ; reset encoded byte
    sta gpct
    clc
    bcc ee2cloop000a  ; do the next bit
ee2cfinished

    rts

Here is how the encoded binary format colony look like, this example is a built in demo colony:

; shooter colony, binary encoded    
dcshootenc    

    !byte 100    ; first byte is the number of encoded bytes that follow
    ; encoded colony
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B11000000,B00011000
    !text B00000000,B00000000,B00000001,B01000000,B00011000
    !text B01100000,B00110000,B00000001,B10000000,B00000000
    !text B01100000,B01010000,B00000000,B00000000,B00000000
    !text B00000000,B01100000,B01100000,B00000000,B00000000
    !text B00000000,B00000000,B01010000,B00000000,B00000000
    !text B00000000,B00000000,B01000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00001100
    !text B00000000,B00000000,B00000000,B00000000,B00001010
    !text B00000000,B00000000,B00000000,B00000000,B00001000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B00000000,B00000000
    !text B00000000,B00000000,B00000000,B01110000,B00000000
    !text B00000000,B00000000,B00000000,B01000000,B00000000

    !text B00000000,B00000000,B00000000,B00100000,B00000000


Features and presentation.

After the welcome screen, user presses a key and the help screen is displayed showing keyboard shortcuts to the functions of the program. Due to the presentation environment being text based, that information is not present on the screen during colony editing or simulation, therefore it is a good idea to read and memorize this screen or print it before proceeding.

Game of Life simulator - help screen.
In the next step, user is presented with the program's start menu. Possible choices consist of two template/demo colonies, ability to start with an empty editor or editor filled with random colony. There is also a load from file option available and option to terminate the application (confirmation is required).

Game of Life - start menu.
Depending on the user's choice, the editor starts filled with the colony or empty. Editor encodes the empty and life cells differently than the simulator. The empty cells are represented by minus '-' signs, while the life cells are represented with asterisks '*'. This approach has 2 advantages:

  1. User can recognize immediately in what mode the application is currently working (remember – there is no extra text information, the whole screen is taken by colony data presentation).
  2. The empty cells done with minus sign present a grid which makes it easier for user to design/modify the colony.
Game of Life - editor screen filled with random colony.
In editor, user can position the cursor (visible as a reverse color rectangle) using cursor keys on the keyboard and alter the cell status with space key. Other possible functions are 'H' to display help screen, 'C' to clear all the cells (requires confirmation), 'R' to fill cells with random values (requires confirmation) and 'Q' to exit the editor and start simulation. At the exit, user is prompted to save the colony to file, which can be skipped.

Game of Life - exit editor, save to file prompt.
If user chooses to save the colony to file, a prompt appears to enter the colony file name.

Game of Life - save colony, file name prompt.
The simple file name editor allows to enter alpha-numeric string of up to 12 characters in length. User can press DEL key to remove incorrectly entered characters. If the entered file name is empty (RETURN key pressed with the cursor positioned at the 1st character of the file name), the Save operation is considered as canceled and program proceeds to simulation. Otherwise, the program attempts to save the colony to file. In case of I/O error, there is no action. Extension “.COL” is added to the file name automatically.

Simulator starts in paused mode. User can immediately see that program entered simulation mode because the cells are now presented differently on the screen (Illustration below).

Game of Life – paused simulator.
Pressing any key starts the simulation. Please note the donut style cells (see Illustration below), which are the cells marked to die in the next generation by the algorithm. Newborn dots are not visible until the next generation is fully created and the screen codes converted to the full dots or spaces accordingly.
(NOTE: on the screen shot below, not all the cells that are supposed to die in the next generation are yet marked like donut, because the screen shot captured the screen in the middle of the process)

Game of Life - running simulation.
At any moment during simulation user can choose to quit application (requires confirmation), pause it, go to editor and alter/save the colony, display the help screen or go back to start menu (requires confirmation) using the keyboard shortcuts presented on the help screen at the program start up.

Game of Life - quitting the application.
Game of Life - the fun is over.

Here is the link to source code. Look for a ZIP archive named ConwaysLife.
Thank you for visiting.

Marek Karcz
9/15/2013.

Sunday, February 3, 2013

IRQ sprite clock for Commodore-64.

Today I'd like to share with Commodore community this little piece I made back in the 1990-s - an IRQ sprite clock for Commodore-64.


Clock works in IRQ interrupt. Presentation is made with sprites (clock digits are mapped to sprite objects and displayed on screen). It is possible to use C-64 while this clock works. Oddly enough, this clock seems to be interfering with I/O while running on real hardware (unable to use disk drive or tape, will hung) but works fine in emulator. If you find the bug, please let me know!


; SPRITE IRQ CLOCK (C) Marek Karcz
; Code below compiles with CC65 assembler:
; cl65 --verbose --listing spriteclock.asm
; Use HEX editor to insert "00 C0" at the beginning
; of the executable to be able to run it on C64 or
; in emulator.
; Usage:
; LOAD"SPRITECLOCK",8,1
; CLR:NEW
; SYS49152,"HHMMSS"
; Where:
;     HH - hours, MM - minutes, SS - seconds
; To turn the clock off:
; SYS49152

VIC      = 53248 ;VIC Base
WSKSP0   = 2040  ;PTR. SPRITE 0
WSKSP1   = 2041  ;PTR. SPRITE 1
WSKSP2   = 2042  ;PTR. SPRITE 2
BLOK13   = 832   ;PTR. BEG. OF BLOCK 13
BLOK14   = 896   ;PTR. BEG. OF BLOCK 14
BLOK15   = 960   ;SEE ABOVE - BLOCK 15
IRQWKT   = 788   ;IRQ VECT.
POBZNA   = 121   ;READ CURR. BYTE
PRZECT   = $AEFD ;CHECK COMMA
ANAWYR   = 44446 ;PARSE EXPR.
POBTXT   = 46978 ;GET STRING

         .ORG $C000

         JMP STEROW
LICZNIK:  .BYTE 60                   ;COUNTER
WZORCE:   .BYTE 2,3,5,9,5,9          ;TEMPLATES
CZAS:     .BYTE 1,2,0,0,0,0          ;TIME
LICZPOZ:  .BYTE 1                    ;POS. COUNTER
CYF0:     .BYTE 0,252,204,204        ;DIGIT 0
          .BYTE 204,204,204,252
CYF1:     .BYTE 0,12,12,12           ;DIGIT 1
          .BYTE 12,12,12,12
CYF2:     .BYTE 0,252,12,12          ;DIGIT 2, etc...
          .BYTE 252,192,192,252
CYF3:     .BYTE 0,252,12,12
          .BYTE 60,12,12,252
CYF4:     .BYTE 0,204,204,204
          .BYTE 252,12,12,12
CYF5:     .BYTE 0,252,192,252
          .BYTE 12,12,12,252
CYF6:     .BYTE 0,252,192,192
          .BYTE 252,204,204,252
CYF7:     .BYTE 0,252,12,24
          .BYTE 48,96,192,192
CYF8:     .BYTE 0,252,204,204
          .BYTE 252,204,204,252
CYF9:     .BYTE 0,252,204,204
          .BYTE 252,12,12,252
DWUKR:    .BYTE 0,0,48,48            ;COLON
          .BYTE 0,48,48,0
COPY:    LDX #$00
         LDY #$00
LOOP1:   LDA CYF0,X
BLKNR:   STA BLOK13,Y
         CPX #7
         BEQ POZ
         INY
         INY
         INY
         INX
         JMP LOOP1
POZ:     RTS
IRQROM:  JMP $EA31
MAIN:    DEC LICZNIK
         BNE IRQROM
         LDA #60
         STA LICZNIK
         LDA CZAS
         LDX #3
         CMP #2
         BEQ NOC
         LDX #9
NOC:     STX WZORCE+1
         LDX #5
NXTCYF:  LDA CZAS,X
         INC CZAS,X
         CMP WZORCE,X
         BNE KOP1
         LDA #$00
         STA CZAS,X
         DEX
         BPL NXTCYF
KOP1:    LDA #$00
         STA LICZPOZ
         LDX #<CZAS
         LDY #>CZAS
         STX KOPIUJ+1
         STY KOPIUJ+3
         LDX #<BLOK13
         LDY #>BLOK13
         STX POS0B+1
         STY POS0C+1
KOPIUJ:  LDX #<CZAS
         LDY #>CZAS
         STX POS0+1
         STY POS0+2
POS0:    LDA CZAS
         CMP #$00
         BNE NIE00
         LDX #<CYF0
         LDY #>CYF0
POS0A:   STX LOOP1+1
         STY LOOP1+2
POS0B:   LDX #<BLOK13
POS0C:   LDY #>BLOK13
         STX BLKNR+1
         STY BLKNR+2
         JSR COPY
         JMP POS1
NIE00:   CMP #$01
         BNE NIE01
         LDX #<CYF1
         LDY #>CYF1
         JMP POS0A
NIE01:   CMP #$02
         BNE NIE02
         LDX #<CYF2
         LDY #>CYF2
         JMP POS0A
NIE02:   CMP #$03
         BNE NIE03
         LDX #<CYF3
         LDY #>CYF3
         JMP POS0A
NIE03:   CMP #$04
         BNE NIE04
         LDX #<CYF4
         LDY #>CYF4
         JMP POS0A
NIE04:   CMP #$05
         BNE NIE05
         LDX #<CYF5
         LDY #>CYF5
         JMP POS0A
NIE05:   CMP #$06
         BNE NIE06
         LDX #<CYF6
         LDY #>CYF6
         JMP POS0A
NIE06:   CMP #$07
         BNE NIE07
         LDX #<CYF7
         LDY #>CYF7
         JMP POS0A
NIE07:   CMP #$08
         BNE NIE08
         LDX #<CYF8
         LDY #>CYF8
         JMP POS0A
NIE08:   LDX #<CYF9
         LDY #>CYF9
         JMP POS0A
POS1:    INC KOPIUJ+1
         LDA KOPIUJ+1
         CMP #$00
         BNE POS1A
         INC KOPIUJ+3
POS1A:   INC LICZPOZ
         LDA LICZPOZ
         CMP #1
         BNE POS1B
         LDX #<(BLOK13+1)
         LDY #>(BLOK13+1)
POS1A1:  STX POS0B+1
         STY POS0C+1
         JMP KOPIUJ
POS1B:   CMP #2
         BNE POS1C
         LDX #<BLOK14
         LDY #>BLOK14
         JMP POS1A1
POS1C:   CMP #3
         BNE POS1D
         LDX #<(BLOK14+1)
         LDY #>(BLOK14+1)
         JMP POS1A1
POS1D:   CMP #4
         BNE POS1E
         LDX #<BLOK15
         LDY #>BLOK15
         JMP POS1A1
POS1E:   CMP #5
         BNE POS1F
         LDX #<(BLOK15+1)
         LDY #>(BLOK15+1)
         JMP POS1A1
POS1F:   JMP $EA31
START:   LDX #$00
         LDA #$00
STLOOP:  STA BLOK13,X
         STA BLOK14,X
         STA BLOK15,X
         INX
         CPX #63
         BEQ ST1
         JMP STLOOP
ST1:     LDX #$00
         LDY #$00
ST1LOOP: LDA DWUKR,X
         STA BLOK13+2,Y
         STA BLOK14+2,Y
         INX
         CPX #8
         BEQ ST2
         INY
         INY
         INY
         JMP ST1LOOP
ST2:     LDX #13
         STX WSKSP0
         INX
         STX WSKSP1
         INX
         STX WSKSP2
KOLOR:   LDA #13
         STA VIC+39
         STA VIC+40
         STA VIC+41
         LDA #000110
         STA VIC+16
         LDA #215
         LDX #50
         STA VIC
         STX VIC+1
         LDA #7
         STA VIC+2
         STX VIC+3
         ADC #48
         STA VIC+4
         STX VIC+5
         LDA #000111
         STA VIC+23
         STA VIC+29
         STA VIC+27
         STA VIC+21
         LDX #<MAIN
         LDY #>MAIN
         SEI
         STX IRQWKT
         STY IRQWKT+1
         CLI
         RTS
STEROW:  JSR POBZNA
         BEQ WYLACZ
         JSR PRZECT
         JSR ANAWYR
         ROL 13
         BCS NASTAW
ILLQUA:  JMP 47000
NASTAW:  JSR POBTXT
         CMP #6
         BNE ILLQUA
         LDY #5
         SEI
NASTPE:  LDA (34),Y
         SEC
         SBC #48
         STA CZAS,Y
         DEY
         BPL NASTPE
         LDA #60
         STA LICZNIK
         CLI
         JMP START
WYLACZ:  LDX #$31
         LDY #$EA
         SEI
         STX IRQWKT
         STY IRQWKT+1
         CLI
         LDA #$00
         STA VIC+21
         RTS

In the next episode I will present a BASIC program that uses above clock to sound an alarm (sound and visual effects) at predefined time.

Thanks for reading.

Marek Karcz
2/3/2013