sidprak commited on
Commit
1a1efd5
Β·
verified Β·
1 Parent(s): b65dcd6

Build me a snake game with close to 3d visualization

Browse files
Files changed (2) hide show
  1. README.md +8 -5
  2. index.html +487 -19
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Slithersphere 3d
3
- emoji: πŸ‘
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: SlitherSphere 3D πŸπŸŒ€
3
+ colorFrom: pink
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,487 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SlitherSphere 3D</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/feather-icons"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
13
+
14
+ body {
15
+ font-family: 'Press Start 2P', cursive;
16
+ overflow: hidden;
17
+ touch-action: none;
18
+ }
19
+
20
+ #game-container {
21
+ perspective: 1000px;
22
+ transform-style: preserve-3d;
23
+ }
24
+
25
+ .snake-segment {
26
+ box-shadow: 0 0 15px rgba(110, 231, 183, 0.7);
27
+ transition: all 0.2s ease-out;
28
+ }
29
+
30
+ .food {
31
+ animation: pulse 1.5s infinite alternate;
32
+ box-shadow: 0 0 20px rgba(236, 72, 153, 0.8);
33
+ }
34
+
35
+ @keyframes pulse {
36
+ 0% { transform: scale(1) translateZ(0); }
37
+ 100% { transform: scale(1.2) translateZ(10px); }
38
+ }
39
+
40
+ #vanta-bg {
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ z-index: -1;
47
+ opacity: 0.3;
48
+ }
49
+ </style>
50
+ </head>
51
+ <body class="bg-gray-900 text-teal-300 min-h-screen flex flex-col items-center justify-center">
52
+ <div id="vanta-bg"></div>
53
+
54
+ <div class="text-center mb-8">
55
+ <h1 class="text-4xl md:text-6xl font-bold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-pink-500">
56
+ SLITHERSPHERE 3D
57
+ </h1>
58
+ <div class="flex justify-center gap-8 text-xl">
59
+ <div>
60
+ <p class="text-pink-400">SCORE</p>
61
+ <p id="score" class="text-teal-300">0</p>
62
+ </div>
63
+ <div>
64
+ <p class="text-pink-400">HIGH SCORE</p>
65
+ <p id="high-score" class="text-teal-300">0</p>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ <div id="game-container" class="relative w-full max-w-2xl aspect-square bg-gray-800 rounded-xl overflow-hidden shadow-2xl shadow-black/50">
71
+ <canvas id="game-canvas" class="w-full h-full"></canvas>
72
+
73
+ <div id="game-over" class="absolute inset-0 bg-black/80 flex flex-col items-center justify-center hidden">
74
+ <h2 class="text-4xl text-pink-500 mb-6">GAME OVER</h2>
75
+ <p class="text-xl text-teal-300 mb-8">Score: <span id="final-score">0</span></p>
76
+ <button id="restart-btn" class="px-8 py-3 bg-gradient-to-r from-teal-500 to-pink-500 rounded-full font-bold hover:scale-105 transition-transform">
77
+ PLAY AGAIN
78
+ </button>
79
+ </div>
80
+
81
+ <div id="start-screen" class="absolute inset-0 bg-black/80 flex flex-col items-center justify-center">
82
+ <h2 class="text-4xl text-teal-300 mb-6">SLITHERSPHERE</h2>
83
+ <p class="text-pink-400 mb-8 text-center px-4">Use arrow keys or swipe to control the snake</p>
84
+ <button id="start-btn" class="px-8 py-3 bg-gradient-to-r from-teal-500 to-pink-500 rounded-full font-bold hover:scale-105 transition-transform">
85
+ START GAME
86
+ </button>
87
+ </div>
88
+ </div>
89
+
90
+ <div class="mt-8 flex gap-4">
91
+ <button id="sound-btn" class="p-3 bg-gray-800 rounded-full hover:bg-gray-700 transition-colors">
92
+ <i data-feather="volume-2" class="text-teal-400"></i>
93
+ </button>
94
+ <button id="fullscreen-btn" class="p-3 bg-gray-800 rounded-full hover:bg-gray-700 transition-colors">
95
+ <i data-feather="maximize" class="text-teal-400"></i>
96
+ </button>
97
+ </div>
98
+
99
+ <audio id="eat-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3" preload="auto"></audio>
100
+ <audio id="gameover-sound" src="https://assets.mixkit.co/sfx/preview/mixkit-retro-arcade-lose-2027.mp3" preload="auto"></audio>
101
+ <audio id="bg-music" loop src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" preload="auto"></audio>
102
+
103
+ <script>
104
+ // Initialize Vanta.js background
105
+ VANTA.GLOBE({
106
+ el: "#vanta-bg",
107
+ mouseControls: true,
108
+ touchControls: true,
109
+ gyroControls: false,
110
+ minHeight: 200.00,
111
+ minWidth: 200.00,
112
+ scale: 1.00,
113
+ scaleMobile: 1.00,
114
+ color: 0x3b82f6,
115
+ backgroundColor: 0x111827,
116
+ size: 0.8
117
+ });
118
+
119
+ // Game variables
120
+ const canvas = document.getElementById('game-canvas');
121
+ const ctx = canvas.getContext('2d');
122
+ const scoreElement = document.getElementById('score');
123
+ const highScoreElement = document.getElementById('high-score');
124
+ const finalScoreElement = document.getElementById('final-score');
125
+ const gameOverScreen = document.getElementById('game-over');
126
+ const startScreen = document.getElementById('start-screen');
127
+ const startBtn = document.getElementById('start-btn');
128
+ const restartBtn = document.getElementById('restart-btn');
129
+ const soundBtn = document.getElementById('sound-btn');
130
+ const fullscreenBtn = document.getElementById('fullscreen-btn');
131
+ const eatSound = document.getElementById('eat-sound');
132
+ const gameoverSound = document.getElementById('gameover-sound');
133
+ const bgMusic = document.getElementById('bg-music');
134
+
135
+ let gridSize = 20;
136
+ let tileSize;
137
+ let snake = [];
138
+ let food = {};
139
+ let direction = 'right';
140
+ let nextDirection = 'right';
141
+ let gameSpeed = 150;
142
+ let score = 0;
143
+ let highScore = localStorage.getItem('highScore') || 0;
144
+ let gameInterval;
145
+ let isPaused = false;
146
+ let isSoundOn = true;
147
+ let touchStartX = 0;
148
+ let touchStartY = 0;
149
+
150
+ // Set canvas size
151
+ function resizeCanvas() {
152
+ const container = document.getElementById('game-container');
153
+ const size = Math.min(container.clientWidth, container.clientHeight);
154
+ canvas.width = size;
155
+ canvas.height = size;
156
+ tileSize = canvas.width / gridSize;
157
+ }
158
+
159
+ // Initialize game
160
+ function initGame() {
161
+ resizeCanvas();
162
+
163
+ // Create snake
164
+ snake = [];
165
+ for (let i = 3; i >= 0; i--) {
166
+ snake.push({ x: i, y: 0 });
167
+ }
168
+
169
+ // Create food
170
+ generateFood();
171
+
172
+ // Reset score
173
+ score = 0;
174
+ scoreElement.textContent = score;
175
+ highScoreElement.textContent = highScore;
176
+
177
+ // Reset direction
178
+ direction = 'right';
179
+ nextDirection = 'right';
180
+
181
+ // Hide game over screen
182
+ gameOverScreen.classList.add('hidden');
183
+
184
+ // Start game loop
185
+ if (gameInterval) clearInterval(gameInterval);
186
+ gameInterval = setInterval(gameLoop, gameSpeed);
187
+
188
+ // Play background music
189
+ if (isSoundOn) {
190
+ bgMusic.currentTime = 0;
191
+ bgMusic.play().catch(e => console.log("Autoplay prevented:", e));
192
+ }
193
+ }
194
+
195
+ // Generate food at random position
196
+ function generateFood() {
197
+ let validPosition = false;
198
+
199
+ while (!validPosition) {
200
+ food = {
201
+ x: Math.floor(Math.random() * gridSize),
202
+ y: Math.floor(Math.random() * gridSize)
203
+ };
204
+
205
+ validPosition = true;
206
+
207
+ // Check if food is on snake
208
+ for (let segment of snake) {
209
+ if (segment.x === food.x && segment.y === food.y) {
210
+ validPosition = false;
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ // Main game loop
218
+ function gameLoop() {
219
+ if (isPaused) return;
220
+
221
+ // Move snake
222
+ direction = nextDirection;
223
+ let head = { ...snake[0] };
224
+
225
+ switch (direction) {
226
+ case 'up':
227
+ head.y--;
228
+ break;
229
+ case 'down':
230
+ head.y++;
231
+ break;
232
+ case 'left':
233
+ head.x--;
234
+ break;
235
+ case 'right':
236
+ head.x++;
237
+ break;
238
+ }
239
+
240
+ // Check wall collision
241
+ if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) {
242
+ gameOver();
243
+ return;
244
+ }
245
+
246
+ // Check self collision
247
+ for (let segment of snake) {
248
+ if (segment.x === head.x && segment.y === head.y) {
249
+ gameOver();
250
+ return;
251
+ }
252
+ }
253
+
254
+ // Add new head
255
+ snake.unshift(head);
256
+
257
+ // Check food collision
258
+ if (head.x === food.x && head.y === food.y) {
259
+ score++;
260
+ scoreElement.textContent = score;
261
+
262
+ if (score > highScore) {
263
+ highScore = score;
264
+ highScoreElement.textContent = highScore;
265
+ localStorage.setItem('highScore', highScore);
266
+ }
267
+
268
+ if (isSoundOn) eatSound.play();
269
+
270
+ // Increase speed every 5 points
271
+ if (score % 5 === 0) {
272
+ clearInterval(gameInterval);
273
+ gameSpeed = Math.max(50, gameSpeed - 10);
274
+ gameInterval = setInterval(gameLoop, gameSpeed);
275
+ }
276
+
277
+ generateFood();
278
+ } else {
279
+ // Remove tail if no food eaten
280
+ snake.pop();
281
+ }
282
+
283
+ // Draw game
284
+ drawGame();
285
+ }
286
+
287
+ // Draw game elements
288
+ function drawGame() {
289
+ // Clear canvas
290
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
291
+
292
+ // Draw grid
293
+ ctx.strokeStyle = 'rgba(75, 85, 99, 0.3)';
294
+ ctx.lineWidth = 0.5;
295
+
296
+ for (let i = 0; i < gridSize; i++) {
297
+ // Vertical lines
298
+ ctx.beginPath();
299
+ ctx.moveTo(i * tileSize, 0);
300
+ ctx.lineTo(i * tileSize, canvas.height);
301
+ ctx.stroke();
302
+
303
+ // Horizontal lines
304
+ ctx.beginPath();
305
+ ctx.moveTo(0, i * tileSize);
306
+ ctx.lineTo(canvas.width, i * tileSize);
307
+ ctx.stroke();
308
+ }
309
+
310
+ // Draw food with 3D effect
311
+ const foodX = food.x * tileSize + tileSize / 2;
312
+ const foodY = food.y * tileSize + tileSize / 2;
313
+ const foodRadius = tileSize * 0.4;
314
+
315
+ // Food glow
316
+ const foodGradient = ctx.createRadialGradient(
317
+ foodX, foodY, 0,
318
+ foodX, foodY, foodRadius * 1.5
319
+ );
320
+ foodGradient.addColorStop(0, 'rgba(236, 72, 153, 0.8)');
321
+ foodGradient.addColorStop(1, 'rgba(236, 72, 153, 0)');
322
+
323
+ ctx.fillStyle = foodGradient;
324
+ ctx.beginPath();
325
+ ctx.arc(foodX, foodY, foodRadius * 1.5, 0, Math.PI * 2);
326
+ ctx.fill();
327
+
328
+ // Food main
329
+ const foodMainGradient = ctx.createRadialGradient(
330
+ foodX, foodY - foodRadius * 0.2, 0,
331
+ foodX, foodY, foodRadius
332
+ );
333
+ foodMainGradient.addColorStop(0, '#ec4899');
334
+ foodMainGradient.addColorStop(1, '#be185d');
335
+
336
+ ctx.fillStyle = foodMainGradient;
337
+ ctx.beginPath();
338
+ ctx.arc(foodX, foodY, foodRadius, 0, Math.PI * 2);
339
+ ctx.fill();
340
+
341
+ // Food highlight
342
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
343
+ ctx.beginPath();
344
+ ctx.arc(
345
+ foodX - foodRadius * 0.3,
346
+ foodY - foodRadius * 0.3,
347
+ foodRadius * 0.2,
348
+ 0, Math.PI * 2
349
+ );
350
+ ctx.fill();
351
+
352
+ // Draw snake with 3D effect
353
+ for (let i = 0; i < snake.length; i++) {
354
+ const segment = snake[i];
355
+ const x = segment.x * tileSize;
356
+ const y = segment.y * tileSize;
357
+ const size = tileSize * 0.9;
358
+ const offset = (tileSize - size) / 2;
359
+ const isHead = i === 0;
360
+
361
+ // Snake segment shadow
362
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
363
+ ctx.beginPath();
364
+ ctx.roundRect(
365
+ x + offset + 3,
366
+ y + offset + 3,
367
+ size,
368
+ size,
369
+ [size * 0.2]
370
+ );
371
+ ctx.fill();
372
+
373
+ // Snake segment main
374
+ const segmentGradient = ctx.createLinearGradient(
375
+ x, y,
376
+ x + tileSize, y + tileSize
377
+ );
378
+
379
+ if (isHead) {
380
+ segmentGradient.addColorStop(0, '#2dd4bf');
381
+ segmentGradient.addColorStop(1, '#0d9488');
382
+ } else {
383
+ const intensity = 1 - (i / snake.length) * 0.5;
384
+ segmentGradient.addColorStop(0, `rgba(45, 212, 191, ${intensity})`);
385
+ segmentGradient.addColorStop(1, `rgba(13, 148, 136, ${intensity})`);
386
+ }
387
+
388
+ ctx.fillStyle = segmentGradient;
389
+ ctx.beginPath();
390
+ ctx.roundRect(
391
+ x + offset,
392
+ y + offset,
393
+ size,
394
+ size,
395
+ [size * 0.2]
396
+ );
397
+ ctx.fill();
398
+
399
+ // Snake segment highlight
400
+ if (isHead) {
401
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
402
+ ctx.beginPath();
403
+ ctx.arc(
404
+ x + offset + size * 0.3,
405
+ y + offset + size * 0.3,
406
+ size * 0.15,
407
+ 0, Math.PI * 2
408
+ );
409
+ ctx.fill();
410
+
411
+ // Eyes
412
+ const eyeOffsetX = direction === 'left' ? -0.2 :
413
+ direction === 'right' ? 0.2 : 0;
414
+ const eyeOffsetY = direction === 'up' ? -0.2 :
415
+ direction === 'down' ? 0.2 : 0;
416
+
417
+ // Left eye
418
+ ctx.fillStyle = 'white';
419
+ ctx.beginPath();
420
+ ctx.arc(
421
+ x + offset + size * 0.5 + eyeOffsetX * size * 0.3,
422
+ y + offset + size * 0.4 + eyeOffsetY * size * 0.3,
423
+ size * 0.08,
424
+ 0, Math.PI * 2
425
+ );
426
+ ctx.fill();
427
+
428
+ // Right eye
429
+ ctx.beginPath();
430
+ ctx.arc(
431
+ x + offset + size * 0.7 + eyeOffsetX * size * 0.3,
432
+ y + offset + size * 0.4 + eyeOffsetY * size * 0.3,
433
+ size * 0.08,
434
+ 0, Math.PI * 2
435
+ );
436
+ ctx.fill();
437
+
438
+ // Pupils
439
+ ctx.fillStyle = 'black';
440
+ ctx.beginPath();
441
+ ctx.arc(
442
+ x + offset + size * 0.5 + eyeOffsetX * size * 0.35,
443
+ y + offset + size * 0.4 + eyeOffsetY * size * 0.35,
444
+ size * 0.04,
445
+ 0, Math.PI * 2
446
+ );
447
+ ctx.fill();
448
+
449
+ ctx.beginPath();
450
+ ctx.arc(
451
+ x + offset + size * 0.7 + eyeOffsetX * size * 0.35,
452
+ y + offset + size * 0.4 + eyeOffsetY * size * 0.35,
453
+ size * 0.04,
454
+ 0, Math.PI * 2
455
+ );
456
+ ctx.fill();
457
+ }
458
+ }
459
+ }
460
+
461
+ // Game over
462
+ function gameOver() {
463
+ clearInterval(gameInterval);
464
+ gameOverScreen.classList.remove('hidden');
465
+ finalScoreElement.textContent = score;
466
+
467
+ if (isSoundOn) {
468
+ bgMusic.pause();
469
+ gameoverSound.play();
470
+ }
471
+ }
472
+
473
+ // Event listeners
474
+ window.addEventListener('resize', () => {
475
+ resizeCanvas();
476
+ drawGame();
477
+ });
478
+
479
+ document.addEventListener('keydown', (e) => {
480
+ switch (e.key) {
481
+ case 'ArrowUp':
482
+ if (direction !== 'down') nextDirection = 'up';
483
+ break;
484
+ case 'ArrowDown':
485
+ if (direction !==
486
+ </body>
487
+ </html>