z80 sudoku

This commit is contained in:
Adrian Conlon committed 2026-07-17 10:56:57 +01:00
1 parent 5a670ba718
commit ade1ed9ae7
6 files changed
+556 -1

No files matched your search

+3 -1
View File
@@ -20,6 +20,8 @@ namespace Z80.Test
public string RomDirectory { get; } = "roms";
public string Program { get; } = "zexall.com";
//public string Program { get; } = "zexall.com";
public string Program { get; } = "z80_sudoku/sudoku.com";
}
}
+33
View File
@@ -0,0 +1,33 @@
# z80_sudoku
Sudoku solver in Z80 assembler, assembled with
[sjasmplus](https://github.com/z00m128/sjasmplus) and packaged as a CP/M `.COM`.
A port of the 6502 version (`6502_sudoku`). It uses the same recursive
backtracking algorithm with the per-candidate row/column/box scan. (On 8-bit
CPUs the naive scan beats bitmask/MRV variants - that was measured on the
6502 - so this port keeps the scan.)
## Building
make # or: sjasmplus sudoku.asm
Produces `sudoku.com` (CP/M binary) and `sudoku.hex` (Intel HEX, load address $0100) via the `SAVEBIN`/`SAVEHEX` directives.
## Running
It's a standard CP/M transient program (`ORG $0100`). Run it under CP/M or a
CP/M emulator; it prints via BDOS function 2 and returns via warm boot:
Solving puzzle: pass
followed by the solved grid.
## Notes
- Console output: CP/M BDOS function 2 (`LD E,char / LD C,2 / CALL 5`).
- The board and tables live in the TPA (RAM), so the puzzle is mutated in place.
- `n` (the cell index) is passed to `solve` in `C`; recursion uses the hardware
stack. `is_available` returns its result in the carry flag and preserves
`A`, `B`, `C`.
- The row, column and box scans are fully unrolled (as in the 6502/6809 ports).
+15
View File
@@ -0,0 +1,15 @@
# Makefile for the Z80 (CP/M) sudoku port - sjasmplus
#
# sjasmplus writes sudoku.com via the SAVEBIN directive at the end of the
# source. Run the resulting .COM under CP/M (or a CP/M emulator).
ASM = sjasmplus
all: sudoku.com
sudoku.com: sudoku.asm
$(ASM) sudoku.asm
.PHONY: clean
clean:
rm -fv *.com *.hex *.lst *.map *.sym
+441
View File
@@ -0,0 +1,441 @@
; ============================================================================
; z80_sudoku - Sudoku solver in Z80 assembler (sjasmplus), CP/M .COM
;
; A port of the 6502 version. Same recursive backtracking algorithm with the
; per-candidate row/column/box scan (the naive scan is the right choice on an
; 8-bit CPU - bitmask/MRV variants were measured slower on the 6502).
;
; Console output uses CP/M BDOS function 2; the program returns via warm boot.
; ============================================================================
DEVICE NOSLOT64K
BDOS equ 0x0005
CONOUT equ 2
ORG 0x0100
start:
ld sp, stacktop
ld hl, msg_solving
call print_string
ld c, 0 ; n = 0
call solve
or a
jr nz, sv_fail
ld hl, msg_pass
call print_string
call print_board
jr sv_done
sv_fail:
ld hl, msg_fail
call print_string
sv_done:
ld a, 0x0a ; trailing newline
call conout
jp 0 ; warm boot back to CP/M
; ----------------------------------------------------------------------------
; conout - print the character in A via BDOS function 2. Preserves all regs.
; ----------------------------------------------------------------------------
conout:
push af
push bc
push de
push hl
ld e, a
ld c, CONOUT
call BDOS
pop hl
pop de
pop bc
pop af
ret
; print_string - print the nul-terminated string at HL.
print_string:
push af
ps_loop:
ld a, (hl)
or a
jr z, ps_done
call conout
inc hl
jr ps_loop
ps_done:
pop af
ret
; ----------------------------------------------------------------------------
; solve - recursive backtracking
; entry: C = cell index n (0..81)
; exit : A = 0 on success, A <> 0 on failure
; ----------------------------------------------------------------------------
solve:
ld a, c
cp 81
jr nz, sv_go
xor a ; reached the end -> solved
ret
sv_go:
ld hl, puzzle
ld b, 0
add hl, bc ; HL = puzzle + n
ld a, (hl)
or a
jr z, sv_empty
inc c ; already assigned -> skip
jp solve ; tail recursion
sv_empty:
ld b, 1 ; first candidate digit
sv_loop:
ld a, b
call is_available ; CY = 1 if the digit is already used
jr c, sv_next
ld a, b
ld hl, puzzle
ld d, 0
ld e, c
add hl, de ; HL = puzzle + n
ld (hl), a ; tentative assignment
push bc ; save digit (B) and n (C)
inc c
call solve
pop bc
or a
jr z, sv_success
sv_next:
inc b
ld a, b
cp 10
jp nz, sv_loop
ld hl, puzzle ; every digit failed -> unmake and backtrack
ld d, 0
ld e, c
add hl, de
ld (hl), 0
ld a, 1 ; non-zero -> failure
ret
sv_success:
xor a
ret
; ----------------------------------------------------------------------------
; is_available - may digit A be placed in cell C (n)?
; entry: A = digit, C = n
; exit : CY = 1 if the digit is already used (not available), CY = 0 if free
; preserves A, B, C.
; ----------------------------------------------------------------------------
is_available:
push bc
; --- row (9 contiguous cells from the row start) ---
ld hl, table_move2row_start
ld b, 0
add hl, bc
ld e, (hl)
ld hl, puzzle
ld d, 0
add hl, de ; HL = puzzle + row start
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
inc hl
cp (hl)
jr z, ia_r
jr ia_row_ok ; no conflict in row
ia_r: jp ia_used ; (trampoline: ia_used is > 127 bytes away)
ia_row_ok:
; --- column (9 cells, stride 9) ---
ld hl, table_move2x
ld b, 0
add hl, bc
ld e, (hl)
ld hl, puzzle
ld d, 0
add hl, de ; HL = puzzle + column
ld de, 9
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
add hl, de
cp (hl)
jr z, ia_c
jr ia_col_ok ; no conflict in column
ia_c: jp ia_used
ia_col_ok:
; --- box (offsets 0,1,2,9,10,11,18,19,20); ia_used now in jr range ---
ld hl, table_move2box_start
ld b, 0
add hl, bc
ld e, (hl)
ld hl, puzzle
ld d, 0
add hl, de ; HL = puzzle + box start
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
ld de, 7
add hl, de
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
ld de, 7
add hl, de
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
inc hl
cp (hl)
jr z, ia_used
pop bc ; available
or a ; CY = 0
ret
ia_used:
pop bc
scf ; CY = 1
ret
; ----------------------------------------------------------------------------
; Board printing
; ----------------------------------------------------------------------------
print_element: ; entry: A = cell index
push hl
push bc
ld c, a ; C = index
ld a, 0x20 ; space
call conout
ld hl, puzzle
ld b, 0
add hl, bc ; HL = puzzle + index
ld a, (hl)
or a
jr z, pe_blank
add a, 0x30 ; digit -> ASCII
call conout
jr pe_fin
pe_blank:
ld a, 0x2d ; '-'
call conout
pe_fin:
ld a, 0x20 ; space
call conout
pop bc
pop hl
ret
print_box_break_vertical:
ld a, 0x7c ; '|'
call conout
ret
print_box_break_horizontal:
ld hl, msg_hbreak
call print_string
ret
print_newline:
ld a, 0x0d
call conout
ld a, 0x0a
call conout
ret
print_board:
call print_newline
call print_newline
call print_box_break_horizontal
call print_newline
ld c, 0 ; index
pb_loop:
ld a, c
call print_element
inc c ; advance to next index
; horizontal box break at the top of each band of three rows
ld hl, table_move2box_y
ld b, 0
add hl, bc
ld a, (hl)
or a
jr nz, pb_boxh
ld hl, table_move2x
ld b, 0
add hl, bc
ld a, (hl)
or a
jr nz, pb_boxh
call print_newline
call print_box_break_horizontal
pb_boxh:
ld hl, table_move2x
ld b, 0
add hl, bc
ld a, (hl)
or a
jr nz, pb_newl
call print_newline
jr pb_cont
pb_newl:
ld hl, table_move2box_x
ld b, 0
add hl, bc
ld a, (hl)
or a
jr nz, pb_cont
call print_box_break_vertical
pb_cont:
ld a, c
cp 81
jp nz, pb_loop
ret
; ----------------------------------------------------------------------------
; Strings (nul-terminated)
; ----------------------------------------------------------------------------
msg_solving: db "Solving puzzle: ", 0
msg_pass: db "pass", 0
msg_fail: db "fail", 0
msg_hbreak: db " --------+---------+--------", 0
; ----------------------------------------------------------------------------
; The puzzle (clues). Lives in the TPA (RAM) so it is mutated in place.
; "World's hardest sudoku".
; ----------------------------------------------------------------------------
puzzle:
db 8,0,0,0,0,0,0,0,0
db 0,0,3,6,0,0,0,0,0
db 0,7,0,0,9,0,2,0,0
db 0,5,0,0,0,7,0,0,0
db 0,0,0,0,4,5,7,0,0
db 0,0,0,1,0,0,0,3,0
db 0,0,1,0,0,0,0,6,8
db 0,0,8,5,0,0,0,1,0
db 0,9,0,0,0,0,4,0,0
; ----------------------------------------------------------------------------
; Lookup tables. The three used while printing get a trailing sentinel so the
; index-81 read at the end of the board loop is defined (matches the 6502).
; ----------------------------------------------------------------------------
table_move2x:
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0,1,2,3,4,5,6,7,8
db 0
table_move2box_x:
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0,1,2,0,1,2,0,1,2
db 0
table_move2box_y:
db 0,0,0,0,0,0,0,0,0
db 1,1,1,1,1,1,1,1,1
db 2,2,2,2,2,2,2,2,2
db 0,0,0,0,0,0,0,0,0
db 1,1,1,1,1,1,1,1,1
db 2,2,2,2,2,2,2,2,2
db 0,0,0,0,0,0,0,0,0
db 1,1,1,1,1,1,1,1,1
db 2,2,2,2,2,2,2,2,2
db 0
table_move2row_start:
db 0,0,0,0,0,0,0,0,0
db 9,9,9,9,9,9,9,9,9
db 18,18,18,18,18,18,18,18,18
db 27,27,27,27,27,27,27,27,27
db 36,36,36,36,36,36,36,36,36
db 45,45,45,45,45,45,45,45,45
db 54,54,54,54,54,54,54,54,54
db 63,63,63,63,63,63,63,63,63
db 72,72,72,72,72,72,72,72,72
table_move2box_start:
db 0,0,0,3,3,3,6,6,6
db 0,0,0,3,3,3,6,6,6
db 0,0,0,3,3,3,6,6,6
db 27,27,27,30,30,30,33,33,33
db 27,27,27,30,30,30,33,33,33
db 27,27,27,30,30,30,33,33,33
db 54,54,54,57,57,57,60,60,60
db 54,54,54,57,57,57,60,60,60
db 54,54,54,57,57,57,60,60,60
prog_end: ; end of code + data (this is the saved image)
; ----------------------------------------------------------------------------
; Stack: grows down from stacktop, which sits in TPA RAM just past the image
; (not part of the saved binary). Plenty for ~60 levels of recursion.
; ----------------------------------------------------------------------------
ds 512
stacktop:
SAVEBIN "sudoku.com", 0x0100, prog_end-0x0100
SAVEHEX "sudoku.hex", 0x0100, prog_end-0x0100
Binary file not shown.
+64
View File
@@ -0,0 +1,64 @@
:1001000031E30621C202CD39010E00CD4601B720F0
:100110000B21D302CD3901CD7202180621D802CDB0
:1001200039013E0ACD2A01C30000F5C5D5E55F0EB1
:1001300002CD0500E1D1C1F1C9F57EB72806CD2A6F
:10014000012318F6F1C979FE512002AFC921FA0244
:100150000600097EB728040CC34601060178CD8A43
:100160000138127821FA021600591977C50CCD46CC
:1001700001C1B728130478FE0AC25D0121FA0216F4
:1001800000591936003E01C9AFC9C5214104060016
:10019000095E21FA02160019BE282223BE281E235A
:1001A000BE281A23BE281623BE281223BE280E23DB
:1001B000BE280A23BE280623BE28021803C3310224
:1001C000214B030600095E21FA02160019110900ED
:1001D000BE282219BE281E19BE281A19BE281619B3
:1001E000BE281219BE280E19BE280A19BE280619E3
:1001F000BE28021803C331022192040600095E21C1
:10020000FA02160019BE282923BE282523BE28215C
:1002100011070019BE281A23BE281623BE28121162
:10022000070019BE280B23BE280723BE2803C1B729
:10023000C9C137C9E5C54F3E20CD2A0121FA0206C2
:1002400000097EB72807C630CD2A0118053E2DCDFE
:100250002A013E20CD2A01C1E1C93E7CCD2A01C937
:1002600021DD02CD3901C93E0DCD2A013E0ACD2A3C
:1002700001C9CD6702CD6702CD6002CD67020E00D5
:1002800079CD34020C21EF030600097EB72010213E
:100290004B030600097EB72006CD6702CD60022120
:1002A0004B030600097EB72005CD6702180D219D7E
:1002B000030600097EB72003CD5A0279FE51C280A1
:1002C00002C9536F6C76696E672070757A7A6C65B7
:1002D0003A200070617373006661696C00202D2DF7
:1002E0002D2D2D2D2D2D2B2D2D2D2D2D2D2D2D2D40
:1002F0002B2D2D2D2D2D2D2D2D0008000000000063
:1003000000000000000306000000000000070000DD
:1003100009000200000005000000070000000000C6
:1003200000000405070000000000010000000300B9
:1003300000000100000000060800000805000000A1
:100340000100000900000000040000000102030495
:10035000050607080001020304050607080001025C
:100360000304050607080001020304050607080048
:10037000010203040506070800010203040506073D
:100380000800010203040506070800010203040532
:100390000607080001020304050607080000010221
:1003A000000102000102000102000102000102003E
:1003B000010200010200010200010200010200012D
:1003C000020001020001020001020001020001021C
:1003D000000102000102000102000102000102000E
:1003E00001020001020001020001020001020000FE
:1003F00000000000000000000101010101010101F5
:1004000001020202020202020202000000000000D9
:1004100000000001010101010101010102020202CB
:1004200002020202020000000000000000000101C0
:1004300001010101010101020202020202020202A3
:100440000000000000000000000009090909090976
:100450000909091212121212121212121B1B1B1B73
:100460001B1B1B1B1B2424242424242424242D2D67
:100470002D2D2D2D2D2D2D3636363636363636365B
:100480003F3F3F3F3F3F3F3F3F484848484848483D
:1004900048480000000303030606060000000303AB
:1004A000030606060000000303030606061B1B1BCB
:1004B0001E1E1E2121211B1B1B1E1E1E2121211B56
:1004C0001B1B1E1E1E2121213636363939393C3C74
:1004D0003C3636363939393C3C3C36363639393992
:0304E0003C3C3C65
:00000001FF