Skip to content

Instantly share code, notes, and snippets.

@tluyben
Created July 13, 2025 08:57
Show Gist options
  • Save tluyben/3b805957c85873530cac4fd0cd39a371 to your computer and use it in GitHub Desktop.
Save tluyben/3b805957c85873530cac4fd0cd39a371 to your computer and use it in GitHub Desktop.
shmup deepseek v3
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nemesis Shmup</title>
<style>
body {
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
touch-action: none;
font-family: Arial, sans-serif;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
#game-canvas {
display: block;
background: linear-gradient(to bottom, #000033, #000066);
}
#start-screen, #game-over-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.7);
color: white;
z-index: 10;
}
#game-over-screen {
display: none;
}
h1 {
font-size: 3rem;
color: #ff0;
text-shadow: 0 0 10px #ff0;
margin-bottom: 2rem;
}
button {
background: #f00;
color: white;
border: none;
padding: 1rem 2rem;
font-size: 1.5rem;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
button:hover {
background: #ff0;
color: #000;
transform: scale(1.1);
}
#score-display {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 1.5rem;
z-index: 5;
}
#health-display {
position: absolute;
top: 10px;
right: 10px;
color: #0f0;
font-size: 1.5rem;
z-index: 5;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="score-display">Score: 0</div>
<div id="health-display">Health: 100%</div>
<div id="start-screen">
<h1>NEMESIS</h1>
<button id="start-button">START GAME</button>
</div>
<div id="game-over-screen">
<h1>GAME OVER</h1>
<div id="final-score" style="font-size: 2rem; margin-bottom: 2rem;"></div>
<button id="restart-button">PLAY AGAIN</button>
</div>
</div>
<script>
// Game variables
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const startScreen = document.getElementById('start-screen');
const gameOverScreen = document.getElementById('game-over-screen');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
const scoreDisplay = document.getElementById('score-display');
const healthDisplay = document.getElementById('health-display');
const finalScoreDisplay = document.getElementById('final-score');
// Set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Game state
let gameRunning = false;
let score = 0;
let playerHealth = 100;
// Player
const player = {
x: canvas.width / 4,
y: canvas.height / 2,
width: 40,
height: 40,
speed: 6,
bullets: [],
lastShot: 0,
shootDelay: 300,
direction: 0 // 0 = right, 1 = left
};
// Enemies
const enemies = [];
const enemyTypes = [
{ width: 30, height: 30, color: '#f00', health: 1, score: 10, speed: 2 },
{ width: 50, height: 30, color: '#ff0', health: 2, score: 20, speed: 1.5 },
{ width: 40, height: 40, color: '#f0f', health: 3, score: 30, speed: 1 }
];
// Explosions
const explosions = [];
// Background stars
const stars = [];
for (let i = 0; i < 100; i++) {
stars.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 1,
speed: Math.random() * 2 + 1
});
}
// Sprites (using simple shapes for this example)
function drawPlayer() {
ctx.save();
ctx.translate(player.x, player.y);
if (player.direction === 0) {
// Right-facing ship
ctx.fillStyle = '#0af';
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(-20, -20);
ctx.lineTo(-10, 0);
ctx.lineTo(-20, 20);
ctx.closePath();
ctx.fill();
// Engine glow
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.moveTo(-15, -10);
ctx.lineTo(-25, 0);
ctx.lineTo(-15, 10);
ctx.closePath();
ctx.fill();
} else {
// Left-facing ship
ctx.fillStyle = '#0af';
ctx.beginPath();
ctx.moveTo(-20, 0);
ctx.lineTo(20, -20);
ctx.lineTo(10, 0);
ctx.lineTo(20, 20);
ctx.closePath();
ctx.fill();
// Engine glow
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.moveTo(15, -10);
ctx.lineTo(25, 0);
ctx.lineTo(15, 10);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
function drawEnemy(enemy) {
ctx.save();
ctx.translate(enemy.x, enemy.y);
switch(enemy.type) {
case 0: // Small red enemy
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(0, -15);
ctx.lineTo(15, 15);
ctx.lineTo(-15, 15);
ctx.closePath();
ctx.fill();
break;
case 1: // Medium yellow enemy
ctx.fillStyle = '#ff0';
ctx.beginPath();
ctx.moveTo(0, -15);
ctx.lineTo(25, 15);
ctx.lineTo(-25, 15);
ctx.closePath();
ctx.fill();
// Cockpit
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(0, 0, 5, 0, Math.PI * 2);
ctx.fill();
break;
case 2: // Large purple enemy
ctx.fillStyle = '#f0f';
ctx.beginPath();
ctx.moveTo(0, -20);
ctx.lineTo(20, 20);
ctx.lineTo(-20, 20);
ctx.closePath();
ctx.fill();
// Details
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-5, -10);
ctx.lineTo(5, -10);
ctx.moveTo(-8, 0);
ctx.lineTo(8, 0);
ctx.moveTo(-10, 10);
ctx.lineTo(10, 10);
ctx.stroke();
break;
}
ctx.restore();
}
function drawBullet(bullet) {
ctx.save();
ctx.translate(bullet.x, bullet.y);
ctx.fillStyle = bullet.playerBullet ? '#ff0' : '#f00';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, Math.PI * 2);
ctx.fill();
// Add glow
ctx.fillStyle = bullet.playerBullet ? 'rgba(255, 255, 0, 0.3)' : 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
ctx.arc(0, 0, 6, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawExplosion(explosion) {
ctx.save();
ctx.translate(explosion.x, explosion.y);
const radius = explosion.radius * (1 - explosion.progress);
const alpha = 1 - explosion.progress;
ctx.fillStyle = `rgba(255, ${Math.floor(100 + 155 * explosion.progress)}, 0, ${alpha})`;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.fill();
// Add some particles
for (let i = 0; i < 5; i++) {
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * radius * 0.8;
const size = Math.random() * 3 + 1;
ctx.fillStyle = `rgba(255, ${Math.floor(200 + 55 * explosion.progress)}, 0, ${alpha})`;
ctx.beginPath();
ctx.arc(Math.cos(angle) * dist, Math.sin(angle) * dist, size, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
// Game functions
function spawnEnemy() {
const type = Math.floor(Math.random() * enemyTypes.length);
const enemyType = enemyTypes[type];
enemies.push({
x: canvas.width + 50,
y: Math.random() * (canvas.height - 100) + 50,
width: enemyType.width,
height: enemyType.height,
speed: enemyType.speed,
health: enemyType.health,
type: type,
lastShot: 0,
shootDelay: 1500 + Math.random() * 1000
});
}
function shoot(fromPlayer = true) {
const now = Date.now();
if (fromPlayer) {
if (now - player.lastShot > player.shootDelay) {
player.bullets.push({
x: player.x + (player.direction === 0 ? 20 : -20),
y: player.y,
speed: player.direction === 0 ? 10 : -10,
playerBullet: true
});
player.lastShot = now;
playSound('shoot');
}
} else {
// Enemy shooting
for (let enemy of enemies) {
if (now - enemy.lastShot > enemy.shootDelay) {
player.bullets.push({
x: enemy.x - 15,
y: enemy.y,
speed: -7,
playerBullet: false
});
enemy.lastShot = now;
playSound('enemyShoot');
}
}
}
}
function createExplosion(x, y, radius = 30) {
explosions.push({
x: x,
y: y,
radius: radius,
progress: 0,
duration: 500
});
playSound('explosion');
}
// Sound effects
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
const now = audioContext.currentTime;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
switch(type) {
case 'shoot':
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(880, now);
oscillator.frequency.exponentialRampToValueAtTime(440, now + 0.1);
gainNode.gain.setValueAtTime(0.2, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
oscillator.start(now);
oscillator.stop(now + 0.1);
break;
case 'enemyShoot':
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(220, now);
oscillator.frequency.exponentialRampToValueAtTime(110, now + 0.2);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
oscillator.start(now);
oscillator.stop(now + 0.2);
break;
case 'explosion':
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(50, now);
oscillator.frequency.exponentialRampToValueAtTime(20, now + 0.5);
gainNode.gain.setValueAtTime(0.3, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.5);
oscillator.start(now);
oscillator.stop(now + 0.5);
break;
case 'hit':
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(440, now);
oscillator.frequency.exponentialRampToValueAtTime(220, now + 0.1);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
oscillator.start(now);
oscillator.stop(now + 0.1);
break;
}
}
// Input handling
const keys = {
ArrowUp: false,
ArrowDown: false,
ArrowLeft: false,
ArrowRight: false,
Space: false,
z: false
};
document.addEventListener('keydown', (e) => {
if (e.code === 'ArrowUp') keys.ArrowUp = true;
if (e.code === 'ArrowDown') keys.ArrowDown = true;
if (e.code === 'ArrowLeft') keys.ArrowLeft = true;
if (e.code === 'ArrowRight') keys.ArrowRight = true;
if (e.code === 'Space') keys.Space = true;
if (e.key === 'z' || e.key === 'Z') keys.z = true;
});
document.addEventListener('keyup', (e) => {
if (e.code === 'ArrowUp') keys.ArrowUp = false;
if (e.code === 'ArrowDown') keys.ArrowDown = false;
if (e.code === 'ArrowLeft') keys.ArrowLeft = false;
if (e.code === 'ArrowRight') keys.ArrowRight = false;
if (e.code === 'Space') keys.Space = false;
if (e.key === 'z' || e.key === 'Z') keys.z = false;
});
// Touch controls for mobile
let touchStartX = 0;
let touchStartY = 0;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
// Shoot if touching right side of screen
if (touchStartX > canvas.width / 2) {
keys.Space = true;
}
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touchX = e.touches[0].clientX;
const touchY = e.touches[0].clientY;
// Left side controls movement
if (touchStartX < canvas.width / 2) {
const deltaX = touchX - touchStartX;
const deltaY = touchY - touchStartY;
keys.ArrowLeft = deltaX < -10;
keys.ArrowRight = deltaX > 10;
keys.ArrowUp = deltaY < -10;
keys.ArrowDown = deltaY > 10;
}
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
keys.ArrowUp = false;
keys.ArrowDown = false;
keys.ArrowLeft = false;
keys.ArrowRight = false;
keys.Space = false;
}, { passive: false });
// Game loop
function gameLoop() {
if (!gameRunning) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw background
ctx.fillStyle = '#000033';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw stars
ctx.fillStyle = '#fff';
for (let star of stars) {
star.x -= star.speed;
if (star.x < 0) {
star.x = canvas.width;
star.y = Math.random() * canvas.height;
}
ctx.beginPath();
ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
ctx.fill();
}
// Handle player input
if (keys.ArrowUp && player.y > player.height / 2) {
player.y -= player.speed;
}
if (keys.ArrowDown && player.y < canvas.height - player.height / 2) {
player.y += player.speed;
}
if (keys.ArrowLeft) {
player.x -= player.speed;
player.direction = 1;
}
if (keys.ArrowRight) {
player.x += player.speed;
player.direction = 0;
}
if ((keys.Space || keys.z) && gameRunning) {
shoot(true);
}
// Keep player within bounds
player.x = Math.max(player.width / 2, Math.min(canvas.width - player.width / 2, player.x));
player.y = Math.max(player.height / 2, Math.min(canvas.height - player.height / 2, player.y));
// Spawn enemies
if (Math.random() < 0.02) {
spawnEnemy();
}
// Update and draw bullets
for (let i = player.bullets.length - 1; i >= 0; i--) {
const bullet = player.bullets[i];
bullet.x += bullet.speed;
// Remove bullets that are off screen
if (bullet.x < 0 || bullet.x > canvas.width) {
player.bullets.splice(i, 1);
continue;
}
// Check bullet-enemy collisions
if (bullet.playerBullet) {
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
const dx = bullet.x - enemy.x;
const dy = bullet.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
enemy.health--;
player.bullets.splice(i, 1);
playSound('hit');
if (enemy.health <= 0) {
score += enemyTypes[enemy.type].score;
createExplosion(enemy.x, enemy.y, 40);
enemies.splice(j, 1);
}
break;
}
}
} else {
// Check bullet-player collision
const dx = bullet.x - player.x;
const dy = bullet.y - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
playerHealth -= 10;
player.bullets.splice(i, 1);
playSound('hit');
if (playerHealth <= 0) {
gameOver();
return;
}
}
}
}
// Update and draw enemies
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
enemy.x -= enemy.speed;
// Remove enemies that are off screen
if (enemy.x < -50) {
enemies.splice(i, 1);
continue;
}
// Enemy shooting
if (Math.random() < 0.01) {
shoot(false);
}
// Check player-enemy collision
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
playerHealth -= 20;
playSound('hit');
createExplosion(enemy.x, enemy.y, 50);
enemies.splice(i, 1);
if (playerHealth <= 0) {
gameOver();
return;
}
}
}
// Update explosions
const now = Date.now();
for (let i = explosions.length - 1; i >= 0; i--) {
const explosion = explosions[i];
explosion.progress = (now - explosion.startTime) / explosion.duration;
if (explosion.progress >= 1) {
explosions.splice(i, 1);
}
}
// Draw game objects
for (const bullet of player.bullets) {
drawBullet(bullet);
}
for (const enemy of enemies) {
drawEnemy(enemy);
}
for (const explosion of explosions) {
drawExplosion(explosion);
}
drawPlayer();
// Update UI
scoreDisplay.textContent = `Score: ${score}`;
healthDisplay.textContent = `Health: ${playerHealth}%`;
healthDisplay.style.color = playerHealth > 50 ? '#0f0' : playerHealth > 20 ? '#ff0' : '#f00';
requestAnimationFrame(gameLoop);
}
// Game state functions
function startGame() {
// Reset game state
score = 0;
playerHealth = 100;
player.x = canvas.width / 4;
player.y = canvas.height / 2;
player.bullets = [];
enemies.length = 0;
explosions.length = 0;
// Hide start screen, show game
startScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
gameRunning = true;
// Start game loop
gameLoop();
}
function gameOver() {
gameRunning = false;
finalScoreDisplay.textContent = `Final Score: ${score}`;
gameOverScreen.style.display = 'flex';
createExplosion(player.x, player.y, 60);
}
// Event listeners
startButton.addEventListener('click', startGame);
restartButton.addEventListener('click', startGame);
// Initialize explosions with startTime
function initExplosion(explosion) {
explosion.startTime = Date.now();
return explosion;
}
// Handle window resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment