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