Intel SDK-85 Computer Game
I was considering a new coding project for the Intel MCS-85 System Design Kit and I ended up creating a game that is utilizing the available hardware; 256 bytes of RAM, 6 digit display and hexadecimal keyboard.
The goal of the game is to enter the 4 digit random hexadecimal number that appears in the display before the timer runs out. Each successful entry will increment a score counter which is displayed when the game ends, which is whenever the timer runs out or a wrong number is entered. The "Go" button be used to try again. The speed of the timer countdown (the difficulty) can be adjusted by changing the input argument to the DELAY routine. The random number is generated with the help of a LFSR and it's seed can be changed to get another random sequence.
Here is the source code:
UPDAD equ $0363
UPDDT equ $036e
DELAY equ $05f1
OUTPT equ $02b7
RDKBD equ $02e7
org $2000 ; RAM Start.
start:
; Set stack pointer:
lxi sp,$20c2
lfsr_next:
; Step LFSR:
mvi c,8
mvi b,$bd
lfsr_step:
lxi h,lfsr_high
mov a,m
rlc
mov m,a
lxi h,lfsr_low
mov a,m
rar
mov m,a
jnc lfsr_nomask
lxi h,lfsr_high
mov a,m
xra b
mov m,a
lfsr_nomask:
dcr c
jnz lfsr_step
; Display LFSR value on address part of display:
lxi h,lfsr_low
mov d,m
lxi h,lfsr_high
mov e,m
call UPDAD
; Reset and begin countdown:
lxi h,countdown
mvi a,$ff
mov m,a
countdown_next:
; Display countdown on data part of display:
call UPDDT
; Make sure interrupts are enabled:
ei
mvi a,$08 ; Unmask all RST interrupts.
db $30 ; 8085 "SIM" instruction, use machine code directly for zmac assembler.
; Delay a while:
; $ffff = ~134 seconds
; $0fff = ~8.5 seconds
lxi d,$0fff ; Lower this to increase the difficulty!
call DELAY
; Non-blocking RDKBD:
lxi h,$20fe ; Input buffer.
mov a,m
ora a ; Check if MSB is set.
jp key_pressed
jmp countdown_decrement
key_pressed:
mvi m,$80 ; Set the empty flag.
push a ; Push pressed key onto the stack.
; Check if four stack pushes has occurred:
lxi h,$df44
dad sp ; Sets the carry flag if SP is higher than $20ba.
jnc got_four_keys
jmp countdown_decrement
got_four_keys:
lxi h,lfsr_high
check_byte:
; Check low nibble/digit:
mov a,m
ani $0f
mov b,a
pop a
cmp b
jnz end ; Wrong, game ends.
; Check high nibble/digit:
mov a,m
rrc
rrc
rrc
rrc
ani $0f
mov b,a
pop a
cmp b
jnz end ; Wrong, game ends.
; Check if four stack pops has occurred:
lxi h,$df3e
dad sp ; Sets the carry flag if SP is back at $20c2.
lxi h,lfsr_low
jnc check_byte
; All four digits correctly guessed, increase the score:
lxi h,score
inr m
jmp lfsr_next ; Proceed to next number.
; Continue countdown:
countdown_decrement:
lxi h,countdown
dcr m
mov a,m
jnz countdown_next
; Countdown timed out if got here, game ends.
end:
; Blank out address part of display.
mvi a,0
mvi b,0
lxi h,blank
call OUTPT
; Display score on data part of display:
lxi h,score
mov a,m
call UPDDT
; Clear the score:
lxi h,score
mvi a,0
mov m,a
; Wait for 'Go' key before starting:
wait_for_go:
call RDKBD
mvi b,$12 ; 'Go' key.
cmp b
jnz wait_for_go
jmp start ; This forces the stack pointer to get reset as well.
; Data area:
lfsr_low:
db $aa ; Low seed byte, change it to start with a different random number!
lfsr_high:
db $ee ; High seed byte, change it to start with a different random number!
countdown:
db $ff
score:
db $00
blank:
db $15
db $15
db $15
db $15
I ended up using the zmac assembler for this project, since it can also generate 8080 compatible code. A single 8085 specific instruction is used but that is specified directly as machine code.
Assemble the game like this:
zmac -8 game.asm --od . --oo cim,lst
Here is also the assembled machine code in hexadecimal for convenience:
31 C2 20 0E 08 06 BD 21 AC 20 7E 07 77 21 AB 20
7E 1F 77 D2 1C 20 21 AC 20 7E A8 77 0D C2 07 20
21 AB 20 56 21 AC 20 5E CD 63 03 21 AD 20 3E FF
77 CD 6E 03 FB 3E 08 30 11 FF 0F CD F1 05 21 FE
20 7E B7 F2 49 20 C3 80 20 36 80 F5 21 44 DF 39
D2 56 20 C3 80 20 21 AC 20 7E E6 0F 47 F1 B8 C2
88 20 7E 0F 0F 0F 0F E6 0F 47 F1 B8 C2 88 20 21
3E DF 39 21 AB 20 D2 59 20 21 AE 20 34 C3 03 20
21 AD 20 35 7E C2 31 20 3E 00 06 00 21 AF 20 CD
B7 02 21 AE 20 7E CD 6E 03 21 AE 20 3E 00 77 CD
E7 02 06 12 B8 C2 9F 20 C3 00 20 AA EE FF 00 15
15 15 15
This machine code can theoretically be entered manually using the SDK-85 monitor, but that will take quite a lot of time. An alternative solution is to start the board in serial mode and inject the program from an external PC, then switch to display/keyboard mode and run the game. This works since the contents of the RAM on the board is retained on a CPU reset.
Here is a C program made for Linux that can inject the binary file over the serial interface, but note that it requires a UART that can run at 110 baud:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
static unsigned char to_hex(int n)
{
switch (n) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
return n + 0x30;
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
return n + 0x37;
}
return '?';
}
static int transmit(int fd, unsigned char byte, unsigned char expect)
{
if (write(fd, &byte, 1) == -1) {
fprintf(stderr, "write() failed with errno: %d\n", errno);
return -1;
}
do {
if (read(fd, &byte, 1) != 1) {
fprintf(stderr, "read() failed with errno: %d\n", errno);
return -1;
}
putc(byte, stderr);
} while (byte != expect);
usleep(200000); /* Prevent overloading the target system. */
return 0;
}
int main(int argc, char *argv[])
{
int fd;
FILE *fh;
int c;
struct termios tios;
if (argc != 3) {
fprintf(stderr, "Usage: %s <file> <tty>\n", argv[0]);
return EXIT_FAILURE;
}
fd = open(argv[2], O_RDWR | O_NOCTTY);
if (fd == -1) {
fprintf(stderr, "open() failed with errno: %d\n", errno);
return EXIT_FAILURE;
}
if (tcgetattr(fd, &tios) == -1) {
fprintf(stderr, "tcgetattr() failed with errno: %d\n", errno);
close(fd);
return EXIT_FAILURE;
}
cfmakeraw(&tios);
cfsetispeed(&tios, B110);
cfsetospeed(&tios, B110);
tios.c_iflag |= ISTRIP; /* Strip off eighth bit. */
if (tcsetattr(fd, TCSANOW, &tios) == -1) {
fprintf(stderr, "tcsetattr() failed with errno: %d\n", errno);
close(fd);
return EXIT_FAILURE;
}
fh = fopen(argv[1], "rb");
if (fh == NULL) {
fprintf(stderr, "fopen() failed with errno: %d\n", errno);
close(fd);
return EXIT_FAILURE;
}
/* Initial check, should respond with a dot: */
transmit(fd, '\r', '.');
/* Send substitute memory command: */
transmit(fd, 'S', 'S');
transmit(fd, '2', '2');
transmit(fd, '0', '0');
transmit(fd, '0', '0');
transmit(fd, '0', '0');
transmit(fd, ' ', '-');
/* Send the data: */
while ((c = fgetc(fh)) != EOF) {
transmit(fd, to_hex(c / 16), to_hex(c / 16)); /* Wait for echo. */
transmit(fd, to_hex(c % 16), to_hex(c % 16)); /* Wait for echo. */
transmit(fd, ' ', '-'); /* Wait for next byte marker. */
}
/* Finish the command: */
transmit(fd, '\r', '.');
fclose(fh);
close(fd);
return EXIT_SUCCESS;
}
For development I used my Intel SDK-85 emulator which helps tremendously, and I added a new function to inject input in the display/keyboard mode so the assembled program can be tested quickly. I also restricted the RAM to 256 bytes which is standard on real hardware. These updates are present in version 0.2. The Git repository has also been updated.