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