; Super Sudoku 81 for the 16K ZX81 ; (c) 2006,2007 Simon Holdsworth ; http://www.zx81stuff.org.uk/ ; ; TODO: add some more 6x6 boards of various difficulties. ; ; Possible Future new functions: ; Any additional functionality will require additional compression ; of the instructions - probably dictionary based, and also possibly of ; the supplied boards. ; Add support for 8x8 with X, 6x6 with X. ; Additional logic: FindOptionInsideClosedSet; ; Additional logic: FindYWing ; Allow redefinition of cursor keys. Possibly just QAOP/[shift]5678. ; For hint, first check whether any non-static value is incorrectly placed. ; ; Known limitations/bugs: ; "undo" moves cursor when all non-static moves already undone. ; I expect there are ROM routines that could be used for some of this. ; ; Based on my .P file skeleton. ; Skip down to Line1Text for the interesting bit. #define DEFB .BYTE #define DEFW .WORD #define DEFM .TEXT #define ORG .ORG #define EQU .EQU ; These macros allow checking at assembly time that particular blocks of memory ; do not cross 256-byte boundaries. This is to allow code to only update the ; low byte of a table pointer and know that the high byte never needs updating ; Each such block must either start with a Start256 macro, or be preceded by ; an Align256 or Check256 macro (each of which implicitly does a Start256) MACRO_BlockStart EQU 0 #define Start256 .NOLIST #defcont \MACRO_BlockStart: .SET $ #defcont \.LIST #define Check256(block) .NOLIST #defcont \#IF (($ & 0FF00H) != (MACRO_BlockStart & 0FF00H)) #defcont \.ECHO "ERROR: block crosses 256-byte boundary\n" #defcont \!!! #defcont \#ENDIF #defcont \Start256 #defcont \.LIST ; Align the following block on a 256 byte boundary. #define Align256Msg(block) .NOLIST #defcont \.ECHO "Skipping " #defcont \.ECHO ((($ + 0FFH) & 0FF00H) - $) #defcont \.ECHO " bytes for block\n" #defcont \.LIST ;#define Align256Msg(block) #define Align256(block) .NOLIST #defcont \Align256Msg(block) #defcont \.BLOCK (($ + 0FFH) & 0FF00H) - $ #defcont \Start256 #defcont \.LIST ; ZX81 ROM routines KSCAN EQU $02BB FINDCHR EQU $07BD COPY EQU $0869 FAST EQU $0F23 SLOW EQU $0F2B ; =========================================================== ; Start of the Program ; =========================================================== ; Origin of a ZX81 file is always 16393 ORG 16393 ; System variables. VERSN: DEFB 0 E_PPC: DEFW 2 D_FILE: DEFW Display DF_CC: DEFW Display+1 ; First character of display VARS: DEFW Variables DEST: DEFW 0 E_LINE: DEFW BasicEnd CH_ADD: DEFW BasicEnd+4 ; Simulate SAVE "X" X_PTR: DEFW 0 STKBOT: DEFW BasicEnd+5 STKEND: DEFW BasicEnd+5 ; Empty stack BREG: DEFB 0 MEM: DEFW MEMBOT UNUSED1: DEFB 0 DF_SZ: DEFB 2 S_TOP: DEFW $0002 ; Top program line number LAST_K: DEFW $FDBF DEBOUN: DEFB 15 MARGIN: DEFB 55 NXTLIN: DEFW Line2 ; Next line address OLDPPC: DEFW 0 FLAGX: DEFB 0 STRLEN: DEFW 0 T_ADDR: DEFW $0C8D SEED: DEFW 0 FRAMES: DEFW $F5A3 COORDS: DEFW 0 PR_CC: DEFB $BC S_POSN: DEFW $1821 CDFLAG: DEFB $40 PRBUFF: DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,$76 ; 32 Spaces + Newline MEMBOT: DEFB 0,0,0,0,0,0,0,0,0,0,$84,$20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 30 zeros UNUNSED2: DEFW 0 ; End of system variables Program: Line1: DEFB $00,$01 ; Line 1 DEFW Line1End-Line1Text ; Line 1 length Line1Text: DEFB $EA ; REM ; =========================================================== ; Insert your program here.... ; =========================================================== ;------------------------------------------------------------ ; ; Initial program entry point, and GUI routines start here. ; ; Routines that handle the game rules are further down. ; ;------------------------------------------------------------ Start: LD B,13 ; Flash the bottom line LD HL,(D_FILE) LD DE,1+(22*33)+9 ; Line 22, column 9 ADD HL,DE CALL WaitForKeyFlash ; Assume a splash screen present after loading JP OptionsMenu ; Drop through to option selection. ;------------------------------------------------------------ ; GUI Variables ; ; Convention used for labels: ; ; Any label starting with a capital letter is a callable routine. ; Labels starting with '_' are local to modules ; Labels starting with 'v' or '_v' are variables/working arrays. ; Labels starting with 's' are static data tables. ;------------------------------------------------------------ vShowOptions: DEFB 0 ; Indicates whether showing actual or pencilled options vBoardType: DEFB 0 ; The type of board, using the values below vMessageVisible:DEFB 0 ; Indicates whether a message has been displayed vUpdateDisplay: DEFB 0 ; Indicates whether to update the cursor/options display vDisplayNumbers:DEFB 0 vRandomSeed: DEFW 0 ; Random seed value vBoardColRowOffset: ; Allows loading of the offsets in one go, B=row, C=col vBoardColOffset:DEFB 0 ; Screen offset of column 0 vBoardRowOffset:DEFB 0 ; Screen offset of row 0 ;vKeyUp: DEFB $36 ;'Q' ; The character value of the UP key ;vKeyDown: DEFB $26 ;'A' ; The character value of the DOWN key ;vKeyLeft: DEFB $34 ;'O' ; The character value of the LEFT key ;vKeyRight: DEFB $35 ;'P' ; The character value of the RIGHT key NINE_BY_NINE EQU 1 ; The value of the board type for a 9x9 board EIGHT_BY_EIGHT EQU 2 ; The value of the board type for an 8x8 board SIX_BY_SIX EQU 3 ; The value of the board type for a 6x6 board BOARD_COL_ROW_OFFSET_9x9 EQU 4 * 256 + 2 ; Screen offset of row 0/column 0 for the 9x9 board BOARD_COL_ROW_OFFSET_6x6 EQU 7 * 256 + 5 ; Screen offset of row 0/column 0 for the 6x6 board ;------------------------------------------------------------ ; Keyboard table for setup 9x9 game. ; ; This is here so that the tables are contiguous and don't ; sit between pieces of code, forcing all jumps to be absolute. ; ; I could have done a table that was dense using a key table ; such as: ; '1','2','3','4','5','6','7','8','9','A','C','E','H','O'... ; found the key pressed in that table and used the resulting ; index into an address table, however given that around ; half of the keys are used, that would produce no net gain. ;------------------------------------------------------------ SetupGameKeyTable: DEFW SetupGamePlaceNumber ; $1D '1' DEFW SetupGamePlaceNumber ; $1E '2' DEFW SetupGamePlaceNumber ; $1F '3' DEFW SetupGamePlaceNumber ; $20 '4' DEFW SetupGamePlaceNumber ; $21 '5' DEFW SetupGamePlaceNumber ; $22 '6' DEFW SetupGamePlaceNumber ; $23 '7' DEFW SetupGamePlaceNumber ; $24 '8' DEFW SetupGamePlaceNumber ; $25 '9' DEFW SetupGameDown ; $26 'A' DEFW KeyboardHandlerNoAction ; $27 'B' DEFW SetupGameClearCell ; $28 'C' DEFW KeyboardHandlerNoAction ; $29 'D' DEFW SetupGameExit ; $2A 'E' DEFW KeyboardHandlerNoAction ; $2B 'F' DEFW SetupGameGenerate ; $2C 'G' DEFW KeyboardHandlerNoAction ; $2D 'H' DEFW SetupGameIsUnique ; $2E 'I' DEFW KeyboardHandlerNoAction ; $2F 'J' DEFW KeyboardHandlerNoAction ; $30 'K' DEFW SetupGameLoadBoard ; $31 'L' DEFW KeyboardHandlerNoAction ; $32 'M' DEFW SetupGameNewBoard ; $33 'N' DEFW SetupGameLeft ; $34 'O' DEFW SetupGameRight ; $35 'P' DEFW SetupGameUp ; $36 'Q' DEFW KeyboardHandlerNoAction ; $37 'R' DEFW SetupGameStart ; $38 'S' DEFW KeyboardHandlerNoAction ; $39 'T' DEFW SetupGameUndo ; $3A 'U' DEFW KeyboardHandlerNoAction ; $3B 'V' DEFW KeyboardHandlerNoAction ; $3C 'W' DEFW KeyboardHandlerNoAction ; $3D 'X' DEFW KeyboardHandlerNoAction ; $3E 'Y' DEFW SetupGamePrint ; $3F 'Z' ;------------------------------------------------------------ ; Keyboard table for play 9x9 game. ;------------------------------------------------------------ PlayGameKeyTable: DEFW PlayGamePlaceNumber ; $1D '1' DEFW PlayGamePlaceNumber ; $1E '2' DEFW PlayGamePlaceNumber ; $1F '3' DEFW PlayGamePlaceNumber ; $20 '4' DEFW PlayGamePlaceNumber ; $21 '5' DEFW PlayGamePlaceNumber ; $22 '6' DEFW PlayGamePlaceNumber ; $23 '7' DEFW PlayGamePlaceNumber ; $24 '8' DEFW PlayGamePlaceNumber ; $25 '9' DEFW PlayGameDown ; $26 'A' DEFW KeyboardHandlerNoAction ; $27 'B' DEFW PlayGameClearCell ; $28 'C' DEFW PlayGameDifficulty ; $29 'D' DEFW PlayGameExit ; $2A 'E' DEFW KeyboardHandlerNoAction ; $2B 'F' DEFW KeyboardHandlerNoAction ; $2C 'G' DEFW PlayGameHint ; $2D 'H' DEFW PlayGameIsUnique ; $2E 'I' DEFW KeyboardHandlerNoAction ; $2F 'J' DEFW KeyboardHandlerNoAction ; $30 'K' DEFW PlayGameSolveAll ; $31 'L' DEFW KeyboardHandlerNoAction ; $32 'M' DEFW PlayGameSolveOne ; $33 'N' DEFW PlayGameLeft ; $34 'O' DEFW PlayGameRight ; $35 'P' DEFW PlayGameUp ; $36 'Q' DEFW KeyboardHandlerNoAction ; $37 'R' DEFW PlayGameShowOptionsKey ; $38 'S' DEFW PlayGamePencilOption ; $39 'T' DEFW PlayGameUndo ; $3A 'U' DEFW KeyboardHandlerNoAction ; $3B 'V' DEFW KeyboardHandlerNoAction ; $3C 'W' DEFW KeyboardHandlerNoAction ; $3D 'X' DEFW KeyboardHandlerNoAction ; $3E 'Y' DEFW PlayGamePrint ; $3F 'Z' ;------------------------------------------------------------ ; Generic keyboard handling routine. ; ; HL contains the address of the key table, which must provide ; the addresses for routines for keys '1' to 'Z' (i.e. 35 values.) ;------------------------------------------------------------ KeyboardHandler: .MODULE KH PUSH HL ; Save the key table address XOR A ; Clear the exit flag LD (vKeyboardHandlerExitFlag),A _loop CALL GetKeyPress ; A = key value - need to hold on to this until JP (HL) SUB $1D ; Adjust '1'...'Z' to be in the range 0 ... 34 JR C,_loop ; Check in range CP 35 JR NC,_loop ; Check in range LD E,A INC A ; Adjust to range 1..35 for '1'..'Z' LD D,0 POP HL ; Restore key table address PUSH HL ; And save again ADD HL,DE ADD HL,DE ; HL = table address + key value * 2 LD E,(HL) ; NOTE: JP (HL) doesn't jump to the location in the memory INC HL ; address pointed to by HL, it jumps to the memory address LD H,(HL) ; pointed to by HL. LD L,E ; So this code does what JP (HL) OUGHT to do. CALL _callHL ; Note: need to hold on to A to here from GetKeyPress. LD A,(vKeyboardHandlerExitFlag) OR A ; Exit non zero indicates return from KeyboardHandler JR Z,_loop XOR A ; Reset the exit flag LD (vKeyboardHandlerExitFlag),A POP HL ; Clear up stack KeyboardHandlerNoAction: RET ; Re-use this RET instruction for null routines. _callHL JP (HL) ; Allow RET from the called routine to return to the ; caller of _callHL vKeyboardHandlerExitFlag: DEFB 0 ;------------------------------------------------------------ ; Handle the options menu items. ;------------------------------------------------------------ OptionsMenu: .MODULE OM LD HL,sOptionsScreenData ; Display the options screen CALL CopyScreen LD A,NINE_BY_NINE ; Default to 9x9 board LD (vBoardType),A _updateDisplay CALL DisplayBoardType ; Display the board type indicator ; TODO: Use the KeyboardHandler to handle the Options menu items. ; I actually don't think its worth it for this one as ; A) there aren't many entries ; B) the entries aren't shared with other menus. _nextKey CALL GetKeyPress ; Get the next key press CP $36 ; 'Q' Quit option selected JR Z,_quit CP $38 ; 'S' Start option selected JR Z,_start CP $2E ; 'I' Instructions selected JR Z,_instructions CP $25 ; '9' 9 x 9 puzzle selected JR Z,_9x9Selected ;CP $24 ; '8' 8 x 8 puzzle selected ;JR Z,_8x8Selected CP $22 ; '6' 6 x 6 puzzle selected JR Z,_6x6Selected ; Other options go here ; Not a valid key JR _nextKey _quit RET ; Just return to BASIC _9x9Selected LD A,NINE_BY_NINE ; Set board type to 9x9 LD (vBoardType),A JR _updateDisplay ; Update the options display _6x6Selected LD A,SIX_BY_SIX ; Set board type to 6x6 LD (vBoardType),A JR _updateDisplay ; Update the options display _start LD A,(vBoardType) CP SIX_BY_SIX JR NZ,_start9x9 CALL Setup6x6GameMenu ; Set up a 6x6 game JR OptionsMenu ; When finished, go back to the options menu _start9x9 CALL Setup9x9GameMenu ; Set up a 9x9 game JR OptionsMenu ; When finished, go back to the options menu _instructions CALL Instructions ; Display instructions JR OptionsMenu ;------------------------------------------------------------ ; Display currently selected board type. ;------------------------------------------------------------ DisplayBoardType: .MODULE DBT LD A,(vBoardType) ; Get the board type LD HL,(D_FILE) ; Locate the position on screen of the indicator LD DE,1+(7*33)+15 ; Line 7, column 15 ADD HL,DE LD B,NINE_BY_NINE ; Display indicator state for 9x9 board CALL _underline ; Board sizes other than 9x9 not currently supported LD DE,4*33 ; Line 11, Column 17 ADD HL,DE LD B,SIX_BY_SIX ; Drop through... _underline CP B ; If the board type is selected LD C,3 ; then use this graphics character JR Z,_underline2 LD C,0 ; Otherwise use a blank _underline2 LD (HL),C ; The indicator is three characters long INC HL ; (of the same character) LD (HL),C INC HL LD (HL),C RET ;------------------------------------------------------------ ; Handle the setup 9x9 and general setup game menu items. ;------------------------------------------------------------ Setup9x9GameMenu: LD HL,BOARD_COL_ROW_OFFSET_9x9 ; Set up cell display offsets. LD (vBoardColRowOffset),HL LD HL,sSetup9x9ScreenData ; Display the 9x9 game screen SetupGameMenu: CALL CopyScreen CALL InitializeBoard ; Initialize the game board LD BC,0 ; Select the cell at 0,0 LD (vSavedRowColumn),BC ; LD A,PENCILLED_MASK ; Switch on option display by default LD (vShowOptions),A XOR A ; Clear showing hint flag LD (vMessageVisible),A LD (vImpossible),A ; Clear impossible move flag CALL EnableAllDisplayUpdates ; Enable all display updates (will select saved row/column) LD HL,SetupGameKeyTable ; Use the KeyboardHandler to handle the Setup menu items JP KeyboardHandler ; RET merged with CALL. ;------------------------------------------------------------ ; Handle the setup 6x6 game menu items. ;------------------------------------------------------------ Setup6x6GameMenu: LD HL,BOARD_COL_ROW_OFFSET_6x6 ; Set up cell display offsets. LD (vBoardColRowOffset),HL LD HL,sSetup6x6ScreenData ; Display the 6x6 game screen JR SetupGameMenu ;------------------------------------------------------------ ; Key handler routines for setting up a 9 x 9 game. ;------------------------------------------------------------ SetupGameLoadBoard: ; Load one of the supplied boards CALL DisableDisplayUpdate ; Disable display updates CALL LoadBoard ; Load the board with supplied values JP EnableDisplayUpdate ; Re-enable display update ; This table is used for fast multiplication by board size up to boardSize*boardSize*boardSize ; It needs to be on a 256-byte boundary. ; It's here in the program as (currently) this is already close to such a boundary. Align256(sMultBoardSizeSq9x9) sMultBoardSizeSq9x9: DEFW 0, 9, 18, 27, 36, 45, 54, 63, 72 DEFW 81, 90, 99,108,117,126,135,144,153 DEFW 162,171,180,189,198,207,216,225,234 DEFW 243,252,261,270,279,288,297,306,315 DEFW 324,333,342,351,360,369,378,387,396 DEFW 405,414,423,432,441,450,459,468,477 DEFW 486,495,504,513,522,531,540,549,558 DEFW 567,576,585,594,603,612,621,630,639 DEFW 648,657,666,675,684,693,702,711,720 Check256(sMultBoardSizeSq9x9) SetupGamePrint: ; Print the contents of the current screen CALL InvertRowColumn ; Switch off current selection indication CALL COPY ; Copy the screen (first 22 lines) JP InvertRowColumn ; Switch on current selection indication ; RET merged with CALL ;------------------------------------------------------------ ; Handle the play game menu items. ;------------------------------------------------------------ PlayGameMenu: LD HL,sRunPartialScreenData1 ; Copy partial screen data, first area LD DE,1+(15*33)+22 ; Line 15, column 22 LD BC,$060A ; 6 rows, 10 columns CALL CopyPartialScreen LD HL,sRunPartialScreenData2 ; Copy partial screen data, second area LD DE,1 ; Line 0, column 0 LD BC,$0320 ; 3 rows, 32 columns CALL CopyPartialScreen LD A,PENCILLED_FLAG ; Display pencilled options by default LD (vShowOptions),A CALL ClearMessage ; Clear any message CALL DisplayMovesLeft ; Ensure moves left is displayed XOR A LD (vSetupMode),A ; Switch out of setup mode LD HL,PlayGameKeyTable ; Use the KeyboardHandler to handle the Play9x9 menu items JP KeyboardHandler ; RET merged with CALL ;------------------------------------------------------------ ; Key handler routines for setting up a 9 x 9 game continued... ;------------------------------------------------------------ SetupGameGenerate: ; Generate a new board CALL DisableAllDisplayUpdates ; Disable all display updates LD HL,sGeneratingMessage ; Display the generating board message CALL DisplayMessage CALL GenerateBoard ; Generate the board CALL RefreshBoardDisplay ; Ensure that the board is displayed CALL Difficulty CALL DisplayDifficulty ; Display difficulty of the board JP EnableAllDisplayUpdates ; Re-enable all display updates SetupGameStart: ; Start playing the game CALL PlayGameMenu ; Display and run the Play 9x9 game menu ; Intentionally drop through to SetupGameExit SetupGameExit: ; Handle exiting from this menu LD A,1 ; Indicate exit from current KeyboardHandler LD (vKeyboardHandlerExitFlag),A RET ; This table is used for fast division. It needs to be on a 256-byte boundary. Align256(sDivideBoardSize9x9) sDivideBoardSize9x9: DEFB 0,0,0,0,0,0,0,0,0 ; 0 - 8 DEFB 1,1,1,1,1,1,1,1,1 ; 9 - 17 DEFB 2,2,2,2,2,2,2,2,2 ; 18 - 26 DEFB 3,3,3,3,3,3,3,3,3 ; 27 - 35 DEFB 4,4,4,4,4,4,4,4,4 ; 36 - 44 DEFB 5,5,5,5,5,5,5,5,5 ; 45 - 53 DEFB 6,6,6,6,6,6,6,6,6 ; 54 - 62 DEFB 7,7,7,7,7,7,7,7,7 ; 63 - 71 DEFB 8,8,8,8,8,8,8,8,8 ; 72 - 80 Check256(sDivideBoardSize9x9) SetupGameUndo: ; Undo the last number placed CALL InvertRowColumn ; Invert the currently selected row/column CALL Undo ; Undo the last number placed CALL InvertRowColumn ; Re-invert the newly selected row/column JP DisplayOptions ; Update the options display ; RET merged with CALL SetupGamePlaceNumber: ; Place a number in a cell LD D,A ; A holds the number to place DEC A LD HL,vBoardSize ; Check it is in range, i.e. < board size CP (HL) RET NC CALL PlaceAtSelected ; Try to place the number JR NZ,SetupGameInvalidMove ; Z indicates success LD A,(vImpossible) OR A ; Check if impossible = 0 RET Z ; If not, then number placed successfully LD HL,sImpossibleMessage ; Display the message that indicates that CALL DisplayMessage ; the placement would make the board impossible to solve XOR A LD (vImpossible),A ; Clear the impossible flag JP Undo ; Undo the placement of the number ; RET merged with CALL SetupGameInvalidMove: ; Handle an invalid move by displaying a message LD HL,sInvalidPlacementMessage JP DisplayMessage ; RET merged with CALL SetupGameNewBoard: ; Start a new board CALL DisableDisplayUpdate ; Disable display updates SetupGameNewBoard_undo: LD A,(vMoves) ; Undo moves while moves > 0 OR A ; Check if moves = 0 JP Z,EnableDisplayUpdate ; Stop when moves = 0, Re-enable display updates CALL Undo JR SetupGameNewBoard_undo ; Keep undoing SetupGameClearCell: ; Clear the currently selected cell LD A,1 ; Indicate that undo history should be updated JP ClearSelected ; Updates current display/options ; RET merged with CALL SetupGameIsUnique: ; Check whether there is a unique solution CALL DisableAllDisplayUpdates ; Disable display updates while checking for uniqueness LD HL,sLookingForUniqueMessage ; Display the "checking uniqueness" message CALL DisplayMessage CALL IsUnique ; Check if the board has a unique solution PUSH AF ; Save flag values CALL EnableAllDisplayUpdates ; Re-enable all display updates POP AF ; Restore flag values LD HL,sUniqueMessage ; Display the appropriate message JP Z,DisplayMessage ; Z incidates a unique solution LD HL,sNotUniqueMessage ; Display the non-unique message JP DisplayMessage SetupGameUp: ; Move the cursor up LD HL,vCurrentRow JR DecreaseCoordinate ; Decrease current row ; RET merged with CALL SetupGameDown: ; Move the cursor down LD HL,vCurrentRow JR IncreaseCoordinate ; Increase current row ; RET merged with CALL SetupGameRight: ; Move the cursor right LD HL,vCurrentColumn JR IncreaseCoordinate ; Increase current column ; RET merged with CALL SetupGameLeft: ; Move the cursor left LD HL,vCurrentColumn ; Intentionally drop through to DecreaseCoordinate ;------------------------------------------------------------ ; Decrease the coordinate indicated by HL. ;------------------------------------------------------------ DecreaseCoordinate: LD A,(HL) ; Get the current coordinate value OR A RET Z ; If already zero, do nothing PUSH HL CALL InvertRowColumn ; Remove the current cursor indication POP HL DEC (HL) ; Decrement the coordinate value JR UpdateCurrent ; Update cursor and options display ;------------------------------------------------------------ ; Increase the coordinate indicated by HL. ;------------------------------------------------------------ IncreaseCoordinate: LD B,(HL) ; Get the current coordinate value LD A,(vBoardSize) DEC A CP B RET Z ; If already vBoardSize - 1, do nothing PUSH HL CALL InvertRowColumn ; Remove the current cursor indication POP HL INC (HL) ; Increment the coordinate value ; Intentionally drop through to UpdateCurrent #IF $ != UpdateCurrent !!!IncreaseCoordinate/UpdateCurrent #ENDIF ;------------------------------------------------------------ ; Update the current selection and options. ;------------------------------------------------------------ UpdateCurrent: LD BC,(vCurrentRowColumn) ; B = col, C = row CALL SelectCurrentRowColumn ; Select the current row and column CALL InvertRowColumn ; Invert the newly selected row/column CALL ClearMessage ; Clear any message that might be visible JP DisplayOptions ; Update the options display as well ;------------------------------------------------------------ ; Key handler routines for playing a 9 x 9 game. ;------------------------------------------------------------ ; These routines are the same as for the setup phase. PlayGameExit EQU SetupGameExit PlayGamePrint EQU SetupGamePrint PlayGameUndo EQU SetupGameUndo PlayGameClearCell EQU SetupGameClearCell PlayGameUp EQU SetupGameUp PlayGameDown EQU SetupGameDown PlayGameRight EQU SetupGameRight PlayGameLeft EQU SetupGameLeft PlayGameIsUnique EQU SetupGameIsUnique PlayGameSolveOne: ; Find and make the next possible move CALL DisableDisplayUpdate ; Disable display updates while finding the next move CALL SolveNext ; Find and make the next move CALL DisplayExplanation ; Display the hint and re-enable display update JR PlayGameCheckComplete ; Check whether the board is now complete ; RET merged with call PlayGameHint: ; Provide a hint for the next possible move CALL DisableDisplayUpdate ; Disable display updates while looking for a hint CALL GetHint ; Get the hint JP DisplayExplanation ; Display the hint and re-enable display update ; RET merged with call ; This table is used for fast remainder. It needs to be on a 256-byte boundary. ; And 256 bytes after sDivideBoardSize9x9 Align256(sModBoardSize9x9) sModBoardSize9x9: DEFB 0,1,2,3,4,5,6,7,8 ; 0 - 8 DEFB 0,1,2,3,4,5,6,7,8 ; 9 - 17 DEFB 0,1,2,3,4,5,6,7,8 ; 18 - 26 DEFB 0,1,2,3,4,5,6,7,8 ; 27 - 35 DEFB 0,1,2,3,4,5,6,7,8 ; 36 - 44 DEFB 0,1,2,3,4,5,6,7,8 ; 45 - 53 DEFB 0,1,2,3,4,5,6,7,8 ; 54 - 62 DEFB 0,1,2,3,4,5,6,7,8 ; 63 - 71 DEFB 0,1,2,3,4,5,6,7,8 ; 72 - 80 Check256(sModBoardSize9x9) ; Enforce the fact that we rely on sModBoardSize9x9 being 256 bytes beyond sDivideBoardSize9x9 #IF sModBoardSize9x9 - sDivideBoardSize9x9 != 256 !!! #ENDIF PlayGamePlaceNumber: ; Place a number in a cell CALL SetupGamePlaceNumber ; Do the same as for setup for placing the number PlayGameCheckComplete: ; Check whether the board is now complete LD A,(vMovesLeft) OR A ; Check if moves left = 0 RET NZ ; If not, carry on PlayGameCompleted: ; Display the game completed message LD HL,sCompletionMessage CALL DisplayMessage CALL FlashMessage ; Flash the message line until key pressed LD A,1 LD (vKeyboardHandlerExitFlag),A ; Exit back to the setup menu RET PlayGamePencilOption: ; Pencil in an option LD HL,sPencilOptionMessage CALL DisplayMessage ; Prompt for the option to pencil in CALL GetKeyPress ; Get the key pressed PUSH AF CALL ClearMessage ; Clear the prompt message POP AF SUB $1D ; Adjust '1'..'boardsize' to 0..boardsize-1 RET C ; Ignore if outside the required range LD HL,vBoardSize CP (HL) ; Check that it is < boardsize RET NC INC A ; Adjust number selected to 1.. boardsize CALL PencilOption ; Pencil in the option JP DisplayOptions ; Update options display for the current cell ; RET merged with call PlayGameSolveAll: ; Solve the board CALL DisableDisplayUpdate ; Disable display updates while solving the entire board CALL SolveAll ; Solve the entire board LD A,(vMovesLeft) OR A ; Check if any moves left JP Z,PlayGameCompleted ; If none, then the board is complete ;JR DisplayExplanation ; Otherwise it couldn't be solved, so display the explanation of the last move #IF $!= DisplayExplanation !!!PlayGameSolveAll/DisplayExplanation #ENDIF ;------------------------------------------------------------ ; Display the explanation of the next possible move. ; Placed here to save one jump from PlayGameSolveAll ;------------------------------------------------------------ DisplayExplanation: CALL EnableDisplayUpdate ; Enable display updates LD HL,vMoveExplanation ; Display the hint string ; Intentionally drop through to DisplayMessage #IF $!= DisplayMessage !!!DisplayExplanation/DisplayMessage #ENDIF ;------------------------------------------------------------ ; Display a message on line 1. ; Address of the message in HL. ; Assumes the message is 32 characters. ;------------------------------------------------------------ DisplayMessage: PUSH HL ; Save message address CALL GetMessageScreenAddress ; Get the screen address EX DE,HL ; Screen address now in DE POP HL ; Restore message address LD B,32 Call Decompress ; Copy the message LD A,1 ; Remember message visible LD (vMessageVisible),A RET ;------------------------------------------------------------ ; Back to key handler routines for the PlayGame menu. ;------------------------------------------------------------ PlayGameDifficulty: ; Display the board difficulty CALL DisableAllDisplayUpdates ; Disable all display updates while determining difficulty LD HL,sDifficultyCheckMessage ; Display a message indicating checking difficulty CALL DisplayMessage CALL Difficulty ; Work out the board difficulty CALL DisplayDifficulty ; Display it JP EnableAllDisplayUpdates ; Re-enable all display updates PlayGameShowOptionsKey: .MODULE PGSOK LD A,(vShowOptions) ; Switch between displaying pencilled and actual options XOR $FF ; Toggle all bits in the options flag LD (vShowOptions),A LD DE,sShowOptionsPencilMessage ; Determine which message to display CP PENCILLED_FLAG JR Z,_displayMessage LD DE,sShowOptionsActualMessage _displayMessage LD HL,(D_FILE) ; Get the display address in HL LD BC,1+(2*33) ; Row 2, column 0 ADD HL,BC EX DE,HL ; Now display address in DE and message in HL LD BC,6 ; Six characters LDIR ; Display the text JP DisplayOptions ; Update the options display ; RET merged with CALL ;------------------------------------------------------------ ; Update the current cell display, including options display. ;------------------------------------------------------------ DisplayCurrentCell: .MODULE DCC LD HL,(vCurrentRowColumn) ; Get the currently selected row and column LD A,(vBoardRowOffset) ; Work out the screen address of the cell ADD A,L ADD A,L ; A = row*2 + board row offset LD B,A LD A,(vBoardColOffset) ADD A,H ADD A,H ; A = col*2 + board column offset LD C,A ; B = row*2+row offset, C=col*2+col offset CALL GetScreenAddress ; HL = screen address LD DE,(vCurrentBoardAddress) ; Get the current value on the board. LD A,(DE) ; Get the cell content AND $0F ; Mask out the high nibble of the cell content (used to mark fixed values) OR A ; Check for 0 - goes to space, which is 0 anyway JR Z,_displayChar ; Display a space for an empty cell ADD A,$1C ; Map from 1-9 to '1'-'9' _displayChar LD (HL),A ; Display the cell value RET ; This table is used for fast multiplication by board size up to boardSize*boardSize*boardSize ; It needs to be on a 256-byte boundary. ; It's here in the program as (currently) this is already close to such a boundary. Align256(sMultBoardSizeSq6x6) sMultBoardSizeSq6x6: DEFW 0, 6, 12, 18, 24, 30 DEFW 36, 42, 48, 54, 60, 66 DEFW 72, 78, 84, 90, 96,102 DEFW 108,114,120,126,132,138 DEFW 144,150,156,162,168,174 DEFW 180,186,192,198,204,210 Check256(sMultBoardSizeSq6x6) ;------------------------------------------------------------ ; Update the current cell display, including options display. ;------------------------------------------------------------ UpdateCurrentCellDisplay: .MODULE UCCD LD A,(vDisplayNumbers) ; Check whether we are displaying number updates OR A RET Z CALL DisplayCurrentCell ; Display the cell value ; Deliberately drop through to DisplayMovesLeft #IF $ != DisplayMovesLeft !!!UpdateCurrentCellDisplay/DisplayMovesLeft #ENDIF ;------------------------------------------------------------ ; Display the number of moves left. ;------------------------------------------------------------ SCREEN_OFFSET_MOVES_LEFT EQU 1+(0*33)+30 ; Line 0, column 30 DisplayMovesLeft: .MODULE DML LD A,(vUpdateDisplay) ; Update the moves left display OR A ; Check if display update is 0 RET Z ; Do nothing if display update is 0 LD A,(vMovesLeft) ; Get the number of moves left LD DE,SCREEN_OFFSET_MOVES_LEFT CALL DisplayNumber ; Intentionally drop through to DisplayOptions #IF $ != DisplayOptions !!!DisplayMovesLeft/DisplayOptions #ENDIF ;------------------------------------------------------------ ; Display the valid options for the cell. ;------------------------------------------------------------ PENCILLED_MASK EQU %10111111 PENCILLED_FLAG EQU %01000000 SCREEN_OFFSET_OPTIONS EQU 1+(2*33)+16 ; Line 2, column 16 DisplayOptions: .MODULE DO LD A,(vUpdateDisplay) OR A ; Check if display update is 0 RET Z ; Do nothing if display update is 0 LD BC,SCREEN_OFFSET_OPTIONS ; Get the screen address of the options display LD HL,(D_FILE) ADD HL,BC LD DE,(vCurrentBoardAddress) ; Check whether current cell is occupied. LD A,(DE) ; Get the current cell contents OR A ; Check if cell contents = 0 LD A,(vBoardSize) ; Get the board size, needed in either case LD B,A ; JR Z,_unoccupied ; If cell empty display the options for an unoccupied cell LD A,$16 ; '-' _blankLoop LD (HL),A ; Fill in the options with '-'s INC HL DJNZ _blankLoop RET _unoccupied PUSH HL ; Save the screen address LD A,1 ; Get the address of option 1 CALL GetOptionAddress ; HL = option address POP DE ; DE = screen address LD C,$1D ; The character for the first option, '1' _optionLoop LD A,(vShowOptions) ; Determine whether to show actual or pencilled options AND (HL) ; Apply the required mask to the option value CP 128 ; Now check if the option has been masked JR C,_optionNotMasked ; Never the case if showing pencilled options LD A,$08 ; Get the character for a masked option, '#' JR _showOption ; Show the option as masked _optionNotMasked OR A ; Check if the option value (ignoring pencilled flag) = 0 LD A,C ; If available, use the current option digit character JR Z,_showOption _notOption LD A,$16 ; Use the character for an unavailable option, '-' _showOption LD (DE),A ; Display the option character at the current screen location INC DE ; Move on to the next screen location INC HL ; Move on to the address of the next option INC C ; Move on to the next option digit character DJNZ _optionLoop ; Keep going for all options RET ;------------------------------------------------------------ ; Refresh the full board display ;------------------------------------------------------------ RefreshBoardDisplay: .MODULE RBD LD A,(vBoardSizeSquared) LD B,A LD E,0 _refreshLoop PUSH DE PUSH BC CALL SelectCurrentBoardAddress CALL DisplayCurrentCell POP BC POP DE INC E DJNZ _refreshLoop RET ;------------------------------------------------------------ ; Display a number value at the given screen offset ;------------------------------------------------------------ DisplayNumber: LD HL,(D_FILE) ; Work out the screen address for the moves left value ADD HL,DE LD B,$1C ; B holds the tens digit LD C,10 ; C holds the base for the displayed number _loop CP C ; Check if less than ten left JR C,_under10 ; If so, display current tens digit and then units digit SUB C ; Subtract 10, INC B ; Increment the tens digit, JR _loop ; and keep going _under10 ADD A,$1C ; Adjust units digit to '0' - '9' LD (HL),B ; Display the tens digit INC HL LD (HL),A ; Display the units digit RET ;------------------------------------------------------------ ; Display difficulty using the value in B ;------------------------------------------------------------ SCREEN_OFFSET_DIFF EQU 1+(1*33)+18 ; Line 1, column 18 DisplayDifficulty: .MODULE DD LD HL,sDifficultyMessage ; Display the difficulty message CALL DisplayMessage ;LD HL,sDifficultyMessageSimple ; DisplayMessage should leave HL here LD DE,sDifficultySubMsgLen ; Work out the address of the sub text LD A,B OR A JR Z,_copySubMsg ; If diffuculty is 0, skip _mult ADD HL,DE ; Add in difficulty * sub text length DJNZ _mult _copySubMsg PUSH DE ; Copy the sub text into the message EX DE,HL ; DE now holds the sub text address LD BC,SCREEN_OFFSET_DIFF ; Get the screen address of the difficulty display LD HL,(D_FILE) ADD HL,BC ; HL holds the screen address EX DE,HL ; Now DE screen, HL sub text POP BC ; And BC length LDIR RET ;------------------------------------------------------------ ; Clear any message that is currently displayed. ;------------------------------------------------------------ ClearMessage: LD A,(vMessageVisible) OR A ; Check if message visible = 0 RET Z ; Do nothing if not visible CALL GetMessageScreenAddress ; Get the screen address for the message LD BC,32 XOR A CALL FillArray ; Fill the line with zeros LD (vMessageVisible),A ; Remember no message visible now RET ; This table is used for fast division. It needs to be on a 256-byte boundary. Align256(sDivideBoardSize6x6) sDivideBoardSize6x6: DEFB 0,0,0,0,0,0 ; 0 - 5 DEFB 1,1,1,1,1,1 ; 6 - 11 DEFB 2,2,2,2,2,2 ; 12 - 17 DEFB 3,3,3,3,3,3 ; 18 - 23 DEFB 4,4,4,4,4,4 ; 24 - 29 DEFB 5,5,5,5,5,5 ; 30 - 35 Check256(sDivideBoardSize6x6) ;------------------------------------------------------------ ; Copy the entire screen from the location pointed to by HL ;------------------------------------------------------------ CopyScreen: .MODULE CS ; LD DE,(D_FILE) ; INC DE ; Get address of the first character on the screen ; LD BC,33*24-1 ; 24 rows of 33 columns, excluding the last newline ; LDIR ; Copy the data ; RET LD B,24 ; Process 24 lines _loop PUSH BC PUSH HL LD DE,(D_FILE) ; Copy the screen down one line INC DE LD HL,33 ADD HL,DE LD BC,33*23 LDIR POP HL ; Copy the next line to the bottom of the screen LD B,32 ; Decompress 32 characters CALL Decompress POP BC DJNZ _loop ; Process all lines RET ;_copyLine: LD BC,32 ; Uncompressed version ; LDIR ; 7 bytes ; INC HL ; RET ; Decompress routine ; ; Compressed representation: ; ; 00xxxxxx - character literal ; ; 010nnnnn C - run length of following character nnnnn + 1 * C (1-32) ; 011nnnnn - run length of spaces ($00) nnnnn + 1 * ' '(1-32) ; ; 10xxxxxx - character literal (inverse) ; ; 11nnnnnn - dictionary entry nnnnnn, trailing space D(nnnnnn) ' ' (0-61) ; 11111110 E - dictionary entry E, trailing space D(E) ' ' (0-255) ; 11111111 E - dictionary entry E, no trailing space D(E) (0-255) ; ; This routine is 100 bytes, so need to save at least 93 bytes on compressed display to ; break even. ; In fact, we save something like 4758 bytes using this routine (!) Decompress: .MODULE DECOMP _copyNext: LD A,(HL) BIT 6,A ; Check for a special code, indicated by bit 6 set JR NZ,_special LD (DE),A INC DE _copyLoop: INC HL DJNZ _copyNext ; Keep going until line complete RET _special BIT 7,A ; Check for dictionary entry with bit 7 JR NZ,_dictionary BIT 5,A ; Check for a run of non-zeros, indicated by bit 5 unset JR NZ,_runOfZeros AND $1F LD C,A ; Get the length of the run - 1 INC HL LD A,(HL) ; Get the run character JR _runStart _runOfZeros AND $1F LD C,A ; Get the length of the run - 1 XOR A ; Run of zeros _runStart INC C ; Get the length of the run _runLoop LD (DE),A ; Copy the character INC DE DEC B ; Decrement characters left for this line DEC C ; Decrement characters left for this run JR NZ,_runLoop ; Continue for run length INC B ; Adjust for DJNZ in copy loop JR _copyLoop _dictionary LD C,1 ; Indicate trailing space CP $FE JR Z,_dictionaryNext ; $FE indicates next byte is entry, trailing space CP $FF JR Z,_dictionaryNextNoTS ; $FF indicates next byte is entry, no trailing space AND $3F ; Get the entry _expandDictionary PUSH HL ; Save compressed data location PUSH BC ; Save characters left, trailing space LD HL,sDictionary ; Get the address of the start of the dictionary LD B,0 OR A JR Z,_foundEntry ; Found the dictionary entry ; Replaced this with faster version at the cost of one extra byte per dictionary entry ;_findEndLoop LD B,(HL) ; Get the next character in the entry ; INC HL ; BIT 6,B ; Check bit 6 for the end of the entry ; JR Z,_findEndLoop ; If not set, keep looking ; DEC A ; One less entry to go ; JR NZ,_findEndLoop ; Continue until A is zero _findEndLoop LD C,(HL) ; Get the next entry length ADD HL,BC ; Skip it DEC A ; One less entry to go JR NZ,_findEndLoop ; Continue until A is zero ; At this point we can make use of the fact that (HL) contains the length ; of the entry to be copied + 1, and after loading that, HL, DE, and BC are set up for LDIR. ; Here's the alternative version - 22 bytes. _foundEntry LD C,(HL) ; Get distance to next entry DEC C ; Actual length is 1 less PUSH BC ; Save entry length in C INC HL LDIR POP HL ; HL no longer needed, length in L POP BC ; Restore characters left, trailing space LD A,B SUB L INC A ; Account for decrement in DJNZ LD B,A DEC C ; Check if a space is needed (C=1) JR NZ,_noTrailingSpace ; Z here indicates a space is needed XOR A LD (DE),A ; Store the space INC DE DEC B ; One less character to decompress _noTrailingSpace POP HL ; Restore compressed data location JR _copyLoop ; Here's the original version that relies on the end of entry marker - 26 bytes ; ;_foundEntry POP BC ; Restore characters left, trailing space ;_expandLoop LD A,(HL) ; Get the next character in the entry to expand ; BIT 6,A ; Check if its the last character ; JR NZ,_expandLast ; LD (DE),A ; Store the character as is ; INC DE ; DEC B ; One less character to decompress ; INC HL ; Move on to the next character ; JR _expandLoop ;_expandLast AND $BF ; Mask out the end of entry marker ; LD (DE),A ; Store the character ; INC DE ; Note no decrement of B as that will be done in _copyLoop ; DEC C ; Check if a space is needed (C=1) ; JR NZ,_noTrailingSpace ; Z here indicates a space is needed ; XOR A ; LD (DE),A ; Store the space ; INC DE ; DEC B ; One less character to decompress ;_noTrailingSpace ; POP HL ; Restore compressed data location ; JR _copyLoop _dictionaryNextNoTS DEC C ; Indicate no trailing space _dictionaryNext INC HL LD A,(HL) ; Get the entry number JR _expandDictionary ; Expand the entry ;------------------------------------------------------------ ; Copy part of the screen from the location pointed to by HL. ; DE points to the screen location. ; BC contains the number of rows (B) and columns (C). ;------------------------------------------------------------ CopyPartialScreen: .MODULE CPS PUSH HL ; Remember HL LD HL,(D_FILE) ; Get screen address in DE ADD HL,DE EX DE,HL POP HL _loop PUSH BC ; Save number of rows and columns LD B,C CALL Decompress POP BC ; Restore number of rows and columns LD A,33 ; Increase DE by 33-C SUB C ADD A,E LD E,A JR NC,_next ; Check if addition affects high byte INC D ; Increment it if there is carry _next DJNZ _loop ; Do the next row RET ;------------------------------------------------------------ ; Get the screen address and length for displaying a message ;------------------------------------------------------------ SCREEN_OFFSET_MESSAGE EQU 1+(1*33) ; Line 1, column 0 GetMessageScreenAddress: LD HL,(D_FILE) ; Get the address of the display file LD DE,SCREEN_OFFSET_MESSAGE ADD HL,DE ; Work out the address of the message on the screen RET ;------------------------------------------------------------ ; Get the current key press in A ; Returns 1 if no key pressed ;------------------------------------------------------------ GetKey: CALL KSCAN ; Scan keyboard LD B,H LD C,L LD D,C INC D RET Z ; Z set indicates no key CALL FINDCHR ; Translate to a character value LD A,(HL) ; Get the character value OR A ; Ensure Z cleared if A non-zero RET ; Means space is not considered a key... ;------------------------------------------------------------ ; Invert a row of characters at address HL for B characters ;------------------------------------------------------------ InvertHoriz: .MODULE IH LD C,128 ; Bit pattern used to invert a character _loop LD A,(HL) ; Get the character from screen XOR C ; Invert it LD (HL),A ; Put the inverted character back on the screen INC HL ; Next character in the row DJNZ _loop ; Keep going as far as required RET ;------------------------------------------------------------ ; Wait for any key press and release. ;------------------------------------------------------------ GetKeyPress: LD HL,(vRandomSeed) INC HL LD (vRandomSeed),HL CALL GetKey ; Get the currently pressed key JR Z,GetKeyPress ; Z set if no key pressed ; Intentionally drop through to WaitForNoKey ;------------------------------------------------------------ ; Wait for key release. ;------------------------------------------------------------ WaitForNoKey: .MODULE WFK PUSH AF ; Save the key value _loop CALL GetKey ; Check if any key still pressed JR NZ,_loop ; Z set if no key pressed POP AF ; Restore the key value RET ;------------------------------------------------------------ ; Flash the message on line 1 until a key is pressed. ;------------------------------------------------------------ FlashMessage: LD B,32 ; Flash the message line until key pressed LD HL,(D_FILE) ; Work out the address of the message on screen LD DE,SCREEN_OFFSET_MESSAGE ADD HL,DE JR WaitForKeyFlash ; This table is used for fast remainder. It needs to be on a 256-byte boundary. Align256(sModBoardSize6x6) sModBoardSize6x6: DEFB 0,1,2,3,4,5 ; 0 - 5 DEFB 0,1,2,3,4,5 ; 6 - 11 DEFB 0,1,2,3,4,5 ; 12 - 17 DEFB 0,1,2,3,4,5 ; 18 - 23 DEFB 0,1,2,3,4,5 ; 24 - 29 DEFB 0,1,2,3,4,5 ; 30 - 35 Check256(sModBoardSize9x9) ; Enforce the fact that we rely on sModBoardSize6x6 being 256 bytes beyond sDivideBoardSize6x6 #IF sModBoardSize6x6 - sDivideBoardSize6x6 != 256 !!! #ENDIF ;------------------------------------------------------------ ; Wait for any key press, flashing the line at HL, length B ; Not very pretty..... ;------------------------------------------------------------ WaitForKeyFlash: LD A,(HL) ; Check for a character at the given location OR A RET Z ; If no character, return having done nothing PUSH HL ; Save screen address PUSH BC ; Save length of message CALL InvertHoriz ; Invert B characters HALT ; Wait for screen update POP BC ; Restore length of message POP HL ; Restore screen address PUSH HL ; Save screen address PUSH BC ; Save length of message CALL InvertHoriz ; Re-invert HALT ; Wait for screen update CALL GetKey ; Check to see if a key has been pressed POP BC ; Restore length of message POP HL ; Restore screen address JR Z,WaitForKeyFlash ; No key pressed - keep flashing JR WaitForNoKey ; Wait until the key is released ;------------------------------------------------------------ ; Invert the currently selected row and column. ;------------------------------------------------------------ InvertRowColumn: LD A,(vUpdateDisplay) OR A ; Check if display updates = 0 RET Z ; Do nothing if display updates is zero LD A,(vCurrentRow) ; Work out the screen address for the start of the row LD BC,(vBoardColRowOffset) ; b = row offset, c = col offset ADD A,A ADD A,B ; A = r*2+row offset LD B,A ; row = r*2+row offset, col = col offset CALL GetScreenAddress ; B = row, C = col LD A,(vBoardSize) ADD A,A DEC A LD B,A ; boardsize * 2 - 1 characters CALL InvertHoriz ; Invert the row LD A,(vCurrentColumn) ; Work out the screen address for the start of the column LD BC,(vBoardColRowOffset) ; b = row offset, c = col offset ADD A,A ADD A,C ; A = c*2+col offset LD C,A ; col = column c*2+col offset, row = row offset CALL GetScreenAddress ; B = row, C = col LD A,(vBoardSize) ADD A,A DEC A LD B,A ; boardsize * 2 - 1 characters ; Intentionally drop through to InvertVert ;------------------------------------------------------------ ; Invert a row of characters at address HL for B characters ;------------------------------------------------------------ InvertVert: .MODULE IV LD DE,33 ; Number of characters per line LD C,128 ; Bit pattern used to invert a character _loop LD A,(HL) ; Get the character from the screen XOR C ; Invert the character LD (HL),A ; Put the inverted character back on the screen ADD HL,DE ; Next character in the column DJNZ _loop ; Do all required rows RET ;------------------------------------------------------------ ; Disable all display updates, including number placement ;------------------------------------------------------------ DisableAllDisplayUpdates: XOR A LD (vDisplayNumbers),A ; Intentionally drop through to DisableDisplayUpdate ;------------------------------------------------------------ ; Disable display updates. ;------------------------------------------------------------ DisableDisplayUpdate: CALL InvertRowColumn ; Uninvert the current row/column XOR A LD (vUpdateDisplay),A ; Remember display update is disabled LD HL,(vCurrentRowColumn) ; Save the current row/column LD (vSavedRowColumn),HL RET ;------------------------------------------------------------ ; Enable all display updates, including number placement ;------------------------------------------------------------ EnableAllDisplayUpdates: LD A,1 LD (vDisplayNumbers),A JR EnableDisplayUpdate ;------------------------------------------------------------ ; Enable display updates. ;------------------------------------------------------------ EnableDisplayUpdate: LD A,1 ; Remember display update is enabled LD (vUpdateDisplay),A CALL DisplayMovesLeft ; Ensure moves left and options displayed LD BC,(vSavedRowColumn) ; Re-select the row/column that was selected Call SelectCurrentRowColumn ; when display updates were disabled JR InvertRowColumn ; Re-invert the current row/column ; RET merged with CALL ;------------------------------------------------------------ ; Get screen address. B=row, C=column ;------------------------------------------------------------ GetScreenAddress: .MODULE GSA LD HL,(D_FILE) ; Get the address of the display file INC HL ; Skip first byte LD A,B OR A ; Check if line number = 0 JR Z,_doneLines ; Skip first line LD DE,33 ; Number of characters per line _addLine ADD HL,DE DJNZ _addLine ; Add 33 per line _doneLines ADD HL,BC ; Add in the column (B = 0 by now) RET ;------------------------------------------------------------ ; ; End of UI routines... ; ;------------------------------------------------------------ ;------------------------------------------------------------ ; ; Start hand compilation of Java code to Z80 ASM... ; Lines with C have been hand-compiled ; ; As a general rule, methods are called with parameters in ; registers. A and HL are avoided for carrying parameters ; as they are generally used for calculations, however ; A is quite often used as the return value as that is likely ; to then be used immediately. ; For consistency, the following are the usual uses of registers: ; A working, return from method call ; B column ; C row ; D ; E array index, board address ; H working ; L working ; ; Methods trash register values; its up to the callers to ; protect values that they depend on. ; Methods may store parameter values at fixed addresses; this ; doesn't support recursive behaviour. ; ;------------------------------------------------------------ ;------------------------------------------------------------ ; SelectCurrentRowColumn ; ; Select the cell identified by the given row and column. ; ; These values are used until a different cell is selected. ; ; Registers on input: Registers on output: ; A A boardAddress ; B column B column (unchanged) ; C row C row (unchanged) ; D D unchanged ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ SelectCurrentRowColumn: ;CALL TestValidateCoords ; TEST only: validate coordinates LD (vCurrentRowColumn),BC LD HL,(vMultBoardSize) ; Work out the board address of the row/column LD L,C ; vMultBoardSize is on a 256-byte boundary LD A,(HL) ADD A,B ; A = row * vBoardSize + col LD (vCurrentBoardAddress),A RET ;------------------------------------------------------------ ; SelectCurrentBoardAddress ; ; Select the cell identified by the board address ; ; Registers on input: Registers on output: ; A A trashed ; B B column ; C C row ; D D unchanged ; E board address E board address ; H H trashed ; L L trashed ;------------------------------------------------------------ SelectCurrentBoardAddress: ;CALL TestValidateBA ; TEST only: validate board address ; Inlined: CALL GetRowColumnNumber ; Get row/column from board address LD HL,(vDivideBoardSize) ; Divide the address by the board size LD L,E ; vDivideBoardSize is on a 256-byte boundary LD C,(HL) ; C = getRowNumber(boardAddress) INC H ; vModBoardSize must be 256 greater than vDivideBoardSize LD B,(HL) ; B = getColumnNumber(boardAddress) ; Intentionally drop through to SelectCurrentRowColumnAddress ;------------------------------------------------------------ ; SelectCurrentRowColumnAddress ; ; Select the cell identified by the row and column and board address ; ; These values are used until a different cell is selected. ; ; Registers on input: Registers on output: ; A A board address ; B column B column (unchanged) ; C row C row (unchanged) ; D D unchanged ; E board address E board address (unchanged) ; H H unchanged ; L L unchanged ;------------------------------------------------------------ SelectCurrentRowColumnAddress: ;CALL TestValidateCoords ; TEST only: validate coordinates LD (vCurrentRowColumn),BC ; Save row and column LD A,E LD (vCurrentBoardAddress),A ; Save current board address RET ;------------------------------------------------------------ ; GetRowColumnNumber ; ; Optimised version of separate getRowNumber/getColumnNumber ; ; Registers on input: Registers on output: ; A A unchanged ; B B column ; C C row ; D D unchanged ; E board address E board address (unchanged) ; H H trashed ; L L trashed ;------------------------------------------------------------ GetRowColumnNumber: LD HL,(vDivideBoardSize) ; Divide the address by the board size LD L,E ; vDivideBoardSize is on a 256-byte boundary LD C,(HL) ; C = getRowNumber(boardAddress) INC H ; vModBoardSize must be 256 greater than vDivideBoardSize LD B,(HL) ; B = getColumnNumber(boardAddress) RET ;------------------------------------------------------------ ; GetBlockNumber ; ; Returns the block number for the given board address. ; ; Registers on input: Registers on output: ; A A block number ; B B unchanged ; C C unchanged ; D D unchanged ; E board address E board address (unchanged) ; H H trashed ; L L trashed ;------------------------------------------------------------ GetBlockNumber: ;LD H,sRemoveMod3DataHigh ; Set A to be ((int)row/3, assuming 0 <= row <= 8 ;LD L,C ; sRemoveMod3Data is on a 256-byte boundary ;LD A,(HL) ; A is now ((int)row/3)*3 ;INC H ; sDiv3Data is 256 bytes beyond sRemoveMod3Data ;LD L,B ; sDiv3Data is on a 256-byte boundary ;ADD A,(HL) ; Now add (int)column/3 LD HL,(vBlockNumbers) LD L,E ; vBlockNumbers is on a 256-byte boundary LD A,(HL) RET ;------------------------------------------------------------ ; GetBlockAndCellNumber ; ; Returns the block and cell number for a given row and column ; ; Registers on input: Registers on output: ; A A trashed ; B column B column (unchanged) ; C row C row (unchanged) ; D D cell number ; E E block number ; H H trashed ; L L trashed ;------------------------------------------------------------ GetBlockAndCellNumber: LD HL,(vMultBoardSize) ; Work out the board address of the row/column LD L,C ; vMultBoardSize is on a 256-byte boundary LD A,(HL) ADD A,B ; A = row * vBoardSize + col LD HL,(vBlockNumbers) LD L,A ; vBlockNumbers is on a 256-byte boundary LD E,(HL) ; E = block number for this cell LD HL,(vCellNumbers) LD L,A ; vCellNumbers is on a 256-byte boundary LD D,(HL) ; D = cell number within the block for this cell RET ;------------------------------------------------------------ ; RemoveOption, RemoveOption2 ; ; Remove the given option from the currently selected cell. ; ; The second entry point requires the board address in E. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D option D trashed ; E [board address] E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ RemoveOption: LD A,(vCurrentBoardAddress) ; Get the current board address LD E,A RemoveOption2: LD B,D ; The option to be removed LD C,128 ; Modify the option value by 128 to indicate manually removed JP UpdateOption ; Update the option value and its effects ; RET merged with CALL ; Multiples of the board size. ; Must be on a 256-byte boundary. Align256(sMultBoardSize9x9) sMultBoardSize9x9: DEFB 0,9,18,27,36,45,54,63,72 ; can't reuse mult sq as that's words Check256(sMultBoardSize9x9) ;------------------------------------------------------------ ; GetOptionAddress, GetOptionAddress0 ; ; Get the address of the given option. ; ; Registers on input: Registers on output: ; A option number A trashed ; B B unchanged ; C C unchanged ; D D unchanged ; E boardAddress E boardAddress (unchanged) ; H H } optionAddress ; L L } ;------------------------------------------------------------ GetOptionAddress: .MODULE GOA DEC A ; This is used when the option is 1-based GetOptionAddress0: ; This is used when the option is zero-based LD HL,(vMultBoardSizeSq) ; Multiply the board address by board size LD L,E ; vMultBoardSizeSq is on a 256-byte boundary SLA L ; Entries are 2 bytes each ADD A,(HL) ; A = low byte of option address + option number INC HL ; Move on to the high byte LD H,(HL) ; Get the high byte of multiplied value JR NC,_notCrossing ; Check if add resulted in carry (INC and LD don't affect C flag) INC H ; Add carry to the high byte of the multiplied value _notCrossing LD L,A ; Get the low byte of the multiplied value LD A,vOptionsHigh ; Add in the address of the options table ADD A,H LD H,A ; Now we've got the address of the option value RET ;------------------------------------------------------------ ; GetOptionAddressBase ; ; Get the base address for options for the given address. ; ; Registers on input: Registers on output: ; A A trashed ; B B unchanged ; C C unchanged ; D D unchanged ; E boardAddress E boardAddress (unchanged) ; H H } optionAddress ; L L } ;------------------------------------------------------------ GetOptionAddressBase: ; This is used when the option is zero-based LD HL,(vMultBoardSizeSq) ; Multiply the board address by board size LD L,E ; vMultBoardSizeSq is on a 256-byte boundary SLA L ; Entries are 2 bytes each LD A,(HL) ; A = low byte of option address + option number INC HL ; Move on to the high byte LD H,(HL) ; Get the high byte of multiplied value LD L,A ; Get the low byte of the multiplied value LD A,vOptionsHigh ; Add in the address of the options table ADD A,H LD H,A ; Now we've got the address of the option value RET ;------------------------------------------------------------ ; InitializeBoard ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ InitializeBoard: .MODULE IB LD A,(vBoardType) CP SIX_BY_SIX JR Z,_6x6 LD A,9 ; Only 6x6 and 9x9 supported at the moment. LD (vBoardSize),A ; Store the board size LD HL,sLookupTables9x9 ; Set up all the lookup tables to use the 9x9 versions. LD DE,vLookupTables LD BC,vLookupTablesLength LDIR LD HL,9*9 LD A,L LD (vBoardSizeSquared),A ; Remember the board size squared value LD A,9*(9-1)/2 ; The sum of option values 0 to 8 LD (vBoardSizeValueSum),A ; Store this for later use LD A,sNumBoards9x9 ; Remember the number of pre-set boards LD (vNumBoards),A JR ResetBoard _6x6: LD A,6 LD (vBoardSize),A ; Store the board size LD HL,sLookupTables6x6 ; Set up all the lookup tables to use the 6x6 versions. LD DE,vLookupTables LD BC,vLookupTablesLength LDIR LD HL,6*6 LD A,L LD (vBoardSizeSquared),A ; Remember the board size squared value LD A,6*(6-1)/2 ; The sum of option values 0 to 6 LD (vBoardSizeValueSum),A ; Store this for later use LD A,sNumBoards6x6 ; Remember the number of pre-set boards LD (vNumBoards),A JR ResetBoard ResetBoard: LD A,(vBoardSizeSquared) ; General initialization for any size board LD (vMovesLeft),A ; Moves left is number of cells on the board XOR A LD (vMoves),A ; Number of moves is 0 INC A LD (vSetupMode),A ; Start in setup mode LD HL,vComputationData ; Fill all data arrays with zeros to begin with LD BC,vComputationDataLength XOR A CALL FillArray LD HL,vOptions ; Indicate that no options available by pencil marks LD BC,vOptionsLen LD A,PENCILLED_FLAG CALL FillArray LD A,(vBoardSizeSquared) ; Get the size of the array LD C,A LD B,0 LD A,(vBoardSize) ; Fill vRowOptionCounts with vBoardSize LD HL,vRowOptionCounts CALL FillArray LD HL,vColumnOptionCounts ; Fill vColumnOptionCounts with vBoardSize CALL FillArray LD HL,vBlockOptionCounts ; Fill vBlockOptionCounts with vBoardSize CALL FillArray LD HL,vCellOptionCounts ; Fill vCellOptionCounts with vBoardSize CALL FillArray LD A,(vBoardSizeValueSum) ; Fill vRowOnlyOptionColumns with vBoardSizeValueSum LD HL,vRowOnlyOptionColumns CALL FillArray LD HL,vColumnOnlyOptionRows ; Fill vColumnOnlyOptionRows with vBoardSizeValueSum CALL FillArray LD HL,vBlockOnlyOptionCells ; Fill vBlockOnlyOptionCells with vBoardSizeValueSum ; Intentionally drop through to FillArray #IF $ != FillArray !!! #ENDIF ;------------------------------------------------------------ ; FillArray ; ; Fill an area of memory with the given value. ; ; Registers on input: Registers on output: ; A value A value (unchanged) ; B } size B } size (unchanged) ; C } C } ; D D trashed ; E E trashed ; H } address H trashed ; L } L trashed ;------------------------------------------------------------ FillArray: PUSH BC ; Store the number of elements LD (HL),A ; Store first value in source DEC BC ; Done one LD D,H ; Destination is next value LD E,L INC DE LDIR ; Copy source to destination POP BC ; Restore the number of elements RET ;------------------------------------------------------------ ; GetHint ; ; Get a hint for the next move to make. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ GetHint: CALL FindNextMove ; Find the next possible move RET Z ; Return if no move LD (vSavedRowColumn),BC ; Ensure the cell gets selected RET ;------------------------------------------------------------ ; SolveNext ; ; Solve one cell. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ SolveNext: .MODULE SN CALL FindNextMove ; Find the next possible move RET Z ; No move possible CP FIND_MOVE_PLACE ; Next call doesn't affect flags LD (vSavedRowColumn),BC ; Ensure the cell gets selected JR Z,SelectRCAAndPlace ; Select the identified row/column/address and place the number LD A,D ; Intentionally drop through to PencilOption #IF $ != PencilOption !!!SolveNext/PencilOption #ENDIF ;------------------------------------------------------------ ; PencilOption ; ; Pencil in the given option as available in the selected cell. ; ; Registers on input: Registers on output: ; A option A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ; ;------------------------------------------------------------ PencilOption: LD DE,(vCurrentBoardAddress) ; Get the current board address CALL GetOptionAddress LD A,(HL) ; Get the current option value XOR PENCILLED_FLAG ; Flip pencilled flag for the option LD (HL),A ; Update the option value RET ; This table maps from board address to the block number of which that cell is a member. ; Needs to be on a 256-byte boundary. Align256(sBlockNumbers9x9) sBlockNumbers9x9: DEFB 0,0,0,1,1,1,2,2,2 ; 0 - 8 DEFB 0,0,0,1,1,1,2,2,2 ; 9 - 17 DEFB 0,0,0,1,1,1,2,2,2 ; 18 - 26 DEFB 3,3,3,4,4,4,5,5,5 ; 27 - 35 DEFB 3,3,3,4,4,4,5,5,5 ; 36 - 44 DEFB 3,3,3,4,4,4,5,5,5 ; 45 - 53 DEFB 6,6,6,7,7,7,8,8,8 ; 54 - 62 DEFB 6,6,6,7,7,7,8,8,8 ; 63 - 71 DEFB 6,6,6,7,7,7,8,8,8 ; 72 - 80 Check256(sBlockNumbers9x9) ;------------------------------------------------------------ ; SelectAndPlace, SelectRCAAndPlace ; ; Select the current cell and place the given value there. ; NZ on return indicates that the placement failed. ; ; PlaceAtSelected, PlaceAtSelected2 ; ; Place the given value in the currently selected cell. ; NZ on return indicates that the placement failed. ; ; PlaceAtSelected2 can be called if E already contains ; the current board address. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D number D trashed ; E [board address] E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ PlaceAtSelected: .MODULE PAS LD A,(vCurrentBoardAddress) LD E,A ; E = current board address JR PlaceAtSelected2 SelectAndPlace: CALL SelectCurrentBoardAddress JR PlaceAtSelected2 SelectRCAAndPlace: CALL SelectCurrentRowColumnAddress JR PlaceAtSelected2 PlaceAtSelected2: LD A,D ; A = number to be placed CALL GetOptionAddress ; Check if the option is available in this cell LD A,(HL) ; A = vOptions[optionAddress] AND PENCILLED_MASK ; Check if option value (ignoring pencilled bit) = 0 RET NZ ; NZ indicates failure LD B,vBoardContentsHigh ; If there is already a number in this cell, clear it. LD C,E LD A,(BC) ; Get the cell contents OR A ; Check if cell contents = 0 JR Z,_storeMove ; If empty, cell does not need to be cleared PUSH DE ; Save number, board address PUSH BC ; Save cell address LD A,1 ; A indicates history needs to be updated CALL ClearSelected ; Clear the currently selected cell POP BC ; Resource cell address POP DE ; Restore number, board address RET NZ ; Failed to clear the cell _storeMove LD A,(vMoves) ; Get the address of the current move in the history LD H,vHistoryHigh LD L,A ; History is on a 256-byte boundary LD (HL),E ; Store the board address of the move LD A,(vSetupMode) OR A ; Check if setup mode = 0 LD A,D ; A = number JR Z,_notSetupMode OR $F0 ; A = number | 0xf0 _notSetupMode LD (BC),A ; Store the number on the board LD HL,vMoves INC (HL) ; Increment the number of moves INC HL ; vMovesLeft is immediately after vMoves in memory DEC (HL) ; Decrement the number of moves left LD B,D LD C,1 ; B = number, c = 1 (indicates number placed) CALL UpdateOptions ; Update the valid options having placed the number CALL UpdateCurrentCellDisplay ; Update the current cell display XOR A ; Ensure Z flag set to indicate success RET ;------------------------------------------------------------ ; IsUnique ; ; Check if there is a unique solution. ; On return Z flag set indicates unique solution. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ IsUnique: .MODULE IU LD A,1 LD (vCheckUnique),A ; Indicate uniqueness checking CALL FindBruteForce ; Do a brute force search LD HL,vCheckUnique LD A,(HL) LD (HL),0 ; Reset the check unique flag CP 2 ; If unique, check unique returns 2 RET ; Z indicates success ;------------------------------------------------------------ ; Difficulty ; ; Work out the difficulty of the current board. ; Returns a value 0 .. 5 ; ; Registers on input: Registers on output: ; A A trashed ; B B The difficulty ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ Difficulty: .MODULE D XOR A LD (vDifficulty),A ; Reset difficulty level LD HL,vBoardContents ; Make a copy of the current board contents LD DE,vBoardCopy LD BC,9*9 LDIR CALL SolveAll ; Solve the entire board, working out the difficulty CALL ResetBoard ; Reset the board LD HL,vBoardCopy CALL LoadBoard2 ; Load back in the current board LD A,(vDifficulty) ; Get the difficulty determined LD C,16 ; Difficulty 5 indicated by bit 4 set LD B,5 _findDiff CP C ; See if current difficulty bit set RET NC ; If so, return the value CCF ; Look at next bit RR C DEC B ; And one less difficulty JR NZ,_findDiff ; Keep going if greater than zero RET ;------------------------------------------------------------ ; SolveAll ; ; Solve all cells in the puzzle. ; ; Registers on input: Registers on output: ; A A indicates whether solved or not ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ; ;------------------------------------------------------------ SolveAll: .MODULE SA LD A,1 LD (vSolvingAll),A ; Indicate that a solution is being sought _nextMove LD A,(vMovesLeft) OR A ; Check if moves left = 0 JR Z,_solveDone ; No moves left, so done LD (vNoExplanation),A ; Indicate no move explanation required CALL FindNextMove ; Try to find a move to make JR Z,_solveDone ; No move found LD A,(vMovesLeft) ; Check again whether fully solved OR A ; Check if moves left = 0 JR Z,_solveDone ; In which case no need to place number CALL SelectRCAAndPlace ; Select the row/column/address and place the value JR Z,_nextMove ; Z indicates success _solveDone XOR A ; Clear the solving all flag LD (vSolvingAll),A LD (vNoExplanation),A RET ; This table maps from board address to the cell number within a block. ; Needs to be on a 256-byte boundary. Align256(sCellNumbers9x9) sCellNumbers9x9: DEFB 0,1,2,0,1,2,0,1,2 ; 0 - 8 DEFB 3,4,5,3,4,5,3,4,5 ; 9 - 17 DEFB 6,7,8,6,7,8,6,7,8 ; 18 - 26 DEFB 0,1,2,0,1,2,0,1,2 ; 27 - 35 DEFB 3,4,5,3,4,5,3,4,5 ; 36 - 44 DEFB 6,7,8,6,7,8,6,7,8 ; 45 - 53 DEFB 0,1,2,0,1,2,0,1,2 ; 54 - 62 DEFB 3,4,5,3,4,5,3,4,5 ; 63 - 71 DEFB 6,7,8,6,7,8,6,7,8 ; 72 - 80 Check256(sCellNumbers9x9) ;------------------------------------------------------------ ; Undo ; ; Undo the last move made. ; On return, the Z flag is set if a move could be undone, i.e. Z => true ; ; TODO: enable undo of option removal and cell clearing ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ Undo: .MODULE U LD A,(vMoves) OR A ; Check if any moves made JR NZ,_movesNonZero ; Return if no moves made INC A ; Ensure Z not set RET ; Z indicates undo failed _movesNonZero DEC A ; Undo the previous move LD H,vHistoryHigh LD L,A ; History is on a 256-byte boundary LD E,(HL) ; Get the board address of the last move CALL SelectCurrentBoardAddress ; Select the board address as current XOR A ; Indicate history update not required ; Intentionally drop through to ClearSelected #IF $ != ClearSelected !!!Undo/ClearSelected #ENDIF ;------------------------------------------------------------ ; ClearSelected ; ; Clear the contents of the currently selected cell. ; On return, the Z flag is set if the cell could be cleared, i.e. Z => true ; ; Registers on input: Registers on output: ; A checkHistory A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ ClearSelected: .MODULE CS PUSH AF ; Store checkHistory value for later use LD HL,(vCurrentBoardAddress) ; Get the current board address LD B,(HL) ; Get the content of the current cell LD A,$F0 ; Check if the top nibble is set AND B ; A = number & 0xf0 JR Z,_okToClear ; If not set, then OK to clear the cell LD A,(vSetupMode) ; Check if in setup mode CP 1 ; 1 indicates setup mode, so can clear cell JR NZ,_doneNothing ; If not setup mode, return _okToClear LD A,$0F AND B ; Mask off the high nibble JR NZ,_occupied ; If number non-zero, then cell is occupied _doneNothing POP HL ; Discard checkHistory value INC A ; If number is 0, there is nothing to do RET ; Return NZ to indicate nothing was done. _occupied LD (HL),0 ; Clear the contents of the cell LD B,A ; B is the option number LD C,$FF ; Option modifier = -1 CALL UpdateOptions ; Remove the effect of this number on the options POP AF ; See whether to check history for this cell OR A ; Check if checkHistory = 0 JR Z,_historyDone ; If so, no need to modify history LD A,(vMoves) LD B,A DEC B ; If vMoves = 1, don't bother JR Z,_historyDone LD A,(vCurrentBoardAddress) ; Get the current board address LD HL,vHistory ; Start with the address of the first history value _historyLoop LD E,(HL) ; Get the history value CP E ; Compare with the current board address JR Z,_foundHistory ; If they match, then we need to remove this history element INC HL ; Move on to the next element in the hisory DJNZ _historyLoop ; Keep going until all moves made have been compared JR _historyDone ; The current board address was not found in the history _foundHistory LD D,H ; Copy values over the top of the entry in the history LD E,L INC HL LD C,B LD B,0 LDIR _historyDone LD HL,vMoves DEC (HL) ; Decrement the number of moves INC HL INC (HL) ; Increment the number of moves left CALL UpdateCurrentCellDisplay ; Update the current cell display XOR A ; Set Z flag to indicate success RET ;------------------------------------------------------------ ; LoadBoard ; ; Loads a supplied board. ; ; The second entry point requires the address of board data ; in HL. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H } [Board data] H trashed ; L } L trashed ;------------------------------------------------------------ LoadBoard: .MODULE LB CALL Undo ; Undo sets Z flag if successful JR Z,LoadBoard ; Keep going until all moves undone _undoDone LD A,(vNumBoards) LD C,A ; C = num boards LD DE,(vBoardSizeSquared) ; Get the size of single board's data LD HL,(vBoardData) ; Get the address of the first board LD A,(vCurrentBoard) OR A ; Check if current loaded board = 0 JR Z,_found ; If so, then we've got the address CP C ; Check if over the number of boards LD B,A ; Iterate for the number of boards JR NZ,_findLoop ; Find the board address in memory XOR A ; Go back to the first board JR _found ; And already got the address _findLoop ADD HL,DE ; Move on by the size of data for a single board DJNZ _findLoop _found INC A ; Increment the board number CP C ; Check if we need to wrap round to zero JR NZ,_updateBoardNo ; If not, start loading the board XOR A ; Wrap the board number round to zero _updateBoardNo LD (vCurrentBoard),A ; Store the next board number LD B,E ; Get the number of cells in a board _startLoading LD E,0 ; Start with board address 0 _loop PUSH DE ; Save current board address PUSH BC ; Save number of cells to place LD A,(HL) ; Get the cell value to place OR A ; Check if cell value to place = 0 JR Z,_next ; If so, skip placing the value AND $0F ; Just use the low nibble LD D,A PUSH HL CALL SelectAndPlace ; Select cell and place the number there POP HL ; Restore the address of board data _next POP BC ; Restore the number of cells to place POP DE ; Restore the current board address INC E ; Move on to the next cell INC HL ; Move on to the next board data address DJNZ _loop ; Keep going for all cells on the board RET LoadBoard2: LD A,(vBoardSizeSquared) ; Get the size of a single board's data LD B,A JR _startLoading ; Load up the board. ;------------------------------------------------------------ ; GenerateBoard ; ; Generates a new board. ; ; Registers on input: Registers on output: ; A A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H H trashed ; L L trashed ;------------------------------------------------------------ SCREEN_OFFSET_CELLS_LEFT EQU 1+(33*1)+30 GenerateBoard: .MODULE GB LD A,(vSetupMode) ; Only works in setup mode OR A RET Z CALL FAST _clearBoard CALL Undo ; Undo all moves made so far JR Z,_clearBoard ; LD A,(vBoardSizeSquared) ; LD DE,SCREEN_OFFSET_CELLS_LEFT ; CALL DisplayNumber ; Display the number of cells left LD HL,vGenerateRandomCells ; This will hold a random sequence of columns LD A,(vBoardSize) CALL RandomFill ; Generate the random column sequence ; Try to randomly fill the puzzle by iterating through the ; rows, placing each number. ; i.e. try placing 1 in each row in turn, then move on to 2; keep ; going until all numbers placed in all rows. Step back if a ; number can't be placed and try a different position. XOR A LD (_vRow),A ; Initialize row (0-based) LD (_vNumber),A ; Initialize number (0-based) LD HL,vGenerateStack+1 ; HL points to the stack of available columns LD (_vStackPointer),HL ; Save the stack pointer LD (HL),$FF ; Stack always starts with 0xFF _fillNext LD HL,(_vStackPointer) DEC HL ; Mark the previous stack value SET 7,(HL) ; as being the last one for the previous row. LD HL,(vBARowStarts) ; Get the start offset for the current row LD A,(_vRow) ADD A,L ; vBARowStarts doesn't cross a 256-byte boundary LD L,A LD C,(HL) ; Get the start offset LD A,(vBoardSize) ; Process all columns in the row LD B,A LD HL,vGenerateRandomCells ; This table holds the randomized column numbers _cellLoop LD A,(HL) ; Get the next randomised column number ; Don't actually need to add in row cell offsets as RowCellOffsets(A) always = A ; LD DE,(vBARowCellOffsets) ; Get the column offset ; ADD A,E ; vBARowCellOffsets doesn't cross a 256-byte boundary ; LD E,A ; LD A,(DE) ; Get the cell offset ADD A,C ; Add the start offset LD E,A ; Check if the cell is available LD D,vBoardContentsHigh ; vBoardContents is on a 256-byte boundary LD A,(DE) ; Get the contents of the cell OR A JR NZ,_nextCell ; Cell is not available, so skip LD A,(_vNumber) ; Get the number being placed PUSH HL ; Save the randomised column number address CALL GetOptionAddress0 ; Get the option address for the number LD A,(HL) AND PENCILLED_MASK ; Check if the option is available JR NZ,_nextCell2 ; Option is not available in this cell, so skip LD HL,(_vStackPointer) ; Put the board address of the cell on the stack LD (HL),E INC HL LD (_vStackPointer),HL ; Increment the stack pointer _nextCell2 POP HL ; Restore the randomized column number address _nextCell INC HL ; Move on to the next randomized column number address DJNZ _cellLoop ; Process all columns LD HL,(_vStackPointer) ; Check if any values have been added to the stack DEC HL ; If the top value has bit 7 set, then we've not BIT 7,(HL) ; added any values JR Z,_placeValue ; We've found at least one available cell, so place the current number _undoLastMove PUSH HL ; Save the stack pointer address - 1 CALL Undo ; Undo the last move LD HL,_vRow LD A,(HL) ; Decrement the row number OR A JR Z,_decreaseNumber ; If already zero, then decrement the number DEC (HL) ; Just decrement the row JR _undoNext ; See whether more moves need to be undone _decreaseNumber LD A,(vBoardSize) ; Set the row number to board size - 1 DEC A LD (HL),A LD HL,_vNumber ; Decrement the number DEC (HL) _undoNext POP HL ; Restore the stack pointer address - 1 LD A,(HL) ; Get the top value on the stack CP $FF JR NZ,_placeValue ; If it's not $FF, then stop undoing LD (_vStackPointer),HL ; Otherwise remove the value from the stack and keep DEC HL ; undoing, as there are no available values for the JR _undoLastMove ; previous row/number _placeValue LD A,(HL) ; Pop the top value off the stack DEC HL ; Check the previous value on the stack BIT 7,(HL) ; If that indicates more values (bit 7 not set) JR Z,_doneStack ; then just update the stack pointer INC HL ; Otherwise push $FF onto the stack to indicate LD (HL),$FF ; no more values available _doneStack INC HL ; Update the stack pointer LD (_vStackPointer),HL AND $7F ; Ignore the high bit LD E,A LD A,(_vNumber) INC A LD D,A CALL SelectAndPlace ; Select the cell and place the number there LD A,(_vRow) ; Increment the row number INC A LD HL,vBoardSize CP (HL) ; Check if it is equal to board size JR NZ,_fillLoop ; If not, keep going PUSH HL ; Save address of board size LD HL,_vNumber ; Increment the number INC (HL) POP HL ; Restore address of board size XOR A ; Set row to zero _fillLoop LD (_vRow),A LD A,(_vNumber) ; See if all numbers have been tried CP (HL) JP NZ,_fillNext ; If not, then try the next row ; So now we have a completely filled in, random solution. ; remove pairs of opposite values so long as there is a unique ; solution. LD HL,vGenerateRandomPairs ; Generate random cell numbers LD A,(vBoardSizeSquared) ; Do half of the board, as the other RR A ; half are 180 degree rotated PUSH AF ; Save half the board size CALL RandomFill ; Fill in a random sequence POP AF ; Restore half the board size LD B,A ; That's how many pairs to try LD HL,vGenerateRandomPairs _pairLoop LD A,(HL) ; Get the first cell of the pair PUSH BC ; Save the loop counter PUSH HL ; Save the cell pair array address ; PUSH AF ; Save the current cell offset ; LD A,B ; LD DE,SCREEN_OFFSET_CELLS_LEFT ; CALL DisplayNumber ; Display the number of cells left ; POP AF ; Restore the current cell offset LD HL,_vFirstCell ; Save the first cell offset, CALL _saveValueAndClear ; value, and then clear the cell LD A,(_vFirstCell) ; Work out the other cell offset LD C,A LD A,(vBoardSizeSquared) ; Other cell is DEC A ; board cells - 1 - first cell SUB C LD HL,_vSecondCell ; Save the second cell offset, CALL _saveValueAndClear ; value, and then clear the cell CALL IsUnique ; Check for a unique solution JR Z,_nextPair ; If so, carry on LD HL,_vFirstCell ; Otherwise replace the first value CALL _replaceValue LD HL,_vSecondCell ; And the second value CALL _replaceValue _nextPair POP HL ; Restore the cell pair array address POP BC ; Restore the loop counter INC HL ; Move on to the next value DJNZ _pairLoop ; Process all values LD A,(vBoardSizeSquared) ; Check if there are an odd number of cells BIT 0,A JR Z,_done ; If not, then nothing more to do. RR A ; Try to remove the middle cell value LD HL,_vFirstCell ; Save the offset, value, and CALL _saveValueAndClear ; clear the cell CALL IsUnique ; Check if there is still a unique solution JR Z,_done ; If so, then just return LD HL,_vFirstCell ; Otherwise replace the value and return CALL _replaceValue _done JP SLOW ; RET merged with CALL _replaceValue: LD E,(HL) ; Get the board address INC HL LD D,(HL) ; Get the value to replace JP SelectAndPlace ; Select the board address and place the value _saveValueAndClear: LD (HL),A ; Save the cell offset LD D,vBoardContentsHigh LD E,A ; Get the value in the cell LD A,(DE) AND $0F ; Ignore high nibble INC HL LD (HL),A ; Save the cell value CALL SelectCurrentBoardAddress ; Select the cell LD A,1 JP ClearSelected ; Clear the cell, updating history _vRow DEFB 0 _vNumber DEFB 0 _vStackPointer DEFW 0 _vFirstCell EQU _vRow _vFirstValue EQU _vNumber _vSecondCell EQU _vStackPointer _vSecondValue EQU _vStackPointer+1 ;------------------------------------------------------------ ; RandomFill ; ; Fill the given array with the required number of different ; values. ; ; Registers on input: Registers on output: ; A The number of values A trashed ; B B trashed ; C C trashed ; D D trashed ; E E trashed ; H } The address H trashed ; L } L trashed ;------------------------------------------------------------ RandomFill: .MODULE RF LD (_vArray),HL ; Save the array address LD B,A ; Fill the array with the values in order LD C,A ; Remember the number of values XOR A ; Start with zero _fill LD (HL),A ; Store the current value INC HL ; Move on to the next value and array entry INC A DJNZ _fill ; Keep going until the array is full LD B,C ; Now shuffle the array RL B ; Do it array size * 2 times _swap CALL GetRandom ; Get the first random value LD D,0 LD E,A LD HL,(_vArray) ADD HL,DE ; Work out the array address PUSH HL ; Save the first array address CALL GetRandom ; Get the second random value LD E,A LD HL,(_vArray) ADD HL,DE ; Work out the array address POP DE ; Restore the first array address PUSH BC ; Save the loop counter and array size LD B,(HL) ; Swap the two array values LD A,(DE) LD (HL),A LD A,B LD (DE),A POP BC ; Restore the loop counter and array size DJNZ _swap ; Keep going until all done RET _vArray DEFW 0 ;------------------------------------------------------------ ; GetRandom ; ;