diff --git a/src/field.asm b/src/field.asm index a7eca68..2b2a946 100644 --- a/src/field.asm +++ b/src/field.asm @@ -10,6 +10,16 @@ wField:: ds (10*24) wShadowField:: ds (14*26) +SECTION "Field High Variables", HRAM +hToppedOut:: ds 1 +hPieceDataBase:: ds 2 +hPieceDataOffset:: ds 1 +hCurrentLockDelayRemaining:: ds 1 +hTicksUntilG:: ds 1 +hWantX:: ds 1 +hWantRotation:: ds 1 + + SECTION "Field Functions", ROM0 FieldInit:: ld hl, wField @@ -30,7 +40,8 @@ FieldClear:: call UnsafeMemSet ret -ToShadowField:: + +ToShadowField: ld hl, wField ld de, wShadowField+2 ld c, 24 @@ -51,7 +62,7 @@ ToShadowField:: ret -FromShadowField:: +FromShadowField: ld hl, wField ld de, wShadowField+2 ld c, 24 @@ -121,4 +132,554 @@ BlitField:: jp EventLoop +SetPieceData: + ldh a, [hCurrentPiece] + ld hl, sPieceRotationStates + ld de, 16 +: cp a, 0 + jr z, :+ + add hl, de + dec a + jr :- +: ld a, l + ldh [hPieceDataBase], a + ld a, h + ldh [hPieceDataBase+1], a + ret + + +SetPieceDataOffset: + ldh a, [hCurrentPieceRotationState] + rlc a + rlc a + ldh [hPieceDataOffset], a + ret + + + ; Converts piece Y in B and a piece X in A to a pointer to the shadow field in HL. +XYToSFieldPtr: + ld hl, wShadowField + ld de, 14 + inc a + inc b +: dec b + jr z, :+ + add hl, de + jr :- +: dec a + ret z + inc hl + jr :- + ret + + + + ; Converts piece Y in B and a piece X in A to a pointer to the field in HL. +XYToFieldPtr: + ld hl, wField-2 + ld de, 10 + inc a + inc b +: dec b + jr z, :+ + add hl, de + jr :- +: dec a + ret z + inc hl + jr :- + ret + + +GetPieceData: + ldh a, [hPieceDataBase] + ld l, a + ldh a, [hPieceDataBase+1] + ld h, a + ldh a, [hPieceDataOffset] + ld c, a + xor a, a + ld b, a + add hl, bc + ret + + ; Checks if the piece can fit at the current position. + ; HL should point to the piece's rotation state data. + ; DE should be pointing to the right place in the SHADOW field. +CanPieceFit: + xor a, a + ld b, a + + ; Row 1 + bit 3, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 2, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 1, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 0, [hl] + jr z, .r1end + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz + +.r1end + REPT 11 + inc de + ENDR + + ; Row 2 + inc hl + bit 3, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 2, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 1, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 0, [hl] + jr z, .r2end + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz + +.r2end + REPT 11 + inc de + ENDR + + ; Row 3 + inc hl + bit 3, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 2, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 1, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 0, [hl] + jr z, .r3end + ld a, [de] + cp a, TILE_FIELD_EMPTY + ret nz + +.r3end + REPT 11 + inc de + ENDR + + ; Row 4 + inc hl + bit 3, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 2, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 1, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz +: inc de + inc b + bit 0, [hl] + jr z, :+ + ld a, [de] + cp a, TILE_FIELD_EMPTY + ld a, b + ret nz + + ; If we got here, the piece can fit. +: ld a, $FF + ret + + +TrySpawnPiece:: + ldh a, [hCurrentFramesPerGravityTick] + ldh [hTicksUntilG], a + + ; Copy the field to the shadow field. + call ToShadowField + + ; Assume we're not topped out. + xor a, a + ldh [hToppedOut], a + + ; Point the piece data to the correct piece. + call SetPieceData + call SetPieceDataOffset + + ; Get the piece's spawn position. + ldh a, [hCurrentPieceY] + ld b, a + ldh a, [hCurrentPieceX] + call XYToSFieldPtr + + ; Check if the piece can spawn. + ld d, h + ld e, l + call GetPieceData + jp CanPieceFit + ; A will be $FF if the piece can fit. + ; We jump instead of return to save a few cycles. + + + ; Draws the piece onto the field. + ; B is the tile. + ; DE should point to the piece's rotation state data. + ; HL should be pointing to the right place in the NORMAL field. +DrawPiece: + ld a, [de] + inc de + + bit 3, a + jr z, :+ + ld [hl], b +: inc hl + bit 2, a + jr z, :+ + ld [hl], b +: inc hl + bit 1, a + jr z, :+ + ld [hl], b +: inc hl + bit 0, a + jr z, .r1end2 + ld [hl], b + +.r1end2 + REPT 7 + inc hl + ENDR + ld a, [de] + inc de + + bit 3, a + jr z, :+ + ld [hl], b +: inc hl + bit 2, a + jr z, :+ + ld [hl], b +: inc hl + bit 1, a + jr z, :+ + ld [hl], b +: inc hl + bit 0, a + jr z, .r2end2 + ld [hl], b + +.r2end2 + REPT 7 + inc hl + ENDR + ld a, [de] + inc de + + bit 3, a + jr z, :+ + ld [hl], b +: inc hl + bit 2, a + jr z, :+ + ld [hl], b +: inc hl + bit 1, a + jr z, :+ + ld [hl], b +: inc hl + bit 0, a + jr z, .r3end2 + ld [hl], b + +.r3end2 + REPT 7 + inc hl + ENDR + ld a, [de] + inc de + + bit 3, a + jr z, :+ + ld [hl], b +: inc hl + bit 2, a + jr z, :+ + ld [hl], b +: inc hl + bit 1, a + jr z, :+ + ld [hl], b +: inc hl + bit 0, a + ret z + ld [hl], b + ret + + +FieldProcess:: + ; Wipe out the piece. + ldh a, [hCurrentPieceY] + ld b, a + ldh a, [hCurrentPieceX] + call XYToFieldPtr + ld d, h + ld e, l + call GetPieceData + ld b, TILE_FIELD_EMPTY + push hl + push de + pop hl + pop de + call DrawPiece + + ; Gravity? + ldh a, [hTicksUntilG] + dec a + ldh [hTicksUntilG], a + jr nz, .nograv + ldh a, [hCurrentFramesPerGravityTick] + ldh [hTicksUntilG], a + + ; Move the piece down, but first check if there's still sufficient "down" to go. + ldh a, [hCurrentGravityPerTick] + ld b, a +: ldh a, [hCurrentPieceY] + add a, b + cp a, 22 + jr c, :+ + dec b + jr z, .nograv + jr :- +: push bc + ldh a, [hCurrentPieceY] + add a, b + ld b, a + ldh a, [hCurrentPieceX] + call XYToSFieldPtr + ld d, h + ld e, l + call GetPieceData + call CanPieceFit + cp a, $FF + jr z, .dolower + pop bc + dec b + jr z, .nograv + jr :- + +.dolower + pop bc + ldh a, [hCurrentPieceY] + add a, b + ldh [hCurrentPieceY], a + +.nograv + ldh a, [hCurrentPieceX] + ldh [hWantX], a + ldh a, [hCurrentPieceRotationState] + ldh [hWantRotation], a + + ; Want left? +.wantleft + ldh a, [hLeftState] + cp a, 1 + jr z, :+ + ld b, a + ldh a, [hCurrentDAS] + ld c, a + ld a, b + cp a, c + jr c, .wantright +: ldh a, [hWantX] + dec a + ldh [hWantX], a + + ; Want right? +.wantright + ldh a, [hRightState] + cp a, 1 + jr z, :+ + ld b, a + ldh a, [hCurrentDAS] + ld c, a + ld a, b + cp a, c + jr c, .wantrotccw +: ldh a, [hWantX] + inc a + ldh [hWantX], a + + ; Want rotate CCW? +.wantrotccw + ldh a, [hAState] + cp a, 1 + jr nz, .wantrotcw + ldh a, [hWantRotation] + inc a + and a, $03 + ldh [hWantRotation], a + + + ; Want rotate CW? +.wantrotcw + ldh a, [hBState] + cp a, 1 + jr nz, .moverotrequested + ldh a, [hWantRotation] + dec a + and a, $03 + ldh [hWantRotation], a + + + ; Do we need to try to move/rotate the piece? +.moverotrequested + ldh a, [hWantRotation] + ld b, a + ldh a, [hCurrentPieceRotationState] + cp a, b + jr nz, .trymoverot ; Move and rotate. + ldh a, [hWantX] + ld b, a + ldh a, [hCurrentPieceX] + cp a, b + jr z, .postmove ; Neither move nor rotate. + + ; Move only. + ldh a, [hCurrentPieceY] + ld b, a + ldh a, [hWantX] + call XYToSFieldPtr + ld d, h + ld e, l + call GetPieceData + call CanPieceFit + cp a, $FF + jr nz, .postmove + ldh a, [hWantX] + ldh [hCurrentPieceX], a + jr .postmove + +.trymoverot + ldh a, [hCurrentPieceY] + ld b, a + ldh a, [hWantX] + call XYToSFieldPtr + ld d, h + ld e, l + ldh a, [hPieceDataBase] + ld l, a + ldh a, [hPieceDataBase+1] + ld h, a + ldh a, [hWantRotation] + rlc a + rlc a + push bc + ld c, a + xor a, a + ld b, a + add hl, bc + pop bc + call CanPieceFit + cp a, $FF + jr nz, .postmove + ldh a, [hWantX] + ldh [hCurrentPieceX], a + ldh a, [hWantRotation] + ldh [hCurrentPieceRotationState], a + call SetPieceDataOffset + + +.postmove + + + ; Draw the piece. +.draw + ldh a, [hCurrentPieceY] + ld b, a + ldh a, [hCurrentPieceX] + call XYToFieldPtr + ld d, h + ld e, l + call GetPieceData + ldh a, [hCurrentPiece] + ld b, TILE_PIECE_0 + add a, b + ld b, a + push hl + push de + pop hl + pop de + call DrawPiece + + ret + ENDC diff --git a/src/state_gameplay.asm b/src/state_gameplay.asm index 071e849..200417f 100644 --- a/src/state_gameplay.asm +++ b/src/state_gameplay.asm @@ -10,6 +10,9 @@ DEF MODE_GO EQU 1 DEF MODE_POSTGO EQU 2 DEF MODE_FETCH_PIECE EQU 3 DEF MODE_SPAWN_PIECE EQU 4 +DEF MODE_PIECE_IN_MOTION EQU 5 +DEF MODE_DELAY EQU 6 +DEF MODE_GAME_OVER EQU 7 SECTION "Gameplay Variables", WRAM0 @@ -17,10 +20,10 @@ wMode: ds 1 wModeCounter: ds 1 SECTION "Critical Gameplay Variables", HRAM -hCurrentPiece: ds 1 -hCurrentPieceX: ds 1 -hCurrentPieceY: ds 1 -hCurrentPieceRotationState: ds 1 +hCurrentPiece:: ds 1 +hCurrentPieceX:: ds 1 +hCurrentPieceY:: ds 1 +hCurrentPieceRotationState:: ds 1 hHeldPiece: ds 1 hHoldSpent: ds 1 hSkipJingle: ds 1 @@ -101,6 +104,12 @@ GamePlayEventLoopHandler:: jr z, fetchPieceMode cp MODE_SPAWN_PIECE jp z, spawnPieceMode + cp MODE_PIECE_IN_MOTION + jp z, pieceInMotionMode + cp MODE_DELAY + jp z, delayMode + cp MODE_GAME_OVER + jp z, gameOverMode ; Draw "READY" and wait a bit. @@ -204,53 +213,28 @@ fetchPieceMode: ; Spawn the piece. spawnPieceMode: - ; TODO: At this point all the info needed to spawn the piece is known. - ; We spawn the piece and then check if this causes a top out, and transition to the game over state if so. - ; This then immediately transitions into regular gameplay. - - call ToShadowField - call FromShadowField - - ld a, [hEvenFrame] - cp a, 0 - jr nz, :+ - ld e, 1 - call LevelUp - ld a, $10 - ld hl, wScoreIncrement+1 - ld [hl], a - call IncreaseScore - -: ld a, [hUpState] - cp a, 1 - jr nz, :+ - ld a, MODE_FETCH_PIECE + call TrySpawnPiece + cp a, $FF + jr z, :+ + ld a, MODE_GAME_OVER ld [wMode], a jr drawStaticInfo - -: ld a, [hLeftState] - cp a, 1 - jr z, :++ - cp a, 12 - jr nc, :+ - ld a, [hRightState] - cp a, 1 - jr z, :++ - cp a, 12 - jr nc, :+ - jr drawStaticInfo -: ldh a, [hFrameCtr] - and %00000111 - cp 4 - jr nz, drawStaticInfo -: ld a, SFX_MOVE - call SFXEnqueue - jr drawStaticInfo +: ld a, MODE_PIECE_IN_MOTION + ld [wMode], a ; This mode lasts for as long as the piece is in motion. ; Field will let us know when it has locked in place. pieceInMotionMode: + call FieldProcess + jr drawStaticInfo + + +delayMode: + ; TODO. + + +gameOverMode: ; TODO. diff --git a/tools/Emulicious.ini b/tools/Emulicious.ini index a19aa2e..2cbb59e 100644 --- a/tools/Emulicious.ini +++ b/tools/Emulicious.ini @@ -1,5 +1,5 @@ #Emulicious settings file -#Wed Oct 18 13:09:20 CEST 2023 +#Fri Oct 20 08:16:53 CEST 2023 WindowEventViewerWindowHeight=1416 WindowEventViewerWindowDivider=876 WindowMemoryTracerWindowY=631 @@ -72,7 +72,7 @@ InterruptBreakpointEnabled=false OutlineWidth=425 DebuggerEventFiltersGameBoy= GameBoyErrorBreakpointSuspend9=true -WindowMemoryEditorOpen=false +WindowMemoryEditorOpen=true GameBoyErrorBreakpointSuspend8=true GameBoyErrorBreakpointSuspend7=true WindowPaletteViewerY=619 @@ -98,8 +98,8 @@ Gamepad1Key31=-1 Gamepad1Key30=-1 BankSwapAtPCBreakpointEnabled=false DebuggerMemorySelectedTab=HRAM -WindowVideoViewerOpen=true -WindowMemoryEditorTabVisibleRect=0,0,583,128 +WindowVideoViewerOpen=false +WindowMemoryEditorTabVisibleRect=0,496,583,384 Gamepad1Key29=-1 Gamepad1Key28=-1 Gamepad1Key27=-1 @@ -126,9 +126,9 @@ Key1=75 Key0=74 Gamepad1Key19=-1 Gamepad1Key18=-1 -WindowEventViewerWindowY=24 +WindowEventViewerWindowY=99 Gamepad1Key17=-1 -WindowEventViewerWindowX=0 +WindowEventViewerWindowX=570 Gamepad1Key16=-1 Gamepad1Key15=-1 Gamepad1Key14=-1 @@ -136,7 +136,7 @@ Gamepad1Key13=-1 Gamepad1Key12=-1 Gamepad1Key11=-1 Gamepad1Key10=-1 -WindowMemoryEditorSelectedAddress=0 +WindowMemoryEditorSelectedAddress=503 WindowMemoryEditorWidth=665 GameBoyErrorBreakpointCondition9= GameBoyErrorBreakpointCondition8= @@ -155,7 +155,7 @@ Gamepad0Key35=-1 Gamepad0Key34=-1 Gamepad0Key33=-1 Gamepad0Key32=-1 -WindowMemoryEditorSelectedTab=HRAM +WindowMemoryEditorSelectedTab=RAM Gamepad0Key31=-1 Gamepad0Key30=-1 SMSGamepadAThreshold=50 @@ -178,8 +178,8 @@ Gamepad0Key20=-1 DebuggerSouthPanelSelectedTab=1 WindowEmuliciousWidth=816 WindowVideoViewerWidth=980 -WindowMemoryEditorY=557 -WindowMemoryEditorX=837 +WindowMemoryEditorY=732 +WindowMemoryEditorX=2558 Gamepad0Key19=-1 Gamepad0Key18=-1 Gamepad0Key17=-1 @@ -199,8 +199,8 @@ GameBoyErrorBreakpointMessage32= InterruptBreakpointCondition= Recent0=C\:\\workspace\\dmgtris\\bin\\out.gb GameBoyErrorBreakpointMessage20= -WindowEmuliciousY=474 -WindowEmuliciousX=1512 +WindowEmuliciousY=422 +WindowEmuliciousX=89 GameBoyErrorBreakpointEnabled9=false GameBoyErrorBreakpointEnabled8=false GameBoyErrorBreakpointEnabled7=false @@ -243,7 +243,7 @@ Gamepad1Key0=-1 RomDir=C\:\\workspace\\dmgtris\\bin GameBoyErrorBreakpointCondition20= WindowVideoViewerHeight=1027 -WindowEventViewerWindowOpen=false +WindowEventViewerWindowOpen=true WindowSpriteViewerY=512 WindowSpriteViewerX=320 CodeFontSize=13 @@ -264,8 +264,8 @@ Gamepad0Key3=-1 Gamepad0Key2=-1 Gamepad0Key1=-1 Gamepad0Key0=-1 -WindowDebuggerY=103 -WindowDebuggerX=166 +WindowDebuggerY=201 +WindowDebuggerX=727 InterruptBreakpointSuspend=true SMSGamepadAKeyboard=false GameBoyErrorBreakpointSuspend32=true diff --git a/tools/projects/out.exp b/tools/projects/out.exp index 6e4779f..7f01b12 100644 --- a/tools/projects/out.exp +++ b/tools/projects/out.exp @@ -4,3 +4,7 @@ hPlayhead 4 Hexadecimal wScoreIncrementBCD 6 Hexadecimal wScoreIncrement 2 Hexadecimal wScore 6 Hexadecimal +hPieceDataBase 2 Hexadecimal +hPieceDataOffset 1 Hexadecimal +hCurrentPiece 1 Hexadecimal +hCurrentPieceY 1 Hexadecimal