381 lines
9.8 KiB
Text
381 lines
9.8 KiB
Text
;;; Flip the Bits game, CP/M version.
|
|
;;; CP/M zero page locations
|
|
cmdln: equ 80h
|
|
;;; CP/M system calls
|
|
getch: equ 1h
|
|
putch: equ 2h
|
|
rawio: equ 6h
|
|
puts: equ 9h
|
|
org 100h
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Retrieve command line input. If it is not correct, end.
|
|
lxi d,usage ; Usage string
|
|
mvi c,puts ; Print that string when we need to
|
|
lxi h,cmdln ; Pointer to command line
|
|
mov a,m ; Get length
|
|
ana a ; Zero?
|
|
jc 5 ; Then print and stop
|
|
inx h ; Advance to first non-space element
|
|
inx h
|
|
mov a,m ; Get first character
|
|
sui '3' ; Minimum number
|
|
jc 5 ; If input was less than that, print and stop
|
|
adi 3 ; Add 3 back, giving the board size
|
|
cpi 9 ; Is it higher than 8 now?
|
|
jnc 5 ; Then print usage and stop
|
|
sta boardsz ; Store the board size
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Because there's no standard way in CP/M to get at any
|
|
;;; entropy (not even a clock), ask the user to supply some
|
|
;;; by pressing the keys on the keyboard.
|
|
lxi d,presskeys
|
|
call outs
|
|
mvi b,8 ; we want to do this 8 times, for 24 keys total
|
|
randouter: mvi c,3 ; there are 3 bytes of state for the RNG
|
|
lxi h,xabcdat+1
|
|
randinner: push h ; keep the pointer and counters
|
|
push b
|
|
waitkey: mvi c,rawio
|
|
mvi e,0FFh
|
|
call 5
|
|
ana a
|
|
jz waitkey
|
|
pop b ; restore the pointer and counters
|
|
pop h
|
|
xra m ; XOR key with data
|
|
mov m,a
|
|
inx h
|
|
dcr c ; Have we had 3 bytes yet?
|
|
jnz randinner
|
|
dcr b ; Have we done it 8 times yet?
|
|
jnz randouter
|
|
lxi d,welcome
|
|
call outs
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Generate a random board
|
|
lxi h,board
|
|
lxi b,4001h ; B=81, C=1
|
|
genrand: call xabcrand
|
|
ana c
|
|
mov m,a
|
|
inx h
|
|
dcr b
|
|
jnz genrand
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Copy board into goal
|
|
lxi h,board
|
|
lxi d,goal
|
|
mvi b,64
|
|
copygoal: mov a,m
|
|
stax d
|
|
inx h
|
|
inx d
|
|
dcr b
|
|
jnz copygoal
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Do a bunch of random flips on the board (5-20)
|
|
call xabcrand
|
|
ani 0Fh ; 0..15 flips
|
|
adi 5 ; 5..20 flips
|
|
sta sysflips
|
|
mov b,a ; Do that many flips
|
|
randflip: call xabcrand
|
|
call flip ; Unused bits in the random number are ignored
|
|
dcr b
|
|
jnz randflip
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the current state
|
|
gamestate: lxi d,smoves
|
|
call outs
|
|
lda usrflips
|
|
call outanum
|
|
lxi d,sgoal
|
|
call outs
|
|
lda sysflips
|
|
call outanum
|
|
lxi d,newline
|
|
call outs
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the current board and the goal
|
|
;;; Print the header first
|
|
lxi d,sboardhdr
|
|
call outs
|
|
lda boardsz
|
|
lxi d,2041h ; E=letter (A), D=space
|
|
add e ; Last letter
|
|
mov b,a ; B = last letter
|
|
mvi l,2 ; Print two headers
|
|
header: mvi e,'A'
|
|
mov a,d
|
|
call outaspace
|
|
headerpart: mov a,e
|
|
cmp b
|
|
jc headerltr
|
|
mov a,d ; Print spaces for invalid positions
|
|
headerltr: call outaspace
|
|
mov a,e
|
|
inr a
|
|
mov e,a
|
|
cpi 'A'+8
|
|
jc headerpart
|
|
mvi a,9
|
|
call outa
|
|
dcr l ; Print two headers (for two boards)
|
|
jnz header
|
|
lxi d,newline
|
|
call outs
|
|
;;; Then print each line of the board
|
|
mvi c,0 ; Start with line 0
|
|
printline: lxi h,board ; Get position on board
|
|
mvi d,2 ; Run twice - print board and goal
|
|
prbrdline: mvi a,'1' ; Print line number
|
|
add c
|
|
call outaspace
|
|
push d
|
|
mov a,c ; Line offset in board
|
|
rlc
|
|
rlc
|
|
rlc
|
|
mov e,a
|
|
mvi d,0
|
|
dad d ; Add line offset
|
|
pop d ; Restore board counter
|
|
mvi b,0 ; Start with column 0
|
|
printcol: lda boardsz ; Valid position?
|
|
dcr a
|
|
cmp b
|
|
jnc printbit
|
|
mvi a,' ' ; No - print space
|
|
jmp printpos
|
|
printbit: mov a,m ; Yes - print 0 or 1
|
|
adi '0'
|
|
printpos: call outaspace
|
|
inx h ; Next board pos
|
|
inr b
|
|
mov a,b ; Done yet?
|
|
cpi 8
|
|
jnz printcol ; If not, print next column
|
|
mvi a,9
|
|
call outa
|
|
lxi h,goal ; Print goal line next
|
|
dcr d
|
|
jnz prbrdline
|
|
mvi a,13 ; Print newline
|
|
call outa
|
|
mvi a,10
|
|
call outa
|
|
inr c ; Next line
|
|
lda boardsz ; Valid line?
|
|
dcr a
|
|
cmp c
|
|
jnc printline
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Prompt the user for a move
|
|
lxi d,prompt ; Print prompt
|
|
call outs
|
|
readinput: mvi c,getch ; Read character
|
|
call 5
|
|
ori 32 ; Make letters lowercase
|
|
cpi 'q' ; Quit?
|
|
rz
|
|
cpi '9'+1 ; 9 or lower? assume number
|
|
jc nummove
|
|
sui 'a' ; Otherwise, assume letter
|
|
jc invalid ; So <'a' is invalid input
|
|
call checkmove ; See if the move is valid
|
|
jc invalid ; If not, wrong input
|
|
ori 128 ; Set high bit for column flip
|
|
call flip ; Do the flip
|
|
jmp checkboard ; See if the game is won
|
|
nummove: sui '1' ; Board array is 0-based of course
|
|
jc invalid ; Not on board = invalid input
|
|
call checkmove ; See if the move is vali
|
|
jc invalid
|
|
call flip ; Do the move (high bit clear for row)
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; See if the user has won
|
|
checkboard: lda boardsz
|
|
dcr a
|
|
mov b,a ; B = line
|
|
checkrow: lda boardsz
|
|
dcr a
|
|
mov c,a ; C = column
|
|
checkcol: mov a,b
|
|
rlc ; Line * 8
|
|
rlc
|
|
rlc
|
|
add c ; + column = offset
|
|
push b ; Store line/column
|
|
mvi h,0 ; Position offset
|
|
mov l,a
|
|
lxi d,board ; Get board
|
|
dad d
|
|
mov b,m ; B = board position
|
|
lxi d,64 ; Goal = board+64
|
|
dad d
|
|
mov a,m ; A = goal position
|
|
cmp b ; Are they the same?
|
|
pop b ; Restore line/column
|
|
jnz nowin ; If not, the user hasn't won
|
|
dcr c ; If so, try next column position
|
|
jp checkcol
|
|
dcr b ; If column done, try next row
|
|
jp checkrow
|
|
;;; If we get here, the user has won
|
|
lxi d,win
|
|
jmp outs
|
|
;;; The user hasn't won yet, get another move
|
|
nowin: lxi h,usrflips ; Increment the user flips
|
|
inr m
|
|
jmp gamestate ; Print new game state, get new move
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Invalid input: erase it, beep, and get another character
|
|
invalid: lxi d,nope
|
|
call outs
|
|
jmp readinput
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Check if the move in A is valid. Set carry if not.
|
|
checkmove: mov b,a
|
|
lda boardsz
|
|
dcr a
|
|
cmp b
|
|
mov a,b
|
|
ret
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Flip row or column A & 7 (zero-indexed) on the board.
|
|
;;; Bit 7 set = column, else row.
|
|
flip: rlc ; Test bit 7 (and A*2)
|
|
jc flipcol
|
|
;;; Flip row
|
|
push b ; Keep registers
|
|
push h
|
|
ani 0Eh ; Get row number
|
|
rlc ; Calculate offset, A*4
|
|
rlc ; A*8
|
|
mvi b,0 ; BC = A
|
|
mov c,a
|
|
lxi h,board ; Get board pointer
|
|
dad b ; Add row offset
|
|
lxi b,0801h ; Max. 8 bits, and C=1 (to flip)
|
|
fliprowbit: mov a,m ; Get position
|
|
xra c ; Flip position
|
|
mov m,a ; Store position
|
|
inx h ; Increment pointer
|
|
dcr b ; Done yet?
|
|
jnz fliprowbit
|
|
pop h ; Restore registers
|
|
pop b
|
|
ret
|
|
;;; Flip column
|
|
flipcol: push b ; Keep registers
|
|
push d
|
|
push h
|
|
rrc ; Rotate A back
|
|
ani 7 ; Get column number
|
|
mvi d,0 ; Column offset
|
|
mov e,a
|
|
lxi h,board ; Get board pointer
|
|
dad d ; Add column offset
|
|
mvi e,8 ; Advance by 8 each time through the loop
|
|
lxi b,0801h ; Max. 8 bits, and C=1 (to flip)
|
|
flipcolbit: mov a,m ; Get position
|
|
xra c ; Flip position
|
|
mov m,a ; Store position
|
|
dad d ; Next row
|
|
dcr b ; Done yet?
|
|
jnz flipcolbit
|
|
pop h ; Restore registers
|
|
pop d
|
|
pop b
|
|
ret
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; The "X ABC" 8-bit random number generator
|
|
xabcrand: push h
|
|
lxi h,xabcdat
|
|
inr m ; X++
|
|
mov a,m ; X,
|
|
inx h ;
|
|
xra m ; ^ C,
|
|
inx h ;
|
|
xra m ; ^ A,
|
|
mov m,a ; -> A
|
|
inx h
|
|
add m ; + B,
|
|
mov m,a ; -> B
|
|
rar ; >>1
|
|
dcx h
|
|
xra m ; ^ A,
|
|
dcx h
|
|
add m ; + C
|
|
mov m,a ; -> C
|
|
pop h
|
|
ret
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the string in DE. This saves one byte per call.
|
|
outs: mvi c,puts
|
|
jmp 5
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the number in A in decimal, preserving registers
|
|
outanum: push psw
|
|
push b
|
|
push d
|
|
push h
|
|
lxi d,outabuf+3
|
|
outadgt: mvi b,-1
|
|
outdivmod: inr b
|
|
sui 10
|
|
jnc outdivmod
|
|
adi 10+'0'
|
|
dcx d
|
|
stax d
|
|
mov a,b
|
|
ana a
|
|
jnz outadgt
|
|
call outs
|
|
jmp regrestore
|
|
outabuf: db '***$' ; Room for ASCII number
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the character in A followed by a space, preserving
|
|
;;; registers.
|
|
outaspace: call outa
|
|
push psw
|
|
mvi a,' '
|
|
call outa
|
|
pop psw
|
|
ret
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Print the character in A, preserving all registers.
|
|
outa: push psw
|
|
push b
|
|
push d
|
|
push h
|
|
mov e,a
|
|
mvi c,putch
|
|
call 5
|
|
regrestore: pop h
|
|
pop d
|
|
pop b
|
|
pop psw
|
|
ret
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Strings
|
|
usage: db 'Usage: FLIP [3..8], number is board size.$'
|
|
presskeys: db 'Please press some keys to generate a random state...$'
|
|
welcome: db 'done.',13,10,13,10
|
|
db '*** FLIP THE BITS *** ',13,10
|
|
db '--------------------- ',
|
|
newline: db 13,10,'$'
|
|
smoves: db 13,10,13,10,'Your flips: $'
|
|
sgoal: db 9,'Goal: $'
|
|
sboardhdr: db '--Board------------',9,'--Goal-------------',13,10,'$'
|
|
prompt: db 'Press line or column to flip, or Q to quit: $'
|
|
nope: db 8,32,8,7,'$' ; Beep and erase input
|
|
win: db 13,10,7,7,7,'You win!$'
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; Data
|
|
boardsz: ds 1 ; This will hold the board size
|
|
sysflips: ds 1 ; How many flips the system did
|
|
usrflips: ds 1 ; How many flips the user did
|
|
xabcdat: ds 4 ; RNG state
|
|
board: equ $
|
|
goal: equ board + 64 ; Max. 8*8 board
|