diff --git a/build.js b/build.js index aaa24637..bd79b285 100644 --- a/build.js +++ b/build.js @@ -100,6 +100,7 @@ console.log(); // build / compress nametables console.time('nametables'); +process.env['GYM_FLAGS'] = compileFlags.join(' '); require('./src/nametables/build'); console.timeEnd('nametables'); diff --git a/src/constants.asm b/src/constants.asm index 79e759dd..f0560c57 100644 --- a/src/constants.asm +++ b/src/constants.asm @@ -101,9 +101,17 @@ MODE_LINECAP MODE_DASONLY MODE_QUAL MODE_PAL +.if KEYBOARD = 1 +MODE_KEYBOARD +.endif .endenum +.if KEYBOARD = 1 +MODE_QUANTITY = MODE_KEYBOARD + 1 +.else MODE_QUANTITY = MODE_PAL + 1 +.endif + MODE_GAME_QUANTITY = MODE_HARDDROP + 1 SCORING_CLASSIC := 0 ; for scoringModifier @@ -169,6 +177,9 @@ MENU_TOP_MARGIN_SCROLL := 7 ; in blocks .byte $1 ; MODE_DASONLY .byte $1 ; MODE_QUAL .byte $1 ; MODE_PAL +.if KEYBOARD = 1 + .byte $1 ; MODE_KEYBOARD +.endif .endmacro .macro MODENAMES diff --git a/src/gamemode/bootscreen.asm b/src/gamemode/bootscreen.asm index 7c7d63af..9d4fcdb3 100644 --- a/src/gamemode/bootscreen.asm +++ b/src/gamemode/bootscreen.asm @@ -15,6 +15,10 @@ gameMode_bootScreen: ; boot jsr updateAudioAndWaitForNmi jsr checkRegion +.if KEYBOARD = 1 + jsr detectKeyboard +.endif + .if !QUAL_BOOT ; check if qualMode is already set lda qualFlag diff --git a/src/gamemode/gametypemenu/menu.asm b/src/gamemode/gametypemenu/menu.asm index 9ce4695d..f4fb67b1 100644 --- a/src/gamemode/gametypemenu/menu.asm +++ b/src/gamemode/gametypemenu/menu.asm @@ -140,6 +140,7 @@ seedControls: lda #BUTTON_RIGHT jsr menuThrottle beq @skipSeedRight +@moveRight: lda #$01 sta soundEffectSlot1Init inc menuSeedCursorIndex @@ -151,7 +152,44 @@ seedControls: @skipSeedRight: lda menuSeedCursorIndex + +.if KEYBOARD = 1 +@kbSeedLow = generalCounter +@kbSeedHigh = generalCounter2 + bne @checkForKbSeedEntry + jmp @skipSeedControl +@checkForKbSeedEntry: + jsr readKbSeedEntry + bmi @noKeysPressed + sta @kbSeedLow + asl + asl + asl + asl + sta @kbSeedHigh + ldy menuSeedCursorIndex + dey + tya + lsr + tay + ; y = (index-1) // 2 + ; c = (index-1) % 2 + lda set_seed_input,y + bcc @highByte +; low byte: + and #$F0 + ora @kbSeedLow + bcs @storeSeed +@highByte: + and #$0F + ora @kbSeedHigh +@storeSeed: + sta set_seed_input,y + jmp @moveRight +@noKeysPressed: +.else beq @skipSeedControl +.endif lda menuSeedCursorIndex sbc #1 diff --git a/src/gamemodestate/handlegameover.asm b/src/gamemodestate/handlegameover.asm index ae1288d4..2a6a4afe 100644 --- a/src/gamemodestate/handlegameover.asm +++ b/src/gamemodestate/handlegameover.asm @@ -17,7 +17,14 @@ gameModeState_handleGameOver: @gameOver: lda #$03 sta renderMode +.if KEYBOARD = 1 + ; flag for keyboard poll to ignore mapped keys except start/return + inc highScoreEntryActive jsr handleHighScoreIfNecessary + dec highScoreEntryActive +.else + jsr handleHighScoreIfNecessary +.endif lda #$01 sta playState lda #$EF diff --git a/src/gamemodestate/initstate.asm b/src/gamemodestate/initstate.asm index dfd59d17..5bda81a6 100644 --- a/src/gamemodestate/initstate.asm +++ b/src/gamemodestate/initstate.asm @@ -299,7 +299,16 @@ L8824: ldx #rng_seed tay lda #EMPTY_TILE sta playfield,y +.if KEYBOARD = 1 + ; this can probably be the same whether keyboard or not. + ; the keyboard code adds the keyboard reading right before the oam staging reset. + ; the additional keyboard reading cycles causes the b type setup to crash. + ; Using the wait routine that skips the oam staging reset (and keyboard read) for now and + ; keeping separate until b-type board test is developed. + jsr updateAudioAndWaitForNmi +.else jsr updateAudioWaitForNmiAndResetOamStaging +.endif dec generalCounter bne L87E7 L884A: diff --git a/src/highscores/data.asm b/src/highscores/data.asm index c73575ad..7a1ceeb6 100644 --- a/src/highscores/data.asm +++ b/src/highscores/data.asm @@ -1,10 +1,51 @@ highScorePpuAddrTable: .dbyt $2284,$22C4,$2304 highScoreCharToTile: - .byte $FF,$0A,$0B,$0C,$0D,$0E,$0F,$10 - .byte $11,$12,$13,$14,$15,$16,$17,$18 - .byte $19,$1A,$1B,$1C,$1D,$1E,$1F,$20 - .byte $21,$22,$23,$00,$01,$02,$03,$04 - .byte $05,$06,$07,$08,$09,$25,$4F,$5E - .byte $5F,$6E,$6F,$52,$55,$24 + .byte $FF ; Space + .byte $0A ; A + .byte $0B ; B + .byte $0C ; C + .byte $0D ; D + .byte $0E ; E + .byte $0F ; F + .byte $10 ; G + .byte $11 ; H + .byte $12 ; I + .byte $13 ; J + .byte $14 ; K + .byte $15 ; L + .byte $16 ; M + .byte $17 ; N + .byte $18 ; O + .byte $19 ; P + .byte $1A ; Q + .byte $1B ; R + .byte $1C ; S + .byte $1D ; T + .byte $1E ; U + .byte $1F ; V + .byte $20 ; W + .byte $21 ; X + .byte $22 ; Y + .byte $23 ; Z + .byte $00 ; 0 + .byte $01 ; 1 + .byte $02 ; 2 + .byte $03 ; 3 + .byte $04 ; 4 + .byte $05 ; 5 + .byte $06 ; 6 + .byte $07 ; 7 + .byte $08 ; 8 + .byte $09 ; 9 + .byte $25 ; , + .byte $4F ; / + .byte $5E ; ( + .byte $5F ; ) + .byte $6E ; <3 + .byte $6F ; . + .byte $52 ; ! + .byte $55 ; ? + .byte $24 ; - + highScoreCharSize := $2E diff --git a/src/highscores/entry_screen.asm b/src/highscores/entry_screen.asm index fad9f22b..3a59f34d 100644 --- a/src/highscores/entry_screen.asm +++ b/src/highscores/entry_screen.asm @@ -176,6 +176,17 @@ highScoreEntryScreen: jmp @ret @checkForAOrRightPressed: + +.if KEYBOARD = 1 + jsr readKbHighScoreEntry + bmi @noKeyboardInput + beq @nextTile + cmp #$7F + beq @prevTile + jmp @waitForVBlank +@noKeyboardInput: + +.endif lda #BUTTON_RIGHT jsr menuThrottle bne @nextTile diff --git a/src/io.asm b/src/io.asm index db42f1cf..1e99e4da 100644 --- a/src/io.asm +++ b/src/io.asm @@ -34,13 +34,6 @@ SND_CHN := $4015 JOY1 := $4016 JOY2_APUFC := $4017 ; read: bits 0-4 joy data lines (bit 0 being normal controller), bits 6-7 are FC inhibit and mode -; Used by Family Basic Keyboard -.if KEYBOARD -KB_INIT := $05 -KB_COL_0 := $04 -KB_COL_1 := $06 -KB_MASK := $1E -.endif MMC1_Control := $8000 MMC1_CHR0 := $BFFF diff --git a/src/keyboard/buttonmap.asm b/src/keyboard/buttonmap.asm new file mode 100644 index 00000000..d6161606 --- /dev/null +++ b/src/keyboard/buttonmap.asm @@ -0,0 +1,8 @@ +kbMappedUp = keyK +kbMappedDown = keyJ +kbMappedLeft = keyH +kbMappedRight = keyL +kbMappedB = keyD +kbMappedA = keyF +kbMappedSelect = keyShiftLeft +kbMappedStart = keyReturn diff --git a/src/keyboard/constants.asm b/src/keyboard/constants.asm new file mode 100644 index 00000000..2ee0a72c --- /dev/null +++ b/src/keyboard/constants.asm @@ -0,0 +1,109 @@ +; each key is represented by a byte 0RRRRCCC +; row is 0-8 +; col is byte position from left to right + +keyF1 = $0B +keyF2 = $13 +keyF3 = $1B +keyF4 = $23 +keyF5 = $2B +keyF6 = $33 +keyF7 = $3B +keyF8 = $43 + +keyStop = $44 +keyReturn = $42 + +keyShiftRight = $46 +keyShiftLeft = $0F + +keyESC = $0A +keyCTR = $08 +keyGRPH = $0E + +keyCLR_HOME = $03 +keyINS = $04 +keyDEL = $05 + +keyUp = $02 +keyLeft = $00 +keyRight = $01 +keyDown = $07 + +keyCloseBracket = $40 ; ] +keyOpenBracket = $41 ; [ +keyYen = $45 ; ¥ +keySemicolon = $38 ; ; +keyColon = $39 ; : +keyatSign = $3A ; @ +keyCaret = $3C ; ^ +keySlash = $3E ; / +keyUnderscore = $3F ; _ +keyComma = $36 ; , +keyPeriod = $37 ; . +keyDash = $3D ; - +keyKana = $47 + +keySpace = $06 + +key0 = $34 +key1 = $0D +key2 = $0C +key3 = $14 +key4 = $1C +key5 = $1D +key6 = $24 +key7 = $25 +key8 = $2C +key9 = $2D + +keyA = $10 +keyB = $27 +keyC = $1E +keyD = $18 +keyE = $15 +keyF = $1F +keyG = $21 +keyH = $20 +keyI = $2A +keyJ = $28 +keyK = $30 +keyL = $31 +keyM = $2F +keyN = $2E +keyO = $32 +keyP = $35 +keyQ = $09 +keyR = $19 +keyS = $11 +keyT = $1A +keyU = $29 +keyV = $26 +keyW = $12 +keyX = $17 +keyY = $22 +keyZ = $16 + + +KB_DISABLE = $00 +KB_INIT = $05 +KB_COL_0 = $04 +KB_COL_1 = $06 +KB_MASK = $1E + +UPDOWN = BUTTON_UP | BUTTON_DOWN +LEFTRIGHT = BUTTON_LEFT | BUTTON_RIGHT + +.macro readKeyDirect keyMap +; not zero - key is pressed + lda kbRawInput + (keyMap >> 3) + and #$80 >> (keyMap & 7) +.endmacro + +.macro expandKeyRow keyMap + .byte keyMap >> 3 +.endmacro + +.macro expandKeyMask keyMap + .byte $80 >> (keyMap & 7) +.endmacro diff --git a/src/keyboard/keymap.py b/src/keyboard/keymap.py new file mode 100644 index 00000000..30917e8a --- /dev/null +++ b/src/keyboard/keymap.py @@ -0,0 +1,15 @@ +keymap = """ +CloseBracket OpenBracket RETURN F8 STOP Yen SHIFTRight KANA +Semicolon Colon atSign F7 Caret Dash Slash Underscore +K L O F6 0 P Comma Period +J U I F5 8 9 N M +H G Y F4 6 7 V B +D R T F3 4 5 C F +A S W F2 3 E Z X +CTR Q ESC F1 2 1 GRPH SHIFTLeft +Left Right UP CLR_HOME INS DEL Space Down +""" + +for row, keys in enumerate(keymap.strip().splitlines()): + for mask, key in enumerate(keys.split()): + print(f"key{key:<12} = ${((8 - row) << 3) | mask:02X}") diff --git a/src/keyboard/poll.asm b/src/keyboard/poll.asm new file mode 100644 index 00000000..88bd14cd --- /dev/null +++ b/src/keyboard/poll.asm @@ -0,0 +1,307 @@ +; https://www.nesdev.org/wiki/Family_BASIC_Keyboard +.include "keyboard/constants.asm" +.include "keyboard/buttonmap.asm" +.include "keyboard/tables.asm" + +pollKeyboard: + lda keyboardFlag + bne pollKeyboardInit + rts +pollKeyboardInit: + lda #KB_INIT + sta JOY1 + + ; wait 12 cycles before first row + lda #$00 + sta newlyPressedButtons_player1 + ldx #8 + nop + nop + nop + nop +@rowLoop: + ; start first column read + ldy #KB_COL_0 + sty JOY1 + + ldy #6 + jsr keyboardReadWait + lda JOY2_APUFC + + ; start second column read + ldy #KB_COL_1 + sty JOY1 + + ; make good use of wait time + and #KB_MASK + asl + asl + asl + beq @disconnected + sta generalCounter + + ldy #3 + jsr keyboardReadWait + lda JOY2_APUFC + + and #KB_MASK + lsr + ora generalCounter + eor #$FF + sta kbRawInput,x + dex + bpl @rowLoop + +; map keys to buttons + ldx #7 + +; build a byte that looks like controller input +@readKeyLoop: + clc + ldy kbMappedKeyRows,x + lda kbRawInput,y + and kbMappedKeyMasks,x + beq @notPressed + sec +@notPressed: + rol newlyPressedButtons_player1 + dex + bpl @readKeyLoop + +; prevent SOCD (Simultaneous Opposite Cardinal Direction + ldx #$01 +@antiSocd: + lda heldButtons_player1 + and kbAntiSocd,x + sta generalCounter + lda newlyPressedButtons_player1 + and kbAntiSocd,x + cmp kbAntiSocd,x + bne @noMatch + eor #$FF + and newlyPressedButtons_player1 + ; allow previously held input to continue, prevents yutaps + ora generalCounter + sta newlyPressedButtons_player1 +@noMatch: + dex + bpl @antiSocd + +; determine which are new + lda newlyPressedButtons_player1 + +; ignore everything except start during score entry + ldy highScoreEntryActive + beq @entryNotActive + and #BUTTON_START +@entryNotActive: + tay + eor heldButtons_player1 + and newlyPressedButtons_player1 + sta newlyPressedButtons_player1 + sty heldButtons_player1 + rts + +@disconnected: + ldy #8 + lda #$00 + sta keyboardFlag +@clearInput: + sta kbRawInput,y + dey + bpl @clearInput + rts + +keyboardReadWait: + ; consumes (y * 5) + 16 + dey + bpl keyboardReadWait + rts + +detectKeyboard: +; read 10th row, expect 1E +; disable keyboard, expect 00 +; see https://www.nesdev.org/wiki/Family_BASIC_Keyboard#Keyboard_detection_in_other_games + jsr pollKeyboardInit + ldy #KB_COL_0 + sty JOY1 + ldy #6 + jsr keyboardReadWait + lda JOY2_APUFC + and #KB_MASK + cmp #KB_MASK + bne @noKeyboard + ldy #6 + jsr keyboardReadWait + lda #KB_DISABLE + sta JOY1 + ldy #6 + jsr keyboardReadWait + lda JOY2_APUFC + and #KB_MASK + bne @noKeyboard + inc keyboardFlag +@noKeyboard: + rts + +; Seed Entry + + +readKbSeedEntry: + ldx #seedEntryCharCount +@readLoop: + lda seedEntryTable,x + jsr readKey + bne @seedEntered + dex + bpl @readLoop +@seedEntered: + cpx kbHeldInput + beq @noInput + stx kbHeldInput + txa + rts +@noInput: + lda #$FF + rts + + +; high score entry + + +readKbHighScoreEntry: +@kbInputThrottle := generalCounter4 +; 2 frames to complete action +; first reads key, determines action and stores key (unless key is action only) +; second returns cursor action + +; return values +; >$80 - do nothing (flag) +; $00 - move cursor right (flag) +; $7F - move cursor left +; default action is to set render flags for new letter + +; kbReadState set in frame 1 for action in frame 2 +; 0 - read input +; 1 - signal cursor right +; 2 - signal cursor left + +; check if shift flag is set + lda kbReadState + beq @readChar + dec kbReadState + beq @signalRight + dec kbReadState ; reset to 0 + ; signal left + lda #$7F +@signalRight: + rts + +@readChar: + ldx #scoreEntryCharCount + +@checkNextChar: + lda scoreEntryTable,x + jsr readKey + bne @keyPressed + dex + bpl @checkNextChar + stx kbHeldInput + +@noKeyPressed: +; n flag set due to heldInput no keys byte or active throttle + rts + +@keyPressed: + cpx kbHeldInput + bne @newInput + + inc @kbInputThrottle + bne @noKeyPressed + + lda #<-4 + bne @storeThrottle + +@newInput: + stx kbHeldInput + lda #<-16 + +@storeThrottle: + sta @kbInputThrottle + +@placeInput: + lda highScoreEntryNameOffsetForLetter + clc + adc highScoreEntryNameOffsetForRow + tay + txa + + cmp #kbScoreDelete + beq @delete + + cmp #kbScoreLeft + bne @checkRight + + inc kbReadState + bne @signalCursorShift + +@checkRight: + cmp #kbScoreRight + beq @signalCursorShift + bne @normal + +@delete: + lda #$00 + inc kbReadState ; +1 to indicate backspace +@normal: + sta highscores,y +.if SAVE_HIGHSCORES + tax + jsr detectSRAM + beq @signalCursorShift + txa + sta SRAM_highscores,y +@signalCursorShift: +.endif + inc kbReadState + rts + + +readKey: +; clobbers y & generalCounter5 + +@readKeyMask := generalCounter5 +; SRRRRCCC - Shift, Row, Column + php ; store shift (negative) flag + pha ; store byte + +; extract mask + and #07 + tay + lda kbColumnMaskTable,y + sta @readKeyMask + +; extract row index + pla + lsr + lsr + lsr + and #$0F + tay + +; determine if char is shifted + plp + bpl @readKey + +; if so, read both shift keys + readKeyDirect keyShiftLeft + bne @readKey + + readKeyDirect keyShiftRight + beq @ret + +@readKey: + lda kbRawInput,y + and @readKeyMask +@ret: + rts diff --git a/src/keyboard/tables.asm b/src/keyboard/tables.asm new file mode 100644 index 00000000..7ff323b9 --- /dev/null +++ b/src/keyboard/tables.asm @@ -0,0 +1,108 @@ +kbAntiSocd: + .byte BUTTON_UP | BUTTON_DOWN + .byte BUTTON_LEFT | BUTTON_RIGHT + +kbMappedKeyRows: + expandKeyRow kbMappedRight + expandKeyRow kbMappedLeft + expandKeyRow kbMappedDown + expandKeyRow kbMappedUp + expandKeyRow kbMappedStart + expandKeyRow kbMappedSelect + expandKeyRow kbMappedB + expandKeyRow kbMappedA + +kbMappedKeyMasks: + expandKeyMask kbMappedRight + expandKeyMask kbMappedLeft + expandKeyMask kbMappedDown + expandKeyMask kbMappedUp + expandKeyMask kbMappedStart + expandKeyMask kbMappedSelect + expandKeyMask kbMappedB + expandKeyMask kbMappedA + + +seedEntryTable: + .byte key0 + .byte key1 + .byte key2 + .byte key3 + .byte key4 + .byte key5 + .byte key6 + .byte key7 + .byte key8 + .byte key9 + .byte keyA + .byte keyB + .byte keyC + .byte keyD + .byte keyE + .byte keyF +seedEntryTableEnd: +seedEntryCharCount = <(seedEntryTableEnd - seedEntryTable) - 1 + +scoreEntryTable: + .byte keySpace + .byte keyA + .byte keyB + .byte keyC + .byte keyD + .byte keyE + .byte keyF + .byte keyG + .byte keyH + .byte keyI + .byte keyJ + .byte keyK + .byte keyL + .byte keyM + .byte keyN + .byte keyO + .byte keyP + .byte keyQ + .byte keyR + .byte keyS + .byte keyT + .byte keyU + .byte keyV + .byte keyW + .byte keyX + .byte keyY + .byte keyZ + .byte key0 + .byte key1 + .byte key2 + .byte key3 + .byte key4 + .byte key5 + .byte key6 + .byte key7 + .byte key8 + .byte key9 + .byte keyComma + .byte keySlash + .byte keyOpenBracket + .byte keyCloseBracket + .byte keyKana ; <3 + .byte keyPeriod + .byte key1 | $80 ; ! + .byte keySlash | $80 ; ? + .byte keyDash + ; treated differently + .byte keyRight + .byte keyLeft + .byte keyDEL +scoreEntryTableEnd: + +scoreEntryCharCount = <(scoreEntryTableEnd - scoreEntryTable) - 1 + +kbScoreDelete = scoreEntryCharCount +kbScoreLeft = scoreEntryCharCount - 1 +kbScoreRight = scoreEntryCharCount - 2 + +kbColumnMaskTable: + .repeat 8,i + .byte $80 >> i + .endrepeat diff --git a/src/main.asm b/src/main.asm index 000f7ae9..38dd4e30 100644 --- a/src/main.asm +++ b/src/main.asm @@ -35,7 +35,7 @@ mainLoop: .include "nmi/render.asm" .include "nmi/pollcontroller.asm" .if KEYBOARD -.include "nmi/pollkeyboard.asm" +.include "keyboard/poll.asm" .endif .include "gamemode/branch.asm" diff --git a/src/nametables/game_type_menu.js b/src/nametables/game_type_menu.js index 45cc8a47..a12cb1a1 100644 --- a/src/nametables/game_type_menu.js +++ b/src/nametables/game_type_menu.js @@ -95,6 +95,10 @@ drawTiles(buffer, lookup, ` #a d# `); +if (process.env['GYM_FLAGS']?.match(/-D KEYBOARD=1/)) { + drawTiles(extra, lookup, "KEYBOARD", 32 * 15 + 6); + } + const background = ` ɢ##############################ɳ ɲ##############################ɢ diff --git a/src/nametables/nametables.js b/src/nametables/nametables.js index 705e81f2..c7471aaf 100644 --- a/src/nametables/nametables.js +++ b/src/nametables/nametables.js @@ -42,14 +42,14 @@ function blankNT() { return Array.from({ length: 1024 }, () => 0xFF); } -function drawTiles(buffer, lookup, tiles) { +function drawTiles(buffer, lookup, tiles, offset = 0) { [...tiles.trim().split('\n').join('')].forEach((d, i) => { if (d !== '#') { const charCode = d.charCodeAt(0); if (charCode > STR_OFFSET) { - buffer[i] = charCode - STR_OFFSET; + buffer[offset + i] = charCode - STR_OFFSET; } else { - buffer[i] = lookup.indexOf(d); + buffer[offset + i] = lookup.indexOf(d); } } }); diff --git a/src/nmi/nmi.asm b/src/nmi/nmi.asm index d0dfca0a..612f61fb 100644 --- a/src/nmi/nmi.asm +++ b/src/nmi/nmi.asm @@ -28,10 +28,6 @@ nmi: pha jsr pollControllerButtons lda #$00 sta lagState ; clear flag after lag frame achieved -.if KEYBOARD -; Read Family BASIC Keyboard - jsr pollKeyboard -.endif lda #$01 sta verticalBlankingInterval pla diff --git a/src/nmi/pollcontroller.asm b/src/nmi/pollcontroller.asm index d1fe24eb..4f89b1ec 100644 --- a/src/nmi/pollcontroller.asm +++ b/src/nmi/pollcontroller.asm @@ -66,6 +66,21 @@ pollController: diffOldAndNewButtons: ldx #$01 +.if KEYBOARD = 1 +; clear controller input when keyboard is active +; disable keyboard when reset sequence is pressed + lda keyboardFlag + beq @diffForPlayer + lda newlyPressedButtons_player1 + and #BUTTON_B|BUTTON_A|BUTTON_SELECT|BUTTON_START + cmp #BUTTON_B|BUTTON_A|BUTTON_SELECT|BUTTON_START + php + lda #$00 + sta newlyPressedButtons_player1 + plp + bne @ret + sta keyboardFlag +.endif @diffForPlayer: lda newlyPressedButtons_player1,x tay @@ -75,4 +90,4 @@ diffOldAndNewButtons: sty heldButtons_player1,x dex bpl @diffForPlayer - rts +@ret: rts diff --git a/src/nmi/pollkeyboard.asm b/src/nmi/pollkeyboard.asm deleted file mode 100644 index 6b2ff80d..00000000 --- a/src/nmi/pollkeyboard.asm +++ /dev/null @@ -1,194 +0,0 @@ -; https://www.nesdev.org/wiki/Family_BASIC_Keyboard - -; Input ($4016 write) - -; 7 bit 0 -; ---- ---- -; xxxx xKCR -; ||| -; ||+-- Reset the keyboard to the first row. -; |+--- Select column, row is incremented if this bit goes from high to low. -; +---- Enable keyboard matrix (if 0, all voltages inside the keyboard will be 5V, reading back as logical 0 always) - -; Incrementing the row from the (keyless) 10th row will cause it to wrap back to the first row. - -; Output ($4017 read) - -; 7 bit 0 -; ---- ---- -; xxxK KKKx -; | ||| -; +-+++--- Receive key status of currently selected row/column. - -; Any key that is held down, will read back as 0. - -; ($4016 reads from the data recorder.) - -; Similar to the Family Trainer Mat, there are parasitic capacitances that the program must wait for to get a valid result. Family -; BASIC waits approximately 50 cycles after advancing rows before assuming the output is valid. -; Usage - -; Family BASIC reads the keyboard state with the following procedure: - -; Write $05 to $4016 (reset to row 0, column 0) -; Write $04 to $4016 (select column 0, next row if not just reset) -; Read column 0 data from $4017 -; Write $06 to $4016 (select column 1) -; Read column 1 data from $4017 -; Repeat steps 2-5 eight more times - -; Note that Family BASIC never writes to $4016 with bit 2 clear, there is no need to disable the keyboard matrix. - - -pollKeyboard: - ldx #$00 - stx newlyPressedKeys - lda #KB_INIT - sta JOY1 -@rowLoop: - lda #KB_COL_0 - sta JOY1 - ldy #$0A -@avoidParasiticCapacitance: ; wait approx 50 cycles after advancing rows - dey - bne @avoidParasiticCapacitance - lda JOY2_APUFC - and #KB_MASK - sta generalCounter - lda #KB_COL_1 - sta JOY1 - lda JOY2_APUFC - and #KB_MASK - lsr - sta keyboardInput,x - lda generalCounter - asl - asl - asl - ora keyboardInput,x - eor #$FF - cmp #$FF - beq @ret ; Assume 8 simultaneously pressed keys means there's no keyboard to be read - sta keyboardInput,x - inx - cpx #$09 - bne @rowLoop - jsr mapKeysToButtons -@ret: rts - -; Bit0 Bit1 Bit2 Bit3 Bit4 Bit5 Bit6 Bit7 -; ] [ RETURN F8 STOP ¥ RSHIFT KANA -; ; : @ F7 ^ - / _ -; K L O F6 0 P , . -; J U I F5 8 9 N M -; H G Y F4 6 7 V B -; D R T F3 4 5 C F -; A S W F2 3 E Z X -; CTR Q ESC F1 2 1 GRPH LSHIFT -; LEFT RIGHT UP CLR HOME INS DEL SPACE DOWN - -; Read keys. up/down/left/right are mapped directly - - -mapKeysToButtons: - ldy #$08 - ldx #$02 - jsr readKey - beq @upNotPressed - lda newlyPressedKeys - ora #BUTTON_UP - sta newlyPressedKeys - bne @skipDownRead -@upNotPressed: - - ldy #$08 - ldx #$07 - jsr readKey - beq @downNotPressed - lda newlyPressedKeys - ora #BUTTON_DOWN - sta newlyPressedKeys -@skipDownRead: -@downNotPressed: - - ldy #$08 - ldx #$00 - jsr readKey - beq @leftNotPressed - lda newlyPressedKeys - ora #BUTTON_LEFT - sta newlyPressedKeys - bne @skipRightRead -@leftNotPressed: - - ldy #$08 - ldx #$01 - jsr readKey - beq @rightNotPressed - lda newlyPressedKeys - ora #BUTTON_RIGHT - sta newlyPressedKeys -@skipRightRead: -@rightNotPressed: - - ldy #$07 ; grph -> B - ldx #$06 - jsr readKey - beq @bNotPressed - lda newlyPressedKeys - ora #BUTTON_B - sta newlyPressedKeys -@bNotPressed: - - ldy #$08 ; space -> A - ldx #$06 - jsr readKey - beq @aNotPressed - lda newlyPressedKeys - ora #BUTTON_A - sta newlyPressedKeys -@aNotPressed: - - ldy #$00 ; right shift -> select - ldx #$06 - jsr readKey - beq @selectNotPressed - lda newlyPressedKeys - ora #BUTTON_SELECT - sta newlyPressedKeys -@selectNotPressed: - - ldy #$00 ; return -> start - ldx #$02 - jsr readKey - beq @startNotPressed - lda newlyPressedKeys - ora #BUTTON_START - sta newlyPressedKeys -@startNotPressed: - - -; Separate Newly Pressed from Held - lda newlyPressedKeys - tay - eor heldKeys - and newlyPressedKeys - sta newlyPressedKeys - sty heldKeys - -; Copy to buttons - lda newlyPressedButtons_player1 - ora newlyPressedKeys - sta newlyPressedButtons_player1 - - lda heldButtons_player1 - ora heldKeys - sta heldButtons_player1 - rts - -keyMask: - .byte $80,$40,$20,$10,$08,$04,$02,$01 -readKey: - lda keyboardInput,y - and keyMask,x - rts diff --git a/src/ram.asm b/src/ram.asm index f021f206..aa0e2ee4 100644 --- a/src/ram.asm +++ b/src/ram.asm @@ -221,16 +221,20 @@ invisibleFlag: .res 1 ; $63B ; 0 for normal mode, non-zero for Invisible playfi currentFloor: .res 1 ; $63C floorModifier is copied here at game init. Set to 0 otherwise and incremented when linecap floor. mapperId: .res 1 ; $63D ; For INES_MAPPER 1000 (autodetect). 0 = CNROM. 1 = MMC1. - .res $37 - .if KEYBOARD -newlyPressedKeys: .res 1 ; $0675 -heldKeys: .res 1 ; $0676 -keyboardInput: .res 9 ; $0677 +kbReadState: .res 1 ; $063E - used for high score entry +kbHeldInput: .res 1 ; $063E - high score input throttling +kbRawInput: .res 9 ; $0640 - all 72 keys' input + +; used to track state of high score entry screen. Can possibly use the address of the nmi interrupted +; routine in the stack to track instead +highScoreEntryActive: .res 1 ; $0649 .else - .res $B + .res $C .endif + .res $36 + musicStagingSq1Lo: .res 1 ; $0680 musicStagingSq1Hi: .res 1 ; $0681 audioInitialized: .res 1 ; $0682 @@ -351,5 +355,8 @@ linecapFlag: .res 1 dasOnlyFlag: .res 1 qualFlag: .res 1 palFlag: .res 1 +.if KEYBOARD = 1 +keyboardFlag: .res 1 +.endif ; ... $7FF diff --git a/src/util/core.asm b/src/util/core.asm index 9204e1fd..112d4293 100644 --- a/src/util/core.asm +++ b/src/util/core.asm @@ -65,6 +65,10 @@ updateAudioWaitForNmiAndResetOamStaging: lda verticalBlankingInterval beq @checkForNmi +.if KEYBOARD = 1 +; Read Family BASIC Keyboard + jsr pollKeyboard +.endif resetOAMStaging: ; Hide a sprite by moving it down offscreen, by writing any values between #$EF-#$FF here. ; Sprites are never displayed on the first line of the picture, and it is impossible to place