JavaScript · 11063 bytes Raw Blame History
1 // sprong.js - Main game file that ties all modules together
2
3 // Matter.js module aliases
4 const Body = Matter.Body;
5 const World = Matter.World;
6 const Engine = Matter.Engine;
7 const Bodies = Matter.Bodies;
8
9 // Global game variables
10 let ball;
11 let world;
12 let engine;
13 let boundaries = [];
14 let leftSupport, leftPaddle, leftSpring;
15 let rightSupport, rightPaddle, rightSpring;
16
17 // Game state
18 let leftScore = 0;
19 let rightScore = 0;
20 let aiEnabled = true;
21 let gameState = 'menu';
22 let gameMode = 'vs-cpu';
23 let gameStarted = false;
24
25 // Menu state
26 let menuState = {
27 selectedOption: 0,
28 options: ['1 Player vs CPU', '2 Player'],
29 difficultySelected: 1,
30 difficulties: ['Easy', 'Medium', 'Hard'],
31 showDifficulty: true
32 };
33
34 // Player input
35 let keys = {};
36 let inputBuffer = { left: 0, right: 0 };
37
38 // Touch/mouse input
39 let mouseInput = {
40 active: false,
41 targetY: 0,
42 leftPaddleTarget: 0,
43 rightPaddleTarget: 0,
44 smoothing: 0.08,
45 deadZone: 15
46 };
47
48 // Make necessary variables globally accessible for other scripts
49 window.inputBuffer = inputBuffer;
50 window.moveSupportEnhanced = moveSupportEnhanced;
51
52 function setup() {
53 let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
54 canvas.parent('gameCanvas');
55
56 engine = Engine.create();
57 world = engine.world;
58
59 engine.world.gravity.y = 0;
60 engine.world.gravity.x = 0;
61
62 // Create boundaries
63 let topWall = Bodies.rectangle(width/2, -10, width, 20, { isStatic: true });
64 let bottomWall = Bodies.rectangle(width/2, height + 10, width, 20, { isStatic: true });
65 boundaries.push(topWall, bottomWall);
66
67 // Create spring paddle systems
68 let leftSystem = createSpringPaddleSystem('left', width, height);
69 leftSupport = leftSystem.support;
70 leftPaddle = leftSystem.paddle;
71 leftSpring = leftSystem.spring;
72
73 let rightSystem = createSpringPaddleSystem('right', width, height);
74 rightSupport = rightSystem.support;
75 rightPaddle = rightSystem.paddle;
76 rightSpring = rightSystem.spring;
77
78 // Create ball
79 ball = resetBall(null, world, width, height);
80
81 // Add everything to the world
82 World.add(world, [
83 ...boundaries,
84 leftSupport, leftPaddle, leftSpring,
85 rightSupport, rightPaddle, rightSpring
86 ]);
87
88 // Set up collision handlers
89 setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles);
90 }
91
92 function draw() {
93 Engine.update(engine);
94
95 background(10, 10, 10);
96
97 if (gameState === 'menu') {
98 drawMenu(menuState);
99 } else {
100 handleEnhancedInput();
101 updateParticles();
102 checkBallPosition();
103
104 // Enhanced collision detection during bops
105 if (bopState.left.active || bopState.right.active) {
106 Engine.update(engine, 8);
107 Engine.update(engine, 8);
108 }
109
110 // Draw everything
111 drawParticles();
112 drawSpringsEnhanced(leftSupport, leftPaddle, rightSupport, rightPaddle);
113 drawPaddlesWithGlow(ball, leftPaddle, rightPaddle, bopState, aiEnabled, aiState, millis());
114 drawSupportPointsEnhanced(leftSupport, rightSupport, inputBuffer);
115 drawBallEnhanced(ball);
116 drawBoundaries();
117 drawCenterLine();
118 drawDebugInfo(ball, leftSupport, leftPaddle, rightSupport, rightPaddle,
119 inputBuffer, particles, gameMode, aiState, bopState, aiEnabled);
120
121 if (!gameStarted) {
122 drawStartMessage(aiEnabled, aiState.difficulty);
123 }
124 }
125 }
126
127 function handleEnhancedInput() {
128 handleKeyboardInput();
129 handleMouseTouchInput();
130 handleBopInput(keys, aiEnabled, millis(), leftPaddle, rightPaddle,
131 leftSupport, rightSupport, engine, particles);
132
133 if (aiEnabled && gameStarted) {
134 handleAI(millis(), ball, rightPaddle, rightSupport,
135 leftScore, rightScore, width, height,
136 bopState, activateBop, engine, particles);
137 }
138 }
139
140 function handleKeyboardInput() {
141 let leftInput = 0;
142 let rightInput = 0;
143
144 if (keys['w'] || keys['W']) leftInput -= 1;
145 if (keys['s'] || keys['S']) leftInput += 1;
146
147 if (!aiEnabled) {
148 if (keys['ArrowUp']) rightInput -= 1;
149 if (keys['ArrowDown']) rightInput += 1;
150 }
151
152 inputBuffer.left = lerp(inputBuffer.left, leftInput, INPUT_SMOOTHING);
153 if (!aiEnabled) {
154 inputBuffer.right = lerp(inputBuffer.right, rightInput, INPUT_SMOOTHING);
155 }
156
157 if (!mouseInput.active) {
158 if (Math.abs(inputBuffer.left) > 0.01) {
159 moveSupportEnhanced(leftSupport, inputBuffer.left * SUPPORT_SPEED, height);
160 }
161 if (!aiEnabled && Math.abs(inputBuffer.right) > 0.01) {
162 moveSupportEnhanced(rightSupport, inputBuffer.right * SUPPORT_SPEED, height);
163 }
164 }
165 }
166
167 function handleMouseTouchInput() {
168 if (!mouseInput.active) return;
169
170 let controllingLeft = mouseX < width / 2;
171 if (!controllingLeft && aiEnabled) return;
172
173 let targetSupport = controllingLeft ? leftSupport : rightSupport;
174 let currentY = targetSupport.position.y;
175 let targetY = mouseY;
176 let deltaY = targetY - currentY;
177
178 if (Math.abs(deltaY) < mouseInput.deadZone) {
179 return;
180 }
181
182 let movement = deltaY * MOUSE_LAG_FACTOR * TOUCH_SENSITIVITY;
183 movement = constrain(movement, -MOUSE_SPEED_LIMIT, MOUSE_SPEED_LIMIT);
184
185 moveSupportEnhanced(targetSupport, movement, height);
186
187 if (controllingLeft) {
188 inputBuffer.left = constrain(movement / MOUSE_SPEED_LIMIT, -1, 1);
189 } else if (!aiEnabled) {
190 inputBuffer.right = constrain(movement / MOUSE_SPEED_LIMIT, -1, 1);
191 }
192 }
193
194 function checkBallPosition() {
195 let ballX = ball.position.x;
196
197 if (ballX < -BALL_RADIUS) {
198 rightScore++;
199 updateScore();
200 ball = resetBall(ball, world, width, height);
201 gameStarted = false;
202 }
203
204 if (ballX > width + BALL_RADIUS) {
205 leftScore++;
206 updateScore();
207 ball = resetBall(ball, world, width, height);
208 gameStarted = false;
209 }
210 }
211
212 function updateScore() {
213 document.getElementById('leftScore').textContent = leftScore;
214 document.getElementById('rightScore').textContent = rightScore;
215 }
216
217 // Input handlers
218 function keyPressed() {
219 keys[key] = true;
220 keys[keyCode] = true;
221
222 if (gameState === 'menu') {
223 handleMenuInput();
224 return;
225 }
226
227 if (!gameStarted && key !== ' ') {
228 gameStarted = true;
229 }
230
231 if (key === 'm' || key === 'M') {
232 aiEnabled = !aiEnabled;
233 gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
234 console.log("Switched to " + gameMode + " mode");
235 }
236
237 if (key === 'd' || key === 'D') {
238 if (aiState.difficulty === 'easy') {
239 aiState.difficulty = 'medium';
240 } else if (aiState.difficulty === 'medium') {
241 aiState.difficulty = 'hard';
242 } else {
243 aiState.difficulty = 'easy';
244 }
245 console.log("AI difficulty: " + aiState.difficulty);
246 }
247
248 if (key === ' ') {
249 leftScore = 0;
250 rightScore = 0;
251 updateScore();
252 ball = resetBall(ball, world, width, height);
253 gameStarted = false;
254
255 inputBuffer.left = 0;
256 inputBuffer.right = 0;
257 mouseInput.active = false;
258
259 aiState.targetY = height / 2;
260 aiState.lastUpdateTime = 0;
261 aiState.mode = 'ANTICIPATING';
262 aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
263 aiState.windupProgress = 0;
264
265 particles.length = 0;
266
267 console.log("Game reset!");
268 }
269
270 if (keyCode === 27) {
271 gameState = 'menu';
272 gameStarted = false;
273 particles.length = 0;
274 console.log("Returned to menu");
275 }
276 }
277
278 function handleMenuInput() {
279 if (keyCode === UP_ARROW) {
280 menuState.selectedOption = Math.max(0, menuState.selectedOption - 1);
281 } else if (keyCode === DOWN_ARROW) {
282 menuState.selectedOption = Math.min(menuState.options.length - 1, menuState.selectedOption + 1);
283 }
284
285 if (menuState.selectedOption === 0) {
286 if (keyCode === LEFT_ARROW) {
287 menuState.difficultySelected = Math.max(0, menuState.difficultySelected - 1);
288 } else if (keyCode === RIGHT_ARROW) {
289 menuState.difficultySelected = Math.min(menuState.difficulties.length - 1, menuState.difficultySelected + 1);
290 }
291 }
292
293 if (keyCode === ENTER || key === ' ') {
294 startGameWithSelection();
295 }
296 }
297
298 function startGameWithSelection() {
299 if (menuState.selectedOption === 0) {
300 aiEnabled = true;
301 gameMode = 'vs-cpu';
302 aiState.difficulty = menuState.difficulties[menuState.difficultySelected].toLowerCase();
303 } else {
304 aiEnabled = false;
305 gameMode = 'vs-human';
306 }
307
308 gameState = 'playing';
309 gameStarted = false;
310
311 leftScore = 0;
312 rightScore = 0;
313 updateScore();
314 ball = resetBall(ball, world, width, height);
315
316 inputBuffer.left = 0;
317 inputBuffer.right = 0;
318 mouseInput.active = false;
319
320 aiState.targetY = height / 2;
321 aiState.lastUpdateTime = 0;
322
323 particles.length = 0;
324
325 console.log("Started " + gameMode + " mode" + (aiEnabled ? " - Difficulty: " + aiState.difficulty : ""));
326 }
327
328 function keyReleased() {
329 keys[key] = false;
330 keys[keyCode] = false;
331 }
332
333 function mousePressed() {
334 if (gameState === 'menu') {
335 handleMenuClick();
336 return false;
337 }
338
339 if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
340 mouseInput.active = true;
341
342 if (!gameStarted) {
343 gameStarted = true;
344 }
345
346 return false;
347 }
348 }
349
350 function handleMenuClick() {
351 let startY = height/2 - 20;
352 let spacing = 60;
353
354 for (let i = 0; i < menuState.options.length; i++) {
355 let y = startY + i * spacing;
356
357 if (mouseY > y - 25 && mouseY < y + 25) {
358 if (menuState.selectedOption === i) {
359 startGameWithSelection();
360 } else {
361 menuState.selectedOption = i;
362 }
363 break;
364 }
365 }
366
367 if (menuState.selectedOption === 0) {
368 let diffY = startY + 28;
369 if (mouseY > diffY && mouseY < diffY + 20) {
370 menuState.difficultySelected = (menuState.difficultySelected + 1) % menuState.difficulties.length;
371 }
372 }
373 }
374
375 function mouseDragged() {
376 if (mouseInput.active) {
377 return false;
378 }
379 }
380
381 function mouseReleased() {
382 mouseInput.active = false;
383 inputBuffer.left *= 0.8;
384 inputBuffer.right *= 0.8;
385 }
386
387 function touchStarted() {
388 return mousePressed();
389 }
390
391 function touchMoved() {
392 return mouseDragged();
393 }
394
395 function touchEnded() {
396 mouseReleased();
397 return false;
398 }
399
400 // Helper functions
401 function getBallSpeed() {
402 let velocity = ball.velocity;
403 return Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
404 }