JavaScript · 11180 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
54 function setup() {
55 let canvas = createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
56 canvas.parent('gameCanvas');
57
58 engine = Engine.create();
59 world = engine.world;
60
61 engine.world.gravity.y = 0;
62 engine.world.gravity.x = 0;
63
64 // Create boundaries
65 let topWall = Bodies.rectangle(width/2, -10, width, 20, { isStatic: true });
66 let bottomWall = Bodies.rectangle(width/2, height + 10, width, 20, { isStatic: true });
67 boundaries.push(topWall, bottomWall);
68
69 // Create spring paddle systems
70 let leftSystem = createSpringPaddleSystem('left', width, height);
71 leftSupport = leftSystem.support;
72 leftPaddle = leftSystem.paddle;
73 leftSpring = leftSystem.spring;
74
75 let rightSystem = createSpringPaddleSystem('right', width, height);
76 rightSupport = rightSystem.support;
77 rightPaddle = rightSystem.paddle;
78 rightSpring = rightSystem.spring;
79
80 // Create ball
81 ball = resetBall(null, world, width, height);
82 window.ball = ball;
83
84 // Add everything to the world
85 World.add(world, [
86 ...boundaries,
87 leftSupport, leftPaddle, leftSpring,
88 rightSupport, rightPaddle, rightSpring
89 ]);
90
91 // Set up collision handlers
92 setupCollisionHandlers(engine, ball, leftPaddle, rightPaddle, particles);
93 }
94
95 function draw() {
96 Engine.update(engine);
97
98 background(10, 10, 10);
99
100 if (gameState === 'menu') {
101 drawMenu(menuState);
102 } else {
103 handleEnhancedInput();
104 updateParticles();
105 checkBallPosition();
106
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 handleRotationInput(); // Add rotation handling
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 window.ball = ball; // ADD THIS LINE - Update global reference
202 gameStarted = false;
203 }
204
205 if (ballX > width + BALL_RADIUS) {
206 leftScore++;
207 updateScore();
208 ball = resetBall(ball, world, width, height);
209 window.ball = ball; // ADD THIS LINE - Update global reference
210 gameStarted = false;
211 }
212 }
213
214 function updateScore() {
215 document.getElementById('leftScore').textContent = leftScore;
216 document.getElementById('rightScore').textContent = rightScore;
217 }
218
219 // Input handlers
220 function keyPressed() {
221 keys[key] = true;
222 keys[keyCode] = true;
223
224 if (gameState === 'menu') {
225 handleMenuInput();
226 return;
227 }
228
229 if (!gameStarted && key !== ' ') {
230 gameStarted = true;
231 }
232
233 if (key === 'm' || key === 'M') {
234 aiEnabled = !aiEnabled;
235 gameMode = aiEnabled ? 'vs-cpu' : 'vs-human';
236 console.log("Switched to " + gameMode + " mode");
237 }
238
239 if (key === 'd' || key === 'D') {
240 if (aiState.difficulty === 'easy') {
241 aiState.difficulty = 'medium';
242 } else if (aiState.difficulty === 'medium') {
243 aiState.difficulty = 'hard';
244 } else {
245 aiState.difficulty = 'easy';
246 }
247 console.log("AI difficulty: " + aiState.difficulty);
248 }
249
250 if (key === ' ') {
251 leftScore = 0;
252 rightScore = 0;
253 updateScore();
254 ball = resetBall(ball, world, width, height);
255 window.ball = ball;
256 gameStarted = false;
257
258 inputBuffer.left = 0;
259 inputBuffer.right = 0;
260 mouseInput.active = false;
261
262 aiState.targetY = height / 2;
263 aiState.lastUpdateTime = 0;
264 aiState.mode = 'ANTICIPATING';
265 aiState.aggressionLevel = AI_SETTINGS[aiState.difficulty].aggression;
266 aiState.windupProgress = 0;
267
268 particles.length = 0;
269
270 console.log("Game reset!");
271 }
272
273 if (keyCode === 27) {
274 gameState = 'menu';
275 gameStarted = false;
276 particles.length = 0;
277 console.log("Returned to menu");
278 }
279 }
280
281 function handleMenuInput() {
282 if (keyCode === UP_ARROW) {
283 menuState.selectedOption = Math.max(0, menuState.selectedOption - 1);
284 } else if (keyCode === DOWN_ARROW) {
285 menuState.selectedOption = Math.min(menuState.options.length - 1, menuState.selectedOption + 1);
286 }
287
288 if (menuState.selectedOption === 0) {
289 if (keyCode === LEFT_ARROW) {
290 menuState.difficultySelected = Math.max(0, menuState.difficultySelected - 1);
291 } else if (keyCode === RIGHT_ARROW) {
292 menuState.difficultySelected = Math.min(menuState.difficulties.length - 1, menuState.difficultySelected + 1);
293 }
294 }
295
296 if (keyCode === ENTER || key === ' ') {
297 startGameWithSelection();
298 }
299 }
300
301 function startGameWithSelection() {
302 if (menuState.selectedOption === 0) {
303 aiEnabled = true;
304 gameMode = 'vs-cpu';
305 aiState.difficulty = menuState.difficulties[menuState.difficultySelected].toLowerCase();
306 } else {
307 aiEnabled = false;
308 gameMode = 'vs-human';
309 }
310
311 gameState = 'playing';
312 gameStarted = false;
313
314 leftScore = 0;
315 rightScore = 0;
316 updateScore();
317 ball = resetBall(ball, world, width, height);
318
319 inputBuffer.left = 0;
320 inputBuffer.right = 0;
321 mouseInput.active = false;
322
323 aiState.targetY = height / 2;
324 aiState.lastUpdateTime = 0;
325
326 particles.length = 0;
327
328 console.log("Started " + gameMode + " mode" + (aiEnabled ? " - Difficulty: " + aiState.difficulty : ""));
329 }
330
331 function keyReleased() {
332 keys[key] = false;
333 keys[keyCode] = false;
334 }
335
336 function mousePressed() {
337 if (gameState === 'menu') {
338 handleMenuClick();
339 return false;
340 }
341
342 if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) {
343 mouseInput.active = true;
344
345 if (!gameStarted) {
346 gameStarted = true;
347 }
348
349 return false;
350 }
351 }
352
353 function handleMenuClick() {
354 let startY = height/2 - 20;
355 let spacing = 60;
356
357 for (let i = 0; i < menuState.options.length; i++) {
358 let y = startY + i * spacing;
359
360 if (mouseY > y - 25 && mouseY < y + 25) {
361 if (menuState.selectedOption === i) {
362 startGameWithSelection();
363 } else {
364 menuState.selectedOption = i;
365 }
366 break;
367 }
368 }
369
370 if (menuState.selectedOption === 0) {
371 let diffY = startY + 28;
372 if (mouseY > diffY && mouseY < diffY + 20) {
373 menuState.difficultySelected = (menuState.difficultySelected + 1) % menuState.difficulties.length;
374 }
375 }
376 }
377
378 function mouseDragged() {
379 if (mouseInput.active) {
380 return false;
381 }
382 }
383
384 function mouseReleased() {
385 mouseInput.active = false;
386 inputBuffer.left *= 0.8;
387 inputBuffer.right *= 0.8;
388 }
389
390 function touchStarted() {
391 return mousePressed();
392 }
393
394 function touchMoved() {
395 return mouseDragged();
396 }
397
398 function touchEnded() {
399 mouseReleased();
400 return false;
401 }
402
403 // Helper functions
404 function getBallSpeed() {
405 let velocity = ball.velocity;
406 return Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
407 }