Update app.py
Browse files
app.py
CHANGED
@@ -1,153 +1,272 @@
|
|
1 |
import streamlit as st
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
}
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
}
|
52 |
}
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
rotateY(frameCount * 0.005);
|
63 |
-
rotateX(frameCount * 0.003);
|
64 |
-
|
65 |
-
// Update and draw our snake in 3D
|
66 |
-
updateSnake();
|
67 |
-
drawSnake();
|
68 |
-
|
69 |
-
// Draw the Matrix rain overlay on top (in 2D)
|
70 |
-
drawMatrixRain();
|
71 |
-
}
|
72 |
|
73 |
-
//
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
let z = map(noise(t, 200), 0, 1, -300, 300);
|
80 |
-
let newHead = createVector(x, y, z);
|
81 |
-
snake.push(newHead);
|
82 |
-
// Maintain a fixed length by removing the tail segment
|
83 |
-
if (snake.length > maxSnakeLength) {
|
84 |
-
snake.shift();
|
85 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
}
|
87 |
|
88 |
-
// Draw
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
} else {
|
101 |
-
fill(0, 255, 70);
|
102 |
-
textSize(fontSize);
|
103 |
-
let matrixChars = "01ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
104 |
-
let char = matrixChars.charAt(floor(random(matrixChars.length)));
|
105 |
-
text(char, 0, 0);
|
106 |
-
}
|
107 |
-
pop();
|
108 |
}
|
|
|
109 |
}
|
110 |
|
111 |
-
// Draw
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
|
|
|
|
|
|
|
|
131 |
}
|
132 |
-
pop();
|
133 |
}
|
134 |
|
135 |
-
//
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
}
|
|
|
143 |
}
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
-
|
153 |
-
create_app()
|
|
|
1 |
import streamlit as st
|
2 |
|
3 |
+
# Set wide layout
|
4 |
+
st.set_page_config(layout="wide", page_title="Galaxian Snake Game", initial_sidebar_state="expanded")
|
5 |
+
|
6 |
+
# Sidebar instructions
|
7 |
+
st.sidebar.markdown("## Galaxian Snake Game Controls")
|
8 |
+
st.sidebar.markdown(
|
9 |
+
"""
|
10 |
+
- **Controls:**
|
11 |
+
- **Q:** Move Up–Left
|
12 |
+
- **W:** Move Up
|
13 |
+
- **E:** Move Up–Right
|
14 |
+
- **A:** Move Left
|
15 |
+
- **S:** (Center) Continue current direction
|
16 |
+
- **D:** Move Right
|
17 |
+
- **Z:** Move Down–Left
|
18 |
+
- **X:** Move Down
|
19 |
+
- **C:** Move Down–Right
|
20 |
+
|
21 |
+
- **Rules:**
|
22 |
+
- The snake moves on a grid and grows when it eats the alien food (👾).
|
23 |
+
- You must avoid colliding with the walls or yourself.
|
24 |
+
- Press **R** to restart after a game over.
|
25 |
|
26 |
+
Have fun and enjoy the retro Galaxian vibe!
|
27 |
+
"""
|
28 |
+
)
|
29 |
+
|
30 |
+
# p5.js snake game embedded as an HTML string
|
31 |
+
html_code = r"""
|
32 |
+
<!DOCTYPE html>
|
33 |
+
<html>
|
34 |
+
<head>
|
35 |
+
<meta charset="UTF-8">
|
36 |
+
<title>Galaxian Snake Game</title>
|
37 |
+
<!-- Include p5.js (with WEBGL disabled because we use 2D grid) -->
|
38 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script>
|
39 |
+
<style>
|
40 |
+
body { margin: 0; padding: 0; overflow: hidden; background: black; }
|
41 |
+
canvas { display: block; margin: 0 auto; }
|
42 |
+
</style>
|
43 |
+
</head>
|
44 |
+
<body>
|
45 |
+
<script>
|
46 |
+
/*
|
47 |
+
Galaxian Snake Game
|
48 |
+
- The snake moves on a grid.
|
49 |
+
- Controls: Q, W, E, A, S, D, Z, X, C (with S meaning “no turn”).
|
50 |
+
- Food is represented as a retro alien (👾).
|
51 |
+
- A starfield background evokes classic Galaxian style.
|
52 |
+
*/
|
53 |
+
|
54 |
+
// Game configuration
|
55 |
+
let tileSize = 20;
|
56 |
+
let cols, rows;
|
57 |
+
let snake;
|
58 |
+
let direction; // {x: dx, y: dy} in grid units
|
59 |
+
let food;
|
60 |
+
let score;
|
61 |
+
let gameOver;
|
62 |
+
let moveCounter = 0;
|
63 |
+
let moveDelay = 8; // lower => faster snake
|
64 |
+
|
65 |
+
// Starfield for background
|
66 |
+
let stars = [];
|
67 |
+
const numStars = 150;
|
68 |
+
|
69 |
+
function setup() {
|
70 |
+
createCanvas(windowWidth, windowHeight);
|
71 |
+
frameRate(30);
|
72 |
+
cols = floor(width / tileSize);
|
73 |
+
rows = floor(height / tileSize);
|
74 |
+
resetGame();
|
75 |
+
// Create starfield
|
76 |
+
for (let i = 0; i < numStars; i++) {
|
77 |
+
stars.push({
|
78 |
+
x: random(width),
|
79 |
+
y: random(height),
|
80 |
+
speed: random(0.5, 2),
|
81 |
+
size: random(1, 3)
|
82 |
+
});
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
function resetGame() {
|
87 |
+
snake = [];
|
88 |
+
// Start snake in the middle with length 3
|
89 |
+
let startX = floor(cols / 2);
|
90 |
+
let startY = floor(rows / 2);
|
91 |
+
snake.push({x: startX, y: startY});
|
92 |
+
snake.push({x: startX - 1, y: startY});
|
93 |
+
snake.push({x: startX - 2, y: startY});
|
94 |
+
// Initial direction: moving right
|
95 |
+
direction = {x: 1, y: 0};
|
96 |
+
placeFood();
|
97 |
+
score = 0;
|
98 |
+
gameOver = false;
|
99 |
+
moveCounter = 0;
|
100 |
+
}
|
101 |
+
|
102 |
+
// Place food (alien) at a random location not occupied by the snake
|
103 |
+
function placeFood() {
|
104 |
+
let valid = false;
|
105 |
+
let newFood;
|
106 |
+
while (!valid) {
|
107 |
+
newFood = {
|
108 |
+
x: floor(random(cols)),
|
109 |
+
y: floor(random(rows))
|
110 |
+
};
|
111 |
+
valid = true;
|
112 |
+
for (let seg of snake) {
|
113 |
+
if (seg.x === newFood.x && seg.y === newFood.y) {
|
114 |
+
valid = false;
|
115 |
+
break;
|
116 |
+
}
|
117 |
}
|
118 |
+
}
|
119 |
+
food = newFood;
|
120 |
+
}
|
121 |
+
|
122 |
+
function draw() {
|
123 |
+
// Draw Galaxian–style starfield background
|
124 |
+
background(0);
|
125 |
+
noStroke();
|
126 |
+
fill(255);
|
127 |
+
for (let s of stars) {
|
128 |
+
ellipse(s.x, s.y, s.size);
|
129 |
+
s.y += s.speed;
|
130 |
+
if (s.y > height) {
|
131 |
+
s.y = 0;
|
132 |
+
s.x = random(width);
|
133 |
}
|
134 |
}
|
135 |
|
136 |
+
// Draw grid boundaries (optional: for visual style)
|
137 |
+
// stroke(40);
|
138 |
+
// for (let i = 0; i < cols; i++) {
|
139 |
+
// line(i * tileSize, 0, i * tileSize, height);
|
140 |
+
// }
|
141 |
+
// for (let j = 0; j < rows; j++) {
|
142 |
+
// line(0, j * tileSize, width, j * tileSize);
|
143 |
+
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
|
145 |
+
// Game update if not over
|
146 |
+
if (!gameOver) {
|
147 |
+
moveCounter++;
|
148 |
+
if (moveCounter >= moveDelay) {
|
149 |
+
updateSnake();
|
150 |
+
moveCounter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
}
|
152 |
+
} else {
|
153 |
+
// Display Game Over message
|
154 |
+
textAlign(CENTER, CENTER);
|
155 |
+
textSize(64);
|
156 |
+
fill(255, 50, 50);
|
157 |
+
text("GAME OVER", width/2, height/2);
|
158 |
+
textSize(32);
|
159 |
+
text("Score: " + score + " (Press R to Restart)", width/2, height/2 + 50);
|
160 |
}
|
161 |
|
162 |
+
// Draw food as a retro alien emoji
|
163 |
+
textSize(tileSize);
|
164 |
+
textAlign(CENTER, CENTER);
|
165 |
+
text("👾", food.x * tileSize + tileSize/2, food.y * tileSize + tileSize/2);
|
166 |
+
|
167 |
+
// Draw snake as neon blocks
|
168 |
+
for (let i = 0; i < snake.length; i++) {
|
169 |
+
let seg = snake[i];
|
170 |
+
if (i == 0) {
|
171 |
+
fill(0, 255, 0); // head bright green
|
172 |
+
} else {
|
173 |
+
fill(0, 180, 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
}
|
175 |
+
rect(seg.x * tileSize, seg.y * tileSize, tileSize, tileSize);
|
176 |
}
|
177 |
|
178 |
+
// Draw score in top-left corner
|
179 |
+
fill(255);
|
180 |
+
textSize(16);
|
181 |
+
textAlign(LEFT, TOP);
|
182 |
+
text("Score: " + score, 10, 10);
|
183 |
+
}
|
184 |
+
|
185 |
+
// Update snake position and check collisions
|
186 |
+
function updateSnake() {
|
187 |
+
// Determine new head position
|
188 |
+
let head = snake[0];
|
189 |
+
let newHead = { x: head.x + direction.x, y: head.y + direction.y };
|
190 |
+
|
191 |
+
// Check wall collisions
|
192 |
+
if (newHead.x < 0 || newHead.x >= cols || newHead.y < 0 || newHead.y >= rows) {
|
193 |
+
gameOver = true;
|
194 |
+
return;
|
195 |
+
}
|
196 |
+
|
197 |
+
// Check self-collision
|
198 |
+
for (let seg of snake) {
|
199 |
+
if (seg.x === newHead.x && seg.y === newHead.y) {
|
200 |
+
gameOver = true;
|
201 |
+
return;
|
202 |
}
|
|
|
203 |
}
|
204 |
|
205 |
+
// Add new head
|
206 |
+
snake.unshift(newHead);
|
207 |
+
|
208 |
+
// Check food collision
|
209 |
+
if (newHead.x === food.x && newHead.y === food.y) {
|
210 |
+
score += 10;
|
211 |
+
placeFood();
|
212 |
+
// (Do not remove tail: snake grows)
|
213 |
+
} else {
|
214 |
+
// Remove tail (snake moves forward)
|
215 |
+
snake.pop();
|
216 |
+
}
|
217 |
+
}
|
218 |
+
|
219 |
+
// Map key presses to direction changes using the 3x3 layout:
|
220 |
+
// Q: (-1,-1), W: (0,-1), E: (1,-1)
|
221 |
+
// A: (-1, 0), S: (0,0) [no change], D: (1,0)
|
222 |
+
// Z: (-1, 1), X: (0,1), C: (1,1)
|
223 |
+
function keyPressed() {
|
224 |
+
if (gameOver && key.toLowerCase() === 'r') {
|
225 |
+
resetGame();
|
226 |
+
return;
|
227 |
+
}
|
228 |
+
|
229 |
+
// Only process if game is not over
|
230 |
+
if (gameOver) return;
|
231 |
+
|
232 |
+
let newDir = null;
|
233 |
+
let k = key.toLowerCase();
|
234 |
+
if (k === 'q') newDir = {x: -1, y: -1};
|
235 |
+
else if (k === 'w') newDir = {x: 0, y: -1};
|
236 |
+
else if (k === 'e') newDir = {x: 1, y: -1};
|
237 |
+
else if (k === 'a') newDir = {x: -1, y: 0};
|
238 |
+
else if (k === 's') newDir = {x: 0, y: 0}; // center: no change
|
239 |
+
else if (k === 'd') newDir = {x: 1, y: 0};
|
240 |
+
else if (k === 'z') newDir = {x: -1, y: 1};
|
241 |
+
else if (k === 'x') newDir = {x: 0, y: 1};
|
242 |
+
else if (k === 'c') newDir = {x: 1, y: 1};
|
243 |
+
|
244 |
+
// If a valid key was pressed and newDir is not the "no-turn" (0,0) command,
|
245 |
+
// update direction. Also disallow reversing (only if snake length > 1).
|
246 |
+
if (newDir) {
|
247 |
+
if (newDir.x === 0 && newDir.y === 0) {
|
248 |
+
// "S" was pressed: no turn, so do nothing.
|
249 |
+
return;
|
250 |
+
}
|
251 |
+
if (snake.length > 1) {
|
252 |
+
// Prevent reversing (i.e., newDir should not be exactly opposite of current)
|
253 |
+
if (newDir.x === -direction.x && newDir.y === -direction.y) {
|
254 |
+
return;
|
255 |
+
}
|
256 |
}
|
257 |
+
direction = newDir;
|
258 |
}
|
259 |
+
}
|
260 |
+
|
261 |
+
// Resize canvas on window resize
|
262 |
+
function windowResized() {
|
263 |
+
resizeCanvas(windowWidth, windowHeight);
|
264 |
+
cols = floor(width / tileSize);
|
265 |
+
rows = floor(height / tileSize);
|
266 |
+
}
|
267 |
+
</script>
|
268 |
+
</body>
|
269 |
+
</html>
|
270 |
+
"""
|
271 |
|
272 |
+
st.components.v1.html(html_code, height=700, scrolling=False)
|
|