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