Created
January 7, 2026 20:55
-
-
Save TheTridentGuy/4cfec5c8e177d585f35792413c92adf0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math | |
| import random | |
| from browser import document | |
| SHIP_W = 30 | |
| SHIP_H = 40 | |
| ACCELERATION_INCREMENT = 0.1 | |
| ROTATION_INCREMENT = 3 | |
| BULLET_A = 15 | |
| BULLET_R = 5 | |
| ASTEROID_R = 10 | |
| arrow_up_down = False | |
| arrow_down_down = False | |
| arrow_left_down = False | |
| arrow_right_down = False | |
| space_down = False | |
| collision_in_progress = False | |
| def r(x, y, ox, oy, angle): | |
| x = ox + math.cos(angle) * (x - ox) - math.sin(angle) * (y - oy) | |
| y = oy + math.sin(angle) * (x - ox) + math.cos(angle) * (y - oy) | |
| return x, y | |
| class Ship(): | |
| def __init__(self, x, y): | |
| self.x = x | |
| self.y = y | |
| self.a_x = 0 | |
| self.a_y = 0 | |
| self.r = 0 | |
| def move(self): | |
| self.x += self.a_x | |
| self.y += self.a_y | |
| if self.x > canvas.width: | |
| self.x = 0 | |
| elif self.x < 0: | |
| self.x = canvas.width | |
| if self.y > canvas.height: | |
| self.y = 0 | |
| elif self.y < 0: | |
| self.y = canvas.height | |
| def draw(self): | |
| ctx.translate(self.x, self.y) | |
| ctx.rotate(math.radians(self.r+90)) | |
| ctx.fillStyle = "#000000" | |
| ctx.beginPath() | |
| ctx.moveTo(-SHIP_W/2, SHIP_H/2) | |
| ctx.lineTo(SHIP_W/2, SHIP_H/2) | |
| ctx.lineTo(0, -SHIP_H/2) | |
| ctx.fill() | |
| ctx.rotate(-math.radians(self.r+90)) | |
| ctx.translate(-self.x, -self.y) | |
| class Bullet(): | |
| def __init__(self, x, y, r): | |
| self.x = x | |
| self.y = y | |
| self.r = r | |
| def move(self): | |
| self.x += math.cos(math.radians(self.r))*BULLET_A | |
| self.y += math.sin(math.radians(self.r))*BULLET_A | |
| def draw(self): | |
| ctx.fillStyle = "#000000" | |
| ctx.beginPath() | |
| ctx.arc(self.x, self.y, BULLET_R, 0, 360, False) | |
| ctx.fill() | |
| class Asteroid(): | |
| def __init__(self): | |
| self.x = random.randint(0, canvas.width) | |
| self.y = random.randint(0, canvas.height) | |
| self.r = random.randint(0, 360) | |
| self.a = random.random()*2 | |
| self.hp = random.randint(1, 3) | |
| def move(self): | |
| self.x += math.cos(math.radians(self.r))*self.a | |
| self.y += math.sin(math.radians(self.r))*self.a | |
| if self.x > canvas.width: | |
| self.x = 0 | |
| elif self.x < 0: | |
| self.x = canvas.width | |
| if self.y > canvas.height: | |
| self.y = 0 | |
| elif self.y < 0: | |
| self.y = canvas.height | |
| def damage(self): | |
| self.hp -= 1 | |
| def draw(self): | |
| ctx.fillStyle = "#000000" | |
| ctx.beginPath() | |
| ctx.arc(self.x, self.y, abs(ASTEROID_R*self.hp), 0, 360, False) | |
| ctx.fill() | |
| class Particle(): | |
| def __init__(self, x, y, color): | |
| self.x = x | |
| self.y = y | |
| self.r = random.randint(0, 360) | |
| self.life = random.randint(3,7) | |
| self.color = color | |
| def move(self): | |
| self.x += math.cos(math.radians(self.r))*BULLET_A | |
| self.y += math.sin(math.radians(self.r))*BULLET_A | |
| def draw(self): | |
| self.life -= 1 | |
| ctx.fillStyle = self.color | |
| ctx.beginPath() | |
| ctx.arc(self.x, self.y, random.randint(2,5), 0, 360, False) | |
| ctx.fill() | |
| def draw_frame(): | |
| global score, hp, collision_in_progress | |
| clean() | |
| if hp <= 0: | |
| draw_end_frame() | |
| return | |
| ctx.clearRect(0, 0, canvas.width, canvas.height) | |
| if arrow_up_down: | |
| ship.a_x += math.cos(math.radians(ship.r))*ACCELERATION_INCREMENT | |
| ship.a_y += math.sin(math.radians(ship.r))*ACCELERATION_INCREMENT | |
| if arrow_down_down: | |
| ship.a_x -= math.cos(math.radians(ship.r))*ACCELERATION_INCREMENT | |
| ship.a_y -= math.sin(math.radians(ship.r))*ACCELERATION_INCREMENT | |
| if arrow_left_down: | |
| ship.r -= ROTATION_INCREMENT | |
| if arrow_right_down: | |
| ship.r += ROTATION_INCREMENT | |
| if space_down: | |
| bullets.append(Bullet(ship.x, ship.y, ship.r)) | |
| for particle in particles: | |
| particle.move() | |
| particle.draw() | |
| for bullet in bullets: | |
| for asteroid in asteroids: | |
| if abs(bullet.x - asteroid.x) < ASTEROID_R*asteroid.hp and abs(bullet.y - asteroid.y) < ASTEROID_R*asteroid.hp: | |
| score += 1 | |
| asteroid.damage() | |
| particles.extend([Particle(asteroid.x, asteroid.y, "orange") for _ in range(8)]) | |
| particles.extend([Particle(asteroid.x, asteroid.y, "yellow") for _ in range(6)]) | |
| bullet.move() | |
| bullet.draw() | |
| had_collision = False | |
| for asteroid in asteroids: | |
| if abs(ship.x - asteroid.x) < ASTEROID_R*asteroid.hp and abs(ship.y - asteroid.y) < ASTEROID_R*asteroid.hp: | |
| had_collision = True | |
| if not collision_in_progress: | |
| hp -= 1 | |
| collision_in_progress = True | |
| particles.extend([Particle(ship.x, ship.y, "blue") for _ in range(15)]) | |
| asteroid.move() | |
| asteroid.draw() | |
| if not had_collision: | |
| collision_in_progress = False | |
| ctx.font = "30px monospace" | |
| if score < 20: | |
| ctx.fillStyle = "#000000" | |
| elif score < 40: | |
| ctx.fillStyle = "orange" | |
| elif score < 60: | |
| ctx.fillStyle = "red" | |
| elif score < 150: | |
| ctx.fillStyle = "blue" | |
| particles.extend([Particle(30, 25, "blue") for _ in range(3)]) | |
| else: | |
| color = random.choice(["#00ff00", "#009f00", "#00fe00", "#00fd00"]) | |
| particles.extend([Particle(30, 25, color) for _ in range(5)]) | |
| ctx.fillStyle = color | |
| ctx.fillText(score, 10, 40) | |
| ship.move() | |
| ship.draw() | |
| for i in range(hp): | |
| draw_heart(10+20 *i, canvas.height-25) | |
| def keydown(event): | |
| global arrow_up_down, arrow_down_down, arrow_left_down, arrow_right_down, space_down | |
| if event.key in ["ArrowUp", "w"]: | |
| arrow_up_down = True | |
| elif event.key in ["ArrowDown", "s"]: | |
| arrow_down_down = True | |
| elif event.key in ["ArrowLeft", "a"]: | |
| arrow_left_down = True | |
| elif event.key in ["ArrowRight", "d"]: | |
| arrow_right_down = True | |
| elif event.key == " ": | |
| space_down = True | |
| def draw_end_frame(): | |
| for particle in particles: | |
| particle.move() | |
| particle.draw() | |
| ctx.fillStyle = "#000000" | |
| ctx.font = "30px monospace" | |
| ctx.fillText("Game Over", canvas.width/2-80, canvas.height/2-10) | |
| ctx.fillText(score, canvas.width/2-10, canvas.height/2+20) | |
| def keyup(event): | |
| global arrow_up_down, arrow_down_down, arrow_left_down, arrow_right_down, space_down | |
| if event.key in ["ArrowUp", "w"]: | |
| arrow_up_down = False | |
| elif event.key in ["ArrowDown", "s"]: | |
| arrow_down_down = False | |
| elif event.key in ["ArrowLeft", "a"]: | |
| arrow_left_down = False | |
| elif event.key in ["ArrowRight", "d"]: | |
| arrow_right_down = False | |
| elif event.key == " ": | |
| space_down = False | |
| def clean(): | |
| global bullets, asteroids, particles | |
| good_bullets = [] | |
| for bullet in bullets: | |
| if 0 <= bullet.x <= canvas.width and 0 <= bullet.y <= canvas.height: | |
| good_bullets.append(bullet) | |
| bullets = good_bullets | |
| live_asteroids = [] | |
| for asteroid in asteroids: | |
| if asteroid.hp <= 0: | |
| live_asteroids.append(Asteroid()) | |
| else: | |
| live_asteroids.append(asteroid) | |
| asteroids = live_asteroids | |
| live_particles = [] | |
| for particle in particles: | |
| if particle.life > 0: | |
| live_particles.append(particle) | |
| particles = live_particles | |
| def draw_heart(x, y, scale=6): | |
| ctx.fillStyle = "red" | |
| ctx.fillRect(x, y, scale, 2*scale) | |
| ctx.fillRect(x+scale, y+scale, scale, 2*scale) | |
| ctx.fillRect(x+2*scale, y, scale, 2*scale) | |
| document.addEventListener("keydown", keydown) | |
| document.addEventListener("keyup", keyup) | |
| ship = Ship(canvas.width/2, canvas.height/2) | |
| bullets = [] | |
| asteroids = [Asteroid() for _ in range(5)] | |
| score = 0 | |
| particles = [] | |
| hp = 6 | |
| print("Click inside the canvas to focus it.") | |
| print("WASD to move, SPACE to fire.") | |
| print("This game has infinite endings, because you can end with any score, and every frame is a scene.") | |
| interval = timer.set_interval(draw_frame, 10) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment