Dembo commited on
Commit
563274e
·
verified ·
1 Parent(s): 643c3a4

build a 3d snake game

Browse files
Files changed (2) hide show
  1. index.html +14 -10
  2. snake-game.html +289 -0
index.html CHANGED
@@ -137,19 +137,19 @@
137
 
138
  <!-- Practice Area -->
139
  <div class="mt-12 bg-white rounded-xl shadow-md overflow-hidden">
140
- <div class="p-8 hovered-element">
141
  <h2 class="text-2xl font-bold text-center text-indigo-800 mb-6">Practice Zone</h2>
142
  <div class="mb-6">
143
  <label for="practice-word" class="block text-sm font-medium text-gray-700 mb-2">Type the word you hear:</label>
144
  <div class="flex items-center space-x-2 mb-4">
145
  <input type="text" id="practice-word" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" placeholder="Spell the word...">
146
  <button id="play-word" class="p-2 bg-indigo-100 text-indigo-600 rounded-md hover:bg-indigo-200 transition-colors">
147
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2 w-5 h-5 hovered-element"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
148
  </button>
149
  </div>
150
- <div id="feedback" class="bg-red-100 text-red-800 mt-2 p-3 rounded-md hovered-element"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle w-5 h-5 inline mr-2"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg> Not quite right. Try again!</div>
151
  </div>
152
- <div class="flex justify-center space-x-4 hovered-element">
153
  <button id="check-spelling" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md transition-colors">
154
  Check Spelling
155
  </button>
@@ -158,7 +158,7 @@
158
  </button>
159
  </div>
160
  </div>
161
- </div>
162
  </main>
163
 
164
  <!-- Footer -->
@@ -174,13 +174,17 @@
174
  <a href="#" class="hover:text-indigo-200 transition-colors">
175
  <i data-feather="award"></i>
176
  </a>
177
- <a href="#" class="hover:text-indigo-200 transition-colors">
178
- <i data-feather="settings"></i>
179
- </a>
180
- </div>
 
 
 
181
  <p class="text-indigo-200">© 2023 SpellBound - Making spelling magical</p>
182
  <p class="text-indigo-300 text-sm mt-2">Created with <i data-feather="heart" class="w-4 h-4 inline text-pink-300"></i> for word enthusiasts</p>
183
- </div>
 
184
  </footer>
185
  </div>
186
 
 
137
 
138
  <!-- Practice Area -->
139
  <div class="mt-12 bg-white rounded-xl shadow-md overflow-hidden">
140
+ <div class="p-8">
141
  <h2 class="text-2xl font-bold text-center text-indigo-800 mb-6">Practice Zone</h2>
142
  <div class="mb-6">
143
  <label for="practice-word" class="block text-sm font-medium text-gray-700 mb-2">Type the word you hear:</label>
144
  <div class="flex items-center space-x-2 mb-4">
145
  <input type="text" id="practice-word" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500" placeholder="Spell the word...">
146
  <button id="play-word" class="p-2 bg-indigo-100 text-indigo-600 rounded-md hover:bg-indigo-200 transition-colors">
147
+ <i data-feather="volume-2" class="w-5 h-5"></i>
148
  </button>
149
  </div>
150
+ <div id="feedback" class="hidden mt-2 p-3 rounded-md"></div>
151
  </div>
152
+ <div class="flex justify-center space-x-4">
153
  <button id="check-spelling" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-md transition-colors">
154
  Check Spelling
155
  </button>
 
158
  </button>
159
  </div>
160
  </div>
161
+ </div>
162
  </main>
163
 
164
  <!-- Footer -->
 
174
  <a href="#" class="hover:text-indigo-200 transition-colors">
175
  <i data-feather="award"></i>
176
  </a>
177
+ <a href="#" class="hover:text-indigo-200 transition-colors">
178
+ <i data-feather="settings"></i>
179
+ </a>
180
+ <a href="snake-game.html" class="hover:text-indigo-200 transition-colors">
181
+ <i data-feather="gamepad"></i>
182
+ </a>
183
+ </div>
184
  <p class="text-indigo-200">© 2023 SpellBound - Making spelling magical</p>
185
  <p class="text-indigo-300 text-sm mt-2">Created with <i data-feather="heart" class="w-4 h-4 inline text-pink-300"></i> for word enthusiasts</p>
186
+ <p class="text-indigo-300 text-sm mt-2"><a href="snake-game.html" class="underline hover:text-white">Play 3D Snake Game</a></p>
187
+ </div>
188
  </footer>
189
  </div>
190
 
