@@ -6,6 +6,9 @@ canvas.height = 600;
66
77const playerColors = [ "white" , "lime" , "cyan" , "orange" , "violet" ] ;
88const enemyTypes = [ "vi" , "vim" , "neovim" ] ;
9+ let enemyBullets = [ ] ;
10+ let gameOver = false ;
11+ let level = 1 ;
912
1013class Player {
1114 constructor ( x , color ) {
@@ -18,18 +21,28 @@ class Player {
1821 this . bullets = [ ] ;
1922 this . lastShot = 0 ;
2023 this . active = false ;
24+ this . lives = 3 ;
25+ this . score = 0 ;
26+ this . alive = true ;
27+ this . respawnTimer = 0 ;
28+ this . respawnCountdown = null ;
2129 }
2230
2331 draw ( ) {
24- if ( this . active ) {
32+ if ( this . active && this . alive ) {
2533 ctx . fillStyle = this . color ;
2634 ctx . fillRect ( this . x - this . width / 2 , this . y , this . width , this . height ) ;
35+ } else if ( ! this . alive && this . lives > 0 ) {
36+ const secondsLeft = Math . ceil ( ( 5000 - ( Date . now ( ) - this . respawnTimer ) ) / 1000 ) ;
37+ ctx . fillStyle = "white" ;
38+ ctx . font = "14px sans-serif" ;
39+ ctx . fillText ( `P${ this === player1 ? 1 : 2 } respawn in ${ secondsLeft } ` , this . x - 40 , this . y - 10 ) ;
2740 }
2841 }
2942
3043 shoot ( ) {
3144 const now = Date . now ( ) ;
32- if ( now - this . lastShot > 1000 && this . active ) {
45+ if ( now - this . lastShot > 1000 && this . active && this . alive ) {
3346 this . bullets . push ( { x : this . x , y : this . y } ) ;
3447 this . lastShot = now ;
3548 }
@@ -41,6 +54,22 @@ class Player {
4154 ctx . fillStyle = this . color ;
4255 this . bullets . forEach ( b => ctx . fillRect ( b . x - 2 , b . y , 4 , 10 ) ) ;
4356 }
57+
58+ takeHit ( ) {
59+ if ( ! this . alive || this . lives <= 0 ) return ;
60+ this . lives -= 1 ;
61+ this . alive = false ;
62+ this . respawnTimer = Date . now ( ) ;
63+ }
64+
65+ updateRespawn ( ) {
66+ if ( ! this . alive && this . lives > 0 ) {
67+ const now = Date . now ( ) ;
68+ if ( now - this . respawnTimer >= 5000 ) {
69+ this . alive = true ;
70+ }
71+ }
72+ }
4473}
4574
4675class Enemy {
@@ -63,100 +92,145 @@ class Enemy {
6392 this . dx *= - 1 ;
6493 this . y += 10 ;
6594 }
66- }
67- }
68-
69- const player1 = new Player ( canvas . width / 2 - 60 , playerColors [ Math . floor ( Math . random ( ) * playerColors . length ) ] ) ;
70- const player2 = new Player ( canvas . width / 2 + 60 , playerColors [ Math . floor ( Math . random ( ) * playerColors . length ) ] ) ;
71-
7295
73- const ws = new WebSocket ( "ws://" + location . hostname + "/ws" ) ;
74-
75- ws . onmessage = ( event ) => {
76- const data = JSON . parse ( event . data ) ;
77-
78- // joystick 1
79- if ( data . j1x < 1500 ) {
80- player1 . x -= player1 . speed ;
81- player1 . active = true ;
82- } else if ( data . j1x > 3500 ) {
83- player1 . x += player1 . speed ;
84- player1 . active = true ;
85- }
86- if ( data . j1f ) {
87- player1 . shoot ( ) ;
88- player1 . active = true ;
89- }
90-
91- // joystick 2
92- if ( data . j2x < 1500 ) {
93- player2 . x -= player2 . speed ;
94- player2 . active = true ;
95- } else if ( data . j2x > 3500 ) {
96- player2 . x += player2 . speed ;
97- player2 . active = true ;
98- }
99- if ( data . j2f ) {
100- player2 . shoot ( ) ;
101- player2 . active = true ;
96+ if ( this . y > canvas . height - 100 && ! gameOver ) {
97+ gameOver = true ;
98+ setTimeout ( ( ) => alert ( "Game Over – wróg dotarł do bazy!" ) , 10 ) ;
99+ }
102100 }
103- } ;
101+ }
104102
103+ let player1 , player2 ;
104+ let enemies = [ ] ;
105105
106+ function newGame ( ) {
107+ player1 = new Player ( canvas . width / 2 - 60 , randomColor ( ) ) ;
108+ player2 = new Player ( canvas . width / 2 + 60 , randomColor ( ) ) ;
109+ enemyBullets = [ ] ;
110+ level = 1 ;
111+ gameOver = false ;
112+ createEnemies ( ) ;
113+ }
106114
107- let enemies = [ ] ;
115+ function randomColor ( ) {
116+ return playerColors [ Math . floor ( Math . random ( ) * playerColors . length ) ] ;
117+ }
108118
109119function createEnemies ( ) {
110120 enemies = [ ] ;
111- for ( let row = 0 ; row < 2 ; row ++ ) {
112- for ( let col = 0 ; col < 6 ; col ++ ) {
113- enemies . push ( new Enemy ( 100 + col * 60 , 50 + row * 50 , enemyTypes [ Math . floor ( Math . random ( ) * 3 ) ] ) ) ;
121+ const rows = 2 + level ;
122+ const cols = 4 + level ;
123+ for ( let row = 0 ; row < rows ; row ++ ) {
124+ for ( let col = 0 ; col < cols ; col ++ ) {
125+ enemies . push ( new Enemy ( 60 + col * 60 , 50 + row * 40 , enemyTypes [ Math . floor ( Math . random ( ) * 3 ) ] ) ) ;
114126 }
115127 }
116128}
117129
118130function checkCollisions ( player ) {
119131 player . bullets . forEach ( ( b , i ) => {
120132 enemies . forEach ( ( e , j ) => {
121- if (
122- b . x > e . x - 10 &&
123- b . x < e . x + 30 &&
124- b . y < e . y + 16 &&
125- b . y > e . y
126- ) {
127- // trafienie
133+ if ( b . x > e . x - 10 && b . x < e . x + 30 && b . y < e . y + 16 && b . y > e . y ) {
128134 enemies . splice ( j , 1 ) ;
129135 player . bullets . splice ( i , 1 ) ;
136+ player . score += 1 ;
137+ }
138+ } ) ;
139+
140+ enemyBullets . forEach ( ( eb , k ) => {
141+ if ( b . x > eb . x - 4 && b . x < eb . x + 4 && b . y < eb . y + 10 && b . y > eb . y ) {
142+ enemyBullets . splice ( k , 1 ) ;
143+ player . bullets . splice ( i , 1 ) ;
130144 }
131145 } ) ;
132146 } ) ;
133147}
134148
149+ function checkPlayerHit ( player ) {
150+ if ( ! player . alive ) return ;
151+ enemyBullets . forEach ( ( b , i ) => {
152+ if (
153+ b . x > player . x - player . width / 2 &&
154+ b . x < player . x + player . width / 2 &&
155+ b . y > player . y &&
156+ b . y < player . y + player . height
157+ ) {
158+ player . takeHit ( ) ;
159+ enemyBullets . splice ( i , 1 ) ;
160+ }
161+ } ) ;
162+ }
163+
164+ function updateEnemyBullets ( ) {
165+ enemyBullets = enemyBullets . filter ( b => b . y < canvas . height ) ;
166+ enemyBullets . forEach ( b => {
167+ b . y += 4 ;
168+ ctx . fillStyle = "yellow" ;
169+ ctx . fillRect ( b . x - 2 , b . y , 4 , 10 ) ;
170+ } ) ;
171+ }
172+
173+ function enemyFire ( ) {
174+ if ( enemies . length === 0 || gameOver ) return ;
175+ const shooter = enemies [ Math . floor ( Math . random ( ) * enemies . length ) ] ;
176+ enemyBullets . push ( { x : shooter . x + 10 , y : shooter . y + 10 } ) ;
177+ }
178+
179+ function drawHUD ( ) {
180+ ctx . fillStyle = "white" ;
181+ ctx . font = "16px monospace" ;
182+ ctx . fillText ( `P1: x${ player1 . lives } (${ player1 . score } )` , 20 , 20 ) ;
183+ ctx . fillText ( `P2: x${ player2 . lives } (${ player2 . score } )` , canvas . width - 180 , 20 ) ;
184+ ctx . fillText ( `Level: ${ level } ` , canvas . width / 2 - 30 , 20 ) ;
185+ }
186+
135187function draw ( ) {
136188 ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
137189
190+ if ( gameOver ) {
191+ ctx . fillStyle = "red" ;
192+ ctx . font = "30px sans-serif" ;
193+ ctx . fillText ( "GAME OVER" , canvas . width / 2 - 100 , canvas . height / 2 ) ;
194+ return ;
195+ }
196+
197+ player1 . updateRespawn ( ) ;
198+ player2 . updateRespawn ( ) ;
199+
138200 player1 . draw ( ) ;
139201 player1 . updateBullets ( ) ;
140202 checkCollisions ( player1 ) ;
203+ checkPlayerHit ( player1 ) ;
141204
142205 if ( player2 . active ) {
143206 player2 . draw ( ) ;
144207 player2 . updateBullets ( ) ;
145208 checkCollisions ( player2 ) ;
209+ checkPlayerHit ( player2 ) ;
146210 }
147211
148212 enemies . forEach ( e => {
149213 e . update ( ) ;
150214 e . draw ( ) ;
151215 } ) ;
152216
217+ updateEnemyBullets ( ) ;
218+ drawHUD ( ) ;
219+
220+ // Level progression
221+ if ( enemies . length === 0 && ! gameOver ) {
222+ level ++ ;
223+ createEnemies ( ) ;
224+ }
225+
153226 requestAnimationFrame ( draw ) ;
154227}
155228
156- createEnemies ( ) ;
229+ setInterval ( enemyFire , 1500 ) ;
230+ newGame ( ) ;
157231draw ( ) ;
158232
159- // Klawiatura – player 1: A/D/Spacja
233+ // Input: keyboard
160234document . addEventListener ( "keydown" , ( e ) => {
161235 if ( e . key === "a" ) {
162236 player1 . x -= player1 . speed ;
@@ -171,7 +245,6 @@ document.addEventListener("keydown", (e) => {
171245 player1 . active = true ;
172246 }
173247
174- // Klawiatura – player 2: ←/→/Enter
175248 if ( e . key === "ArrowLeft" ) {
176249 player2 . x -= player2 . speed ;
177250 player2 . active = true ;
@@ -180,13 +253,17 @@ document.addEventListener("keydown", (e) => {
180253 player2 . x += player2 . speed ;
181254 player2 . active = true ;
182255 }
183- if ( e . key === "Enter " ) {
256+ if ( e . key === "ArrowUp " ) {
184257 player2 . shoot ( ) ;
185258 player2 . active = true ;
186259 }
260+
261+ if ( e . key === "r" ) {
262+ newGame ( ) ;
263+ }
187264} ) ;
188265
189- // Dotyk (prosty) – steruje player1
266+ // Input: touch
190267canvas . addEventListener ( "touchstart" , ( e ) => {
191268 e . preventDefault ( ) ;
192269 const x = e . touches [ 0 ] . clientX ;
@@ -201,3 +278,33 @@ canvas.addEventListener("touchstart", (e) => {
201278 player1 . shoot ( ) ;
202279 }
203280} , { passive : false } ) ;
281+
282+ // Input: joystick (WebSocket)
283+ const ws = new WebSocket ( "ws://" + location . hostname + "/ws" ) ;
284+ ws . onmessage = ( event ) => {
285+ const data = JSON . parse ( event . data ) ;
286+
287+ if ( data . j1x < 1500 ) {
288+ player1 . x -= player1 . speed ;
289+ player1 . active = true ;
290+ } else if ( data . j1x > 3500 ) {
291+ player1 . x += player1 . speed ;
292+ player1 . active = true ;
293+ }
294+ if ( data . j1f ) {
295+ player1 . shoot ( ) ;
296+ player1 . active = true ;
297+ }
298+
299+ if ( data . j2x < 1500 ) {
300+ player2 . x -= player2 . speed ;
301+ player2 . active = true ;
302+ } else if ( data . j2x > 3500 ) {
303+ player2 . x += player2 . speed ;
304+ player2 . active = true ;
305+ }
306+ if ( data . j2f ) {
307+ player2 . shoot ( ) ;
308+ player2 . active = true ;
309+ }
310+ } ;
0 commit comments