Skip to content

Commit 8aaa46d

Browse files
committed
Correct bank tank bounds on bridge > 7
Road tanks at bridge terrain inherit MinX=8/MaxX=238 (full screen with boundaryPadding) because renderBandedLines stores LeftX=0/RightX=ScreenWidth for every bridge scanline. After bridge destruction on levels > 7 the road tank was converted to bank-tank but kept those wide bounds, so terrainAhead stayed true across the whole screen and the tank walked over the gap instead of stopping at its edge. Introduce convertToBankTank which sets TankLocation and derives MinX/MaxX from the gap constants so the converted tank is bounded to its bank. Expand the existing test to pre-set road-tank bounds and verify MinX/MaxX after conversion; add a right-bank case.
1 parent 2131d1c commit 8aaa46d

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

pkg/logic/collision.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (b bridgeTarget) onHit(r *CollisionResult) {
198198
r.PointsScored += PointsTank
199199
} else if b.bridgeIndex > bridgeEarlyLevel {
200200
// Tank is on the bank, late level: convert to bank-tank behavior.
201-
obj.TankLocation = domain.TankLocationBank
201+
convertToBankTank(obj)
202202
}
203203
// Early level: tank remains frozen (moveTank skips road tanks
204204
// while bridgeDestroyed is true).

pkg/logic/collision_test.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/morozov/river-raid-ebiten/pkg/assets"
77
"github.com/morozov/river-raid-ebiten/pkg/domain"
8+
"github.com/morozov/river-raid-ebiten/pkg/platform"
89
"github.com/morozov/river-raid-ebiten/pkg/state"
910
)
1011

@@ -511,15 +512,18 @@ func TestApplyBridgeDestroyedTanks_InGap(t *testing.T) {
511512
}
512513
}
513514

514-
func TestApplyBridgeDestroyedTanks_OnBank_LateLevel(t *testing.T) {
515+
func TestApplyBridgeDestroyedTanks_OnBank_LateLevel_LeftBank(t *testing.T) {
515516
t.Parallel()
516517

518+
// Tank is left of the gap (X=0x20, X+spriteWidth=0x2A < 0x70).
517519
vp := state.NewViewport()
518520
obj := &state.ViewportObject{
519521
X: 0x20, Y: 50,
520522
Type: domain.ObjectTank,
521523
TankLocation: domain.TankLocationRoad,
522524
Activated: true,
525+
MinX: 8, // road-tank bounds (wide, full screen)
526+
MaxX: 238, // road-tank bounds
523527
}
524528
vp.Objects = append(vp.Objects, obj)
525529

@@ -532,6 +536,47 @@ func TestApplyBridgeDestroyedTanks_OnBank_LateLevel(t *testing.T) {
532536
if obj.TankLocation != domain.TankLocationBank {
533537
t.Errorf("TankLocation = %v, want TankLocationBank", obj.TankLocation)
534538
}
539+
if obj.MinX != 0 {
540+
t.Errorf("MinX = %d, want 0", obj.MinX)
541+
}
542+
wantMaxX := tankGapLeftEdge - assets.SpriteTankWidth - boundaryPadding
543+
if obj.MaxX != wantMaxX {
544+
t.Errorf("MaxX = %d, want %d (gap left edge minus sprite width minus padding)", obj.MaxX, wantMaxX)
545+
}
546+
}
547+
548+
func TestApplyBridgeDestroyedTanks_OnBank_LateLevel_RightBank(t *testing.T) {
549+
t.Parallel()
550+
551+
// Tank is right of the gap (X=0xA0 > 0x90).
552+
vp := state.NewViewport()
553+
obj := &state.ViewportObject{
554+
X: 0xA0, Y: 50,
555+
Type: domain.ObjectTank,
556+
TankLocation: domain.TankLocationRoad,
557+
Activated: true,
558+
MinX: 8, // road-tank bounds (wide, full screen)
559+
MaxX: 238, // road-tank bounds
560+
}
561+
vp.Objects = append(vp.Objects, obj)
562+
563+
var result CollisionResult
564+
bridgeTarget{vp: vp, bridgeIndex: bridgeEarlyLevel + 1}.onHit(&result)
565+
566+
if len(result.DestroyObjects) != 0 {
567+
t.Errorf("DestroyObjects = %v, want empty (tank should become bank-tank)", result.DestroyObjects)
568+
}
569+
if obj.TankLocation != domain.TankLocationBank {
570+
t.Errorf("TankLocation = %v, want TankLocationBank", obj.TankLocation)
571+
}
572+
wantMinX := tankGapRightEdge + boundaryPadding
573+
if obj.MinX != wantMinX {
574+
t.Errorf("MinX = %d, want %d (gap right edge plus padding)", obj.MinX, wantMinX)
575+
}
576+
wantMaxX := platform.ScreenWidth - assets.SpriteTankWidth
577+
if obj.MaxX != wantMaxX {
578+
t.Errorf("MaxX = %d, want %d (screen width minus sprite width)", obj.MaxX, wantMaxX)
579+
}
535580
}
536581

537582
func TestApplyBridgeDestroyedTanks_OnBank_EarlyLevel(t *testing.T) {

pkg/logic/enemy_ai.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ func initializeObjectBoundaries(obj *state.ViewportObject, terrain TerrainBuffer
6565
}
6666
}
6767

68+
// convertToBankTank switches a road tank to bank-tank behavior after bridge destruction
69+
// on a late level (spec §7.5.4). Bounds are derived from the bridge gap position so the
70+
// tank cannot cross the gap — it will stop at the bank edge and fire.
71+
func convertToBankTank(obj *state.ViewportObject) {
72+
obj.TankLocation = domain.TankLocationBank
73+
sprite := assets.SpriteObjects[domain.ObjectTank]
74+
if obj.X+tankGapProbe < tankGapLeftEdge {
75+
obj.MinX = 0
76+
obj.MaxX = tankGapLeftEdge - sprite.Width - boundaryPadding
77+
} else {
78+
obj.MinX = tankGapRightEdge + boundaryPadding
79+
obj.MaxX = platform.ScreenWidth - sprite.Width
80+
}
81+
}
82+
6883
// moveEnemies updates all activated enemy positions based on their type-specific AI.
6984
// gameplayMode is used to suppress helicopter missile firing during scroll-in.
7085
// bridgeDestroyed freezes road tank movement when true.

0 commit comments

Comments
 (0)