snake-game.html ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>3D Snake Game - SpellBound</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
11
+ <style>
12
+ body { margin: 0; padding: 0; background: #1a202c; }
13
+ #game-container { position: relative; width: 100vw; height: 100vh; }
14
+ #ui-overlay {
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ height: 100%;
20
+ pointer-events: none;
21
+ z-index: 10;
22
+ }
23
+ .ui-element { pointer-events: auto; }
24
+ #score-display {
25
+ position: absolute;
26
+ top: 20px;
27
+ left: 20px;
28
+ color: white;
29
+ font-size: 24px;
30
+ background: rgba(0,0,0,0.5);
31
+ padding: 10px 20px;
32
+ border-radius: 10px;
33
+ }
34
+ #game-controls {
35
+ position: absolute;
36
+ bottom: 20px;
37
+ left: 50%;
38
+ transform: translateX(-50%);
39
+ display: flex;
40
+ gap: 10px;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div id="game-container">
46
+ <div id="ui-overlay">
47
+ <div id="score-display" class="ui-element">Score: 0</div>
48
+ <div id="game-controls" class="ui-element">
49
+ <button id="start-btn" class="bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg transition-colors">
50
+ Start Game
51
+ </button>
52
+ <button id="pause-btn" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-3 px-6 rounded-lg transition-colors">
53
+ Pause
54
+ </button>
55
+ <button id="reset-btn" class="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg transition-colors">
56
+ Reset
57
+ </button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <script>
63
+ // Three.js 3D Snake Game
64
+ let scene, camera, renderer;
65
+ let snake = [];
66
+ let food;
67
+ let direction = { x: 1, y: 0, z: 0 };
68
+ let gameSpeed = 150;
69
+ let score = 0;
70
+ let gameRunning = false;
71
+ let gameInterval;
72
+
73
+ // Initialize Three.js
74
+ function init() {
75
+ // Scene
76
+ scene = new THREE.Scene();
77
+ scene.background = new THREE.Color(0x1a202c);
78
+
79
+ // Camera
80
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
81
+ camera.position.set(15, 15, 15);
82
+ camera.lookAt(0, 0, 0);
83
+
84
+ // Renderer
85
+ renderer = new THREE.WebGLRenderer({ antialias: true });
86
+ renderer.setSize(window.innerWidth, window.innerHeight);
87
+ document.getElementById('game-container').appendChild(renderer.domElement);
88
+
89
+ // Lighting
90
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
91
+ scene.add(ambientLight);
92
+
93
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
94
+ directionalLight.position.set(10, 20, 15);
95
+ scene.add(directionalLight);
96
+
97
+ // Grid helper
98
+ const gridHelper = new THREE.GridHelper(30, 30);
99
+ scene.add(gridHelper);
100
+
101
+ // Create initial snake
102
+ createSnake();
103
+ createFood();
104
+
105
+ // Event listeners
106
+ window.addEventListener('resize', onWindowResize);
107
+ document.addEventListener('keydown', handleKeyPress);
108
+
109
+ // UI event listeners
110
+ document.getElementById('start-btn').addEventListener('click', startGame);
111
+ document.getElementById('pause-btn').addEventListener('click', pauseGame);
112
+ document.getElementById('reset-btn').addEventListener('click', resetGame);
113
+
114
+ animate();
115
+ }
116
+
117
+ function createSnake() {
118
+ // Clear existing snake
119
+ snake.forEach(segment => scene.remove(segment));
120
+ snake = [];
121
+
122
+ // Create initial snake segments
123
+ for (let i = 0; i < 3; i++) {
124
+ const geometry = new THREE.BoxGeometry(1, 1, 1);
125
+ const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
126
+ const segment = new THREE.Mesh(geometry, material);
127
+ segment.position.set(-i, 0, 0);
128
+ scene.add(segment);
129
+ snake.push(segment);
130
+ }
131
+ }
132
+
133
+ function createFood() {
134
+ if (food) scene.remove(food);
135
+
136
+ const geometry = new THREE.SphereGeometry(0.5, 16, 16);
137
+ const material = new THREE.MeshPhongMaterial({ color: 0xff0000 });
138
+ food = new THREE.Mesh(geometry, material);
139
+
140
+ // Random position within bounds (-14 to 14)
141
+ food.position.set(
142
+ Math.floor(Math.random() * 29) - 14,
143
+ 0,
144
+ Math.floor(Math.random() * 29) - 14
145
+ );
146
+ scene.add(food);
147
+ }
148
+
149
+ function moveSnake() {
150
+ if (!gameRunning) return;
151
+
152
+ // Calculate new head position
153
+ const head = snake[0];
154
+ const newHead = head.clone();
155
+ newHead.position.x += direction.x;
156
+ newHead.position.z += direction.z;
157
+
158
+ // Check boundaries
159
+ if (newHead.position.x > 14 || newHead.position.x < -14 ||
160
+ newHead.position.z > 14 || newHead.position.z < -14) {
161
+ gameOver();
162
+ return;
163
+ }
164
+
165
+ // Check self collision
166
+ for (let i = 0; i < snake.length; i++) {
167
+ if (newHead.position.distanceTo(snake[i].position) < 0.5) {
168
+ gameOver();
169
+ return;
170
+ }
171
+ }
172
+
173
+ // Check food collision
174
+ if (newHead.position.distanceTo(food.position) < 1) {
175
+ score += 10;
176
+ document.getElementById('score-display').textContent = `Score: ${score}`;
177
+
178
+ // Increase speed slightly
179
+ if (score % 50 === 0 && gameSpeed > 50) {
180
+ gameSpeed -= 10;
181
+ clearInterval(gameInterval);
182
+ gameInterval = setInterval(moveSnake, gameSpeed);
183
+ }
184
+
185
+ createFood();
186
+ } else {
187
+ // Remove tail if no food eaten
188
+ const tail = snake.pop();
189
+ scene.remove(tail);
190
+ }
191
+
192
+ // Add new head
193
+ scene.add(newHead);
194
+ snake.unshift(newHead);
195
+ }
196
+
197
+ function handleKeyPress(event) {
198
+ if (!gameRunning) return;
199
+
200
+ switch(event.key) {
201
+ case 'ArrowUp':
202
+ if (direction.z !== 1) direction = { x: 0, y: 0, z: -1 };
203
+ break;
204
+ case 'ArrowDown':
205
+ if (direction.z !== -1) direction = { x: 0, y: 0, z: 1 };
206
+ break;
207
+ case 'ArrowLeft':
208
+ if (direction.x !== 1) direction = { x: -1, y: 0, z: 0 };
209
+ break;
210
+ case 'ArrowRight':
211
+ if (direction.x !== -1) direction = { x: 1, y: 0, z: 0 };
212
+ break;
213
+ case 'w':
214
+ case 'W':
215
+ if (direction.z !== 1) direction = { x: 0, y: 0, z: -1 };
216
+ break;
217
+ case 's':
218
+ case 'S':
219
+ if (direction.z !== -1) direction = { x: 0, y: 0, z: 1 };
220
+ break;
221
+ case 'a':
222
+ case 'A':
223
+ if (direction.x !== 1) direction = { x: -1, y: 0, z: 0 };
224
+ break;
225
+ case 'd':
226
+ case 'D':
227
+ if (direction.x !== -1) direction = { x: 1, y: 0, z: 0 };
228
+ break;
229
+ }
230
+ }
231
+
232
+ function startGame() {
233
+ if (!gameRunning) {
234
+ gameRunning = true;
235
+ gameInterval = setInterval(moveSnake, gameSpeed);
236
+ }
237
+ }
238
+
239
+ function pauseGame() {
240
+ gameRunning = !gameRunning;
241
+ if (gameRunning) {
242
+ gameInterval = setInterval(moveSnake, gameSpeed);
243
+ } else {
244
+ clearInterval(gameInterval);
245
+ }
246
+ }
247
+
248
+ function resetGame() {
249
+ clearInterval(gameInterval);
250
+ gameRunning = false;
251
+ score = 0;
252
+ document.getElementById('score-display').textContent = `Score: ${score}`;
253
+ direction = { x: 1, y: 0, z: 0 };
254
+ createSnake();
255
+ createFood();
256
+ }
257
+
258
+ function gameOver() {
259
+ gameRunning = false;
260
+ clearInterval(gameInterval);
261
+ alert(`Game Over! Your score: ${score}\nPress OK to play again!`);
262
+ resetGame();
263
+ }
264
+
265
+ function onWindowResize() {
266
+ camera.aspect = window.innerWidth / window.innerHeight;
267
+ camera.updateProjectionMatrix();
268
+ renderer.setSize(window.innerWidth, window.innerHeight);
269
+ }
270
+
271
+ function animate() {
272
+ requestAnimationFrame(animate);
273
+
274
+ // Rotate camera slowly for better 3D effect
275
+ if (gameRunning) {
276
+ camera.position.x = 15 * Math.cos(Date.now() * 0.0001);
277
+ camera.position.z = 15 * Math.sin(Date.now() * 0.0001);
278
+ camera.lookAt(0, 0, 0);
279
+ }
280
+
281
+ renderer.render(scene, camera);
282
+ }
283
+
284
+ // Initialize the game
285
+ init();
286
+ feather.replace();
287
+ </script>
288
+ </body>
289
+ </html>