11import { CurvedSurface } from "./CurvedSurface"
22import { Rail } from "./Rail"
33import { SmoothPath } from "./SmoothPath"
4+ import { Bumper } from "./Obstacle"
45
56type Flipper = {
67 x : number
@@ -9,13 +10,6 @@ type Flipper = {
910 targetAngle : number
1011}
1112
12- type Obstacle = {
13- x : number
14- y : number
15- radius : number
16- points : number
17- }
18-
1913export class PinballGame {
2014 canvas : HTMLCanvasElement
2115 ctx : CanvasRenderingContext2D
@@ -35,12 +29,16 @@ export class PinballGame {
3529 right : Flipper
3630 }
3731
38- obstacles : Array < Obstacle >
32+ obstacles : Array < Bumper >
3933 rails : Array < Rail >
4034 curves : Array < CurvedSurface >
4135 smoothPaths : Array < SmoothPath >
4236
4337 score : number
38+ launching : boolean = false
39+ launchPower : number = 0
40+ maxLaunchPower : number = 30
41+ launchLaneWidth : number = 40
4442
4543 cleanup = new Set < ( ) => void > ( )
4644 rafId : number | null = null
@@ -52,8 +50,8 @@ export class PinballGame {
5250 this . height = this . canvas . height
5351
5452 this . ball = {
55- x : this . width / 2 ,
56- y : this . height - 100 ,
53+ x : this . width - 20 ,
54+ y : this . height - 50 ,
5755 radius : 8 ,
5856 vx : 0 ,
5957 vy : 0 ,
@@ -67,22 +65,26 @@ export class PinballGame {
6765 }
6866
6967 this . obstacles = [
70- { x : 100 , y : 200 , radius : 20 , points : 100 } ,
71- { x : 300 , y : 200 , radius : 20 , points : 100 } ,
72- { x : 200 , y : 150 , radius : 15 , points : 200 } ,
73- { x : 150 , y : 300 , radius : 25 , points : 150 } ,
74- { x : 250 , y : 300 , radius : 25 , points : 150 }
68+ new Bumper ( 100 , 200 , 20 , 100 ) ,
69+ new Bumper ( 300 , 200 , 20 , 100 ) ,
70+ new Bumper ( 200 , 150 , 15 , 200 ) ,
71+ new Bumper ( 150 , 300 , 25 , 150 ) ,
72+ new Bumper ( 250 , 300 , 25 , 150 )
7573 ]
7674
7775 this . rails = [
7876 new Rail ( 50 , 150 , 150 , 200 , 12 ) ,
79- new Rail ( 250 , 200 , 350 , 150 , 12 )
77+ new Rail ( 250 , 200 , 350 , 150 , 12 ) ,
78+ // Launch lane wall
79+ new Rail ( this . width - this . launchLaneWidth , this . height , this . width - this . launchLaneWidth , 80 , 5 )
8080 ]
8181
8282 this . curves = [
8383 new CurvedSurface ( 200 , 100 , 60 , 0 , Math . PI , 15 ) , // Half circle at top
8484 new CurvedSurface ( 100 , 400 , 40 , Math . PI / 4 , 3 * Math . PI / 4 , 10 ) ,
85- new CurvedSurface ( 300 , 400 , 40 , Math . PI / 4 , 3 * Math . PI / 4 , 10 )
85+ new CurvedSurface ( 300 , 400 , 40 , Math . PI / 4 , 3 * Math . PI / 4 , 10 ) ,
86+ // Top right curve to guide ball from launch lane
87+ new CurvedSurface ( this . width - 40 , 40 , 40 , 0 , Math . PI , 10 )
8688 ]
8789
8890 this . smoothPaths = [
@@ -115,7 +117,9 @@ export class PinballGame {
115117 }
116118 if ( e . key === ' ' ) {
117119 e . preventDefault ( )
118- this . launchBall ( )
120+ if ( this . ball . x > this . width - this . launchLaneWidth && this . ball . y > this . height - 100 ) {
121+ this . launching = true
122+ }
119123 }
120124 } , { signal : controller . signal } )
121125
@@ -126,14 +130,20 @@ export class PinballGame {
126130 if ( e . key === 'ArrowRight' || e . key === 'd' ) {
127131 this . flippers . right . targetAngle = 0
128132 }
133+ if ( e . key === ' ' ) {
134+ if ( this . launching ) {
135+ this . launching = false
136+ this . launchBall ( )
137+ }
138+ }
129139 } , { signal : controller . signal } )
130140 this . cleanup . add ( ( ) => controller . abort ( ) )
131141 }
132142
133143 launchBall ( ) {
134- if ( this . ball . y > this . height - 120 ) {
135- this . ball . vx = ( Math . random ( ) - 0.5 ) * 4
136- this . ball . vy = - 15
144+ if ( this . ball . x > this . width - this . launchLaneWidth && this . ball . y > this . height - 100 ) {
145+ this . ball . vy = - this . launchPower
146+ this . launchPower = 0
137147 }
138148 }
139149
@@ -163,24 +173,7 @@ export class PinballGame {
163173
164174 // Check obstacle collisions
165175 this . obstacles . forEach ( obstacle => {
166- const dx = this . ball . x - obstacle . x
167- const dy = this . ball . y - obstacle . y
168- const distance = Math . sqrt ( dx * dx + dy * dy )
169-
170- if ( distance < this . ball . radius + obstacle . radius ) {
171- // Collision response
172- const angle = Math . atan2 ( dy , dx )
173- this . ball . vx = Math . cos ( angle ) * 8
174- this . ball . vy = Math . sin ( angle ) * 8
175-
176- // Move ball out of obstacle
177- const overlap = this . ball . radius + obstacle . radius - distance
178- this . ball . x += Math . cos ( angle ) * overlap
179- this . ball . y += Math . sin ( angle ) * overlap
180-
181- // Add score
182- this . score += obstacle . points
183- }
176+ this . score += obstacle . handleBallCollision ( this . ball )
184177 } )
185178
186179 // Check rail collisions
@@ -240,8 +233,8 @@ export class PinballGame {
240233 }
241234
242235 resetBall ( ) {
243- this . ball . x = this . width / 2
244- this . ball . y = this . height - 100
236+ this . ball . x = this . width - 20
237+ this . ball . y = this . height - 50
245238 this . ball . vx = 0
246239 this . ball . vy = 0
247240 }
@@ -252,15 +245,7 @@ export class PinballGame {
252245 this . ctx . fillRect ( 0 , 0 , this . width , this . height )
253246
254247 // Draw obstacles
255- this . obstacles . forEach ( obstacle => {
256- this . ctx . beginPath ( )
257- this . ctx . arc ( obstacle . x , obstacle . y , obstacle . radius , 0 , Math . PI * 2 )
258- this . ctx . fillStyle = '#ff6b6b'
259- this . ctx . fill ( )
260- this . ctx . strokeStyle = '#ff4757'
261- this . ctx . lineWidth = 2
262- this . ctx . stroke ( )
263- } )
248+ this . obstacles . forEach ( obstacle => obstacle . draw ( this . ctx ) )
264249 this . rails . forEach ( rail => rail . draw ( this . ctx ) )
265250 this . curves . forEach ( curve => curve . draw ( this . ctx ) )
266251 this . smoothPaths . forEach ( path => path . draw ( this . ctx ) )
@@ -277,6 +262,17 @@ export class PinballGame {
277262 this . ctx . strokeStyle = '#ff9ff3'
278263 this . ctx . lineWidth = 2
279264 this . ctx . stroke ( )
265+
266+ // Draw score
267+ this . ctx . fillStyle = '#fff'
268+ this . ctx . font = '24px Arial'
269+ this . ctx . fillText ( `Score: ${ this . score } ` , 20 , 40 )
270+
271+ // Draw launch power
272+ if ( this . launching || this . launchPower > 0 ) {
273+ this . ctx . fillStyle = '#ff4757'
274+ this . ctx . fillRect ( this . width - 30 , this . height - 100 - this . launchPower * 3 , 20 , this . launchPower * 3 )
275+ }
280276 }
281277
282278 drawFlipper ( flipper : Flipper , isLeft : boolean ) {
@@ -294,6 +290,9 @@ export class PinballGame {
294290 }
295291
296292 gameLoop ( ) {
293+ if ( this . launching ) {
294+ this . launchPower = Math . min ( this . launchPower + 0.5 , this . maxLaunchPower )
295+ }
297296 this . updateBall ( )
298297 this . updateFlippers ( )
299298 this . render ( )
0 commit comments