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.