Skip to content

Instantly share code, notes, and snippets.

@pmarreck
Created July 30, 2025 00:54
Show Gist options
  • Save pmarreck/db782fdb68053292ca746d6c7569e2ac to your computer and use it in GitHub Desktop.
Save pmarreck/db782fdb68053292ca746d6c7569e2ac to your computer and use it in GitHub Desktop.
The very first output of the prompt "Write a self-contained HTML, CSS and Javascript page implementing Asteroids" sent to the new model GLM-4.5-Air-q5-hi-mlx running on an M4 128GB. Runs great!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Asteroids Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #000;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
.game-container {
position: relative;
border: 2px solid #0ff;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
#gameCanvas {
background: radial-gradient(ellipse at center, #001122 0%, #000000 100%);
display: block;
}
.ui-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 20px;
display: flex;
justify-content: space-between;
pointer-events: none;
}
.score, .lives {
font-size: 20px;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.8);
}
.game-over-screen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
display: none;
background: rgba(0, 0, 0, 0.9);
padding: 40px;
border-radius: 10px;
border: 2px solid #0ff;
box-shadow: 0 0 30px rgba(0, 255, 255, 0.5);
}
.game-over-screen h2 {
font-size: 48px;
margin-bottom: 20px;
color: #0ff;
text-shadow: 0 0 20px rgba(0, 255, 255, 0.8);
}
.game-over-screen p {
font-size: 24px;
margin-bottom: 30px;
}
.restart-btn {
background: linear-gradient(45deg, #00ffff, #0088ff);
border: none;
color: #000;
padding: 15px 40px;
font-size: 20px;
font-weight: bold;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.restart-btn:hover {
transform: scale(1.1);
box-shadow: 0 0 20px rgba(0, 255, 255, 0.8);
}
.controls {
position: absolute;
bottom: -60px;
left: 50%;
transform: translateX(-50%);
text-align: center;
font-size: 14px;
color: #666;
}
.controls span {
margin: 0 10px;
}
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="ui-overlay">
<div class="score">Score: <span id="scoreValue">0</span></div>
<div class="lives">Lives: <span id="livesValue">3</span></div>
</div>
<div class="game-over-screen" id="gameOverScreen">
<h2>GAME OVER</h2>
<p>Final Score: <span id="finalScore">0</span></p>
<button class="restart-btn" onclick="game.restart()">PLAY AGAIN</button>
</div>
<div class="controls">
<span>↑ - Thrust</span>
<span>← → - Rotate</span>
<span>SPACE - Shoot</span>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game constants
const SHIP_SIZE = 15;
const BULLET_SPEED = 10;
const ASTEROID_MIN_SIZE = 20;
const ASTEROID_MAX_SIZE = 50;
const MAX_SPEED = 7;
// Particle system
class Particle {
constructor(x, y, vx, vy, life = 30) {
this.x = x;
this.y = y;
this.vx = vx + (Math.random() - 0.5) * 2;
this.vy = vy + (Math.random() - 0.5) * 2;
this.life = life;
this.maxLife = life;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
}
draw() {
const alpha = this.life / this.maxLife;
ctx.save();
ctx.globalAlpha = alpha;
ctx.fillStyle = '#ffaa00';
ctx.fillRect(this.x, this.y, 2, 2);
ctx.restore();
}
}
// Ship class
class Ship {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.angle = -Math.PI / 2; // Start pointing up
this.radius = SHIP_SIZE;
}
rotate(direction) {
const rotationSpeed = 0.1;
this.angle += direction * rotationSpeed;
}
thrust() {
const acceleration = 0.2;
this.vx += Math.cos(this.angle) * acceleration;
this.vy += Math.sin(this.angle) * acceleration;
// Add thrust particles
for (let i = 0; i < 3; i++) {
const spread = Math.random() * 0.5;
particles.push(new Particle(
this.x - Math.cos(this.angle) * SHIP_SIZE,
this.y - Math.sin(this.angle) * SHIP_SIZE,
-this.vx / 2 - Math.cos(this.angle + spread) * 3,
-this.vy / 2 - Math.sin(this.angle + spread) * 3,
20
));
}
}
update() {
// Apply friction
this.vx *= 0.99;
this.vy *= 0.99;
// Limit speed
const speed = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
if (speed > MAX_SPEED) {
this.vx = (this.vx / speed) * MAX_SPEED;
this.vy = (this.vy / speed) * MAX_SPEED;
}
// Update position
this.x += this.vx;
this.y += this.vy;
// Wrap around screen
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
// Draw ship
ctx.strokeStyle = '#0ff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(SHIP_SIZE, 0);
ctx.lineTo(-SHIP_SIZE * 0.7, -SHIP_SIZE * 0.6);
ctx.lineTo(-SHIP_SIZE * 0.5, 0);
ctx.lineTo(-SHIP_SIZE * 0.7, SHIP_SIZE * 0.6);
ctx.closePath();
ctx.stroke();
// Draw glow effect
ctx.shadowBlur = 10;
ctx.shadowColor = '#0ff';
ctx.stroke();
ctx.restore();
}
getVertices() {
const cos = Math.cos(this.angle);
const sin = Math.sin(this.angle);
return [
{ x: this.x + SHIP_SIZE * cos, y: this.y + SHIP_SIZE * sin },
{ x: this.x - SHIP_SIZE * 0.7 * cos - SHIP_SIZE * 0.6 * sin,
y: this.y - SHIP_SIZE * 0.7 * sin + SHIP_SIZE * 0.6 * cos },
{ x: this.x - SHIP_SIZE * 0.5 * cos, y: this.y - SHIP_SIZE * 0.5 * sin },
{ x: this.x - SHIP_SIZE * 0.7 * cos + SHIP_SIZE * 0.6 * sin,
y: this.y - SHIP_SIZE * 0.7 * sin - SHIP_SIZE * 0.6 * cos }
];
}
}
// Asteroid class
class Asteroid {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size || ASTEROID_MAX_SIZE;
this.radius = this.size;
// Random velocity
const angle = Math.random() * Math.PI * 2;
const speed = (ASTEROID_MAX_SIZE - this.size) / ASTEROID_MAX_SIZE * 2 + 0.5;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
// Random rotation
this.angle = 0;
this.rotationSpeed = (Math.random() - 0.5) * 0.05;
// Generate vertices
this.vertices = [];
const numVertices = Math.floor(Math.random() * 4) + 8;
for (let i = 0; i < numVertices; i++) {
const angle = (i / numVertices) * Math.PI * 2;
const variance = 0.9 + Math.random() * 0.2;
this.vertices.push({
x: Math.cos(angle) * this.size * variance,
y: Math.sin(angle) * this.size * variance
});
}
}
update() {
this.x += this.vx;
this.y += this.vy;
this.angle += this.rotationSpeed;
// Wrap around screen
if (this.x < -this.size) this.x = canvas.width + this.size;
if (this.x > canvas.width + this.size) this.x = -this.size;
if (this.y < -this.size) this.y = canvas.height + this.size;
if (this.y > canvas.height + this.size) this.y = -this.size;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
// Draw asteroid
ctx.strokeStyle = '#888';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < this.vertices.length; i++) {
const vertex = this.vertices[i];
if (i === 0) {
ctx.moveTo(vertex.x, vertex.y);
} else {
ctx.lineTo(vertex.x, vertex.y);
}
}
ctx.closePath();
ctx.stroke();
// Draw glow
ctx.shadowBlur = 5;
ctx.shadowColor = '#888';
ctx.stroke();
ctx.restore();
}
split() {
if (this.size > ASTEROID_MIN_SIZE) {
const newSize = this.size / 2;
return [
new Asteroid(this.x, this.y, newSize),
new Asteroid(this.x, this.y, newSize)
];
}
return [];
}
getTransformedVertices() {
const cos = Math.cos(this.angle);
const sin = Math.sin(this.angle);
return this.vertices.map(vertex => ({
x: this.x + vertex.x * cos - vertex.y * sin,
y: this.y + vertex.x * sin + vertex.y * cos
}));
}
}
// Bullet class
class Bullet {
constructor(x, y, angle) {
this.x = x;
this.y = y;
const speed = BULLET_SPEED;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 40; // Bullets last for 40 frames
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
// Wrap around screen
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
}
draw() {
ctx.save();
ctx.fillStyle = '#ff0';
ctx.shadowBlur = 5;
ctx.shadowColor = '#ff0';
// Draw bullet as a small square
ctx.fillRect(this.x - 2, this.y - 2, 4, 4);
ctx.restore();
}
}
// Game class
class AsteroidsGame {
constructor() {
this.ship = new Ship(canvas.width / 2, canvas.height / 2);
this.bullets = [];
this.asteroids = [];
this.particles = [];
this.score = 0;
this.lives = 3;
this.gameOver = false;
this.keys = {};
// Initialize asteroids
this.spawnAsteroids(5);
// Set up event listeners
document.addEventListener('keydown', (e) => this.keys[e.key] = true);
document.addEventListener('keyup', (e) => this.keys[e.key] = false);
// Game loop
this.lastShotTime = 0;
this.gameLoop();
}
spawnAsteroids(count) {
for (let i = 0; i < count; i++) {
let x, y;
// Ensure asteroids don't spawn too close to the ship
do {
x = Math.random() * canvas.width;
y = Math.random() * canvas.height;
} while (Math.sqrt((x - this.ship.x) ** 2 + (y - this.ship.y) ** 2) < 100);
const size = Math.random() * (ASTEROID_MAX_SIZE - ASTEROID_MIN_SIZE) + ASTEROID_MIN_SIZE;
this.asteroids.push(new Asteroid(x, y, size));
}
}
handleInput() {
if (this.gameOver) return;
// Rotation
if (this.keys['ArrowLeft']) {
this.ship.rotate(-1);
}
if (this.keys['ArrowRight']) {
this.ship.rotate(1);
}
// Thrust
if (this.keys['ArrowUp']) {
this.ship.thrust();
}
// Shooting
const now = Date.now();
if (this.keys[' '] && now - this.lastShotTime > 150) {
const bullet = new Bullet(
this.ship.x + Math.cos(this.ship.angle) * SHIP_SIZE,
this.ship.y + Math.sin(this.ship.angle) * SHIP_SIZE,
this.ship.angle
);
this.bullets.push(bullet);
this.lastShotTime = now;
}
}
checkCollisions() {
// Bullet-asteroid collisions
for (let i = this.bullets.length - 1; i >= 0; i--) {
const bullet = this.bullets[i];
for (let j = this.asteroids.length - 1; j >= 0; j--) {
const asteroid = this.asteroids[j];
// Simple circular collision
const dx = bullet.x - asteroid.x;
const dy = bullet.y - asteroid.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < asteroid.radius) {
// Remove bullet
this.bullets.splice(i, 1);
// Add explosion particles
for (let k = 0; k < 10; k++) {
const speed = Math.random() * 3 + 1;
particles.push(new Particle(
asteroid.x,
asteroid.y,
Math.random() * speed - speed / 2,
Math.random() * speed - speed / 2,
30
));
}
// Split asteroid or remove it
const newAsteroids = asteroid.split();
this.asteroids.splice(j, 1);
this.asteroids.push(...newAsteroids);
// Update score
let points = 20;
if (asteroid.size > ASTEROID_MIN_SIZE * 1.5) {
points = 10;
} else if (asteroid.size > ASTEROID_MIN_SIZE) {
points = 50;
}
this.score += points;
// Update UI
document.getElementById('scoreValue').textContent = this.score;
break;
}
}
}
// Ship-asteroid collisions
const shipX = this.ship.x;
const shipY = this.ship.y;
for (let i = 0; i < this.asteroids.length; i++) {
const asteroid = this.asteroids[i];
const dx = shipX - asteroid.x;
const dy = shipY - asteroid.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < asteroid.radius + this.ship.radius) {
// Ship crashed
this.lives--;
// Add large explosion particles
for (let k = 0; k < 20; k++) {
const speed = Math.random() * 5 + 2;
particles.push(new Particle(
shipX,
shipY,
Math.random() * speed - speed / 2,
Math.random() * speed - speed / 2,
40
));
}
// Update UI
document.getElementById('livesValue').textContent = this.lives;
if (this.lives <= 0) {
this.endGame();
} else {
// Reset ship position
this.ship.x = canvas.width / 2;
this.ship.y = canvas.height / 2;
this.ship.vx = 0;
this.ship.vy = 0;
}
break;
}
}
}
update() {
// Update game objects
this.ship.update();
this.bullets = this.bullets.filter(bullet => {
bullet.update();
return bullet.life > 0;
});
this.asteroids.forEach(asteroid => asteroid.update());
particles = particles.filter(particle => {
particle.update();
return particle.life > 0;
});
// Spawn new asteroids if needed
if (this.asteroids.length === 0) {
this.spawnAsteroids(5 + Math.floor(this.score / 500));
}
}
draw() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw stars background
ctx.fillStyle = '#fff';
for (let i = 0; i < 100; i++) {
const x = (i * 73) % canvas.width;
const y = (i * 37) % canvas.height;
const size = Math.sin(i) > 0.5 ? 1 : 2;
ctx.fillRect(x, y, size, size);
}
// Draw game objects
this.ship.draw();
this.bullets.forEach(bullet => bullet.draw());
this.asteroids.forEach(asteroid => asteroid.draw());
particles.forEach(particle => particle.draw());
}
endGame() {
this.gameOver = true;
document.getElementById('finalScore').textContent = this.score;
document.getElementById('gameOverScreen').style.display = 'block';
}
restart() {
this.ship = new Ship(canvas.width / 2, canvas.height / 2);
this.bullets = [];
this.particles = particles.filter(p => p.life > 0);
this.asteroids = [];
this.score = 0;
this.lives = 3;
this.gameOver = false;
document.getElementById('scoreValue').textContent = '0';
document.getElementById('livesValue').textContent = '3';
document.getElementById('gameOverScreen').style.display = 'none';
this.spawnAsteroids(5);
}
gameLoop() {
this.handleInput();
if (!this.gameOver) {
this.update();
this.checkCollisions();
}
this.draw();
requestAnimationFrame(() => this.gameLoop());
}
}
// Initialize game
let particles = [];
const game = new AsteroidsGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment