# Distribution Invaders + Chi-square
# pip install pygame numpy scipy
import pygame
import random
import numpy as np
from scipy.stats import norm, t, uniform, laplace, chi2
pygame.init()
# ====================================
# 화면
# ====================================
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Distribution Invaders")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 24)
WHITE = (255,255,255)
BLACK = (0,0,0)
RED = (220,50,50)
GREEN = (50,220,50)
BLUE = (50,50,220)
YELLOW = (255,220,0)
ORANGE = (255,120,0)
# ====================================
# 플레이어
# ====================================
player = pygame.Rect(WIDTH//2-30, HEIGHT-70, 60, 35)
bullets = []
# ====================================
# 분포 생성 함수
# (새 적 생성 시만 랜덤)
# ====================================
def make_normal():
m = random.randint(-2, 2)
s = random.choice([0.5, 1, 1.5])
return {
"name": f"Normal({m},{s})",
"f": lambda x: norm.pdf(x, m, s),
"color": BLUE
}
def make_t():
df = random.choice([1,2,3,5,10])
return {
"name": f"t(df={df})",
"f": lambda x: t.pdf(x, df),
"color": RED
}
def make_uniform():
a = -random.choice([1,2])
b = random.choice([2,3])
return {
"name": f"Uniform({a},{b})",
"f": lambda x: uniform.pdf(x, a, b-a),
"color": GREEN
}
def make_laplace():
b = random.choice([0.5,0.7,1.0])
return {
"name": f"Laplace({b})",
"f": lambda x: laplace.pdf(x, 0, b),
"color": YELLOW
}
def make_chi2():
df = random.choice([1,2,3,5,10])
return {
"name": f"ChiSq(df={df})",
"f": lambda x: chi2.pdf(x+3, df),
"color": ORANGE
}
DISTROS = [
make_normal,
make_t,
make_uniform,
make_laplace,
make_chi2
]
# ====================================
# Enemy
# ====================================
class Enemy:
def __init__(self, x, y, name, f, color):
self.x = x
self.y = y
self.name = name
self.f = f
self.color = color
self.speed = 1 + random.random()
# 크기 2배
self.width = 240
self.height = 160
# 부분 파괴 mask
self.damage = np.ones(60, dtype=bool)
def rect(self):
return pygame.Rect(
self.x,
self.y,
self.width,
self.height
)
# 모양은 유지
def move(self):
self.y += self.speed
# 좌우 10개 제거
def hit(self, bullet_x):
idx = int(
(bullet_x - self.x)
/ self.width
* 60
)
for k in range(idx-10, idx+11):
if 0 <= k < 60:
self.damage[k] = False
return np.sum(self.damage) < 5
def draw(self):
xs = np.linspace(-3, 3, 60)
ys = self.f(xs)
if ys.max() > 0:
ys = ys / ys.max() * 80
pts = []
for i, yv in enumerate(ys):
if not self.damage[i]:
if len(pts) > 1:
pygame.draw.lines(
screen,
self.color,
False,
pts,
4
)
pts = []
continue
px = self.x + i*(self.width/59)
py = self.y + self.height - yv
pts.append((px, py))
if len(pts) > 1:
pygame.draw.lines(
screen,
self.color,
False,
pts,
4
)
txt = font.render(
self.name,
True,
WHITE
)
screen.blit(
txt,
(self.x, self.y-30)
)
# ====================================
# 게임 변수
# ====================================
enemies = []
score = 0
lives = 3
spawn_timer = 0
auto_fire_timer = 0
running = True
# ====================================
# 메인 루프
# ====================================
while running:
clock.tick(60)
screen.fill(BLACK)
# ------------------
# 이벤트
# ------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
bullets.append(
pygame.Rect(
player.centerx-3,
player.y,
6,
20
)
)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
bullets.append(
pygame.Rect(
player.centerx-3,
player.y,
6,
20
)
)
# ------------------
# 마우스 드래그 이동
# ------------------
mx, my = pygame.mouse.get_pos()
if pygame.mouse.get_pressed()[0]:
player.centerx = mx
player.x = max(
0,
min(WIDTH-player.width,
player.x)
)
# ------------------
# 자동 연사
# ------------------
auto_fire_timer += 1
if pygame.mouse.get_pressed()[0] and auto_fire_timer > 8:
bullets.append(
pygame.Rect(
player.centerx-3,
player.y,
6,
20
)
)
auto_fire_timer = 0
# ------------------
# 적 생성
# ------------------
spawn_timer += 1
if spawn_timer > 70:
spawn_timer = 0
maker = random.choice(DISTROS)
d = maker()
enemies.append(
Enemy(
random.randint(
50,
WIDTH-300
),
30,
d["name"],
d["f"],
d["color"]
)
)
# ------------------
# 총알 이동
# ------------------
for b in bullets[:]:
b.y -= 12
if b.y < 0:
bullets.remove(b)
else:
pygame.draw.rect(
screen,
WHITE,
b
)
# ------------------
# 충돌
# ------------------
for e in enemies[:]:
e.move()
e.draw()
if e.y > HEIGHT:
enemies.remove(e)
lives -= 1
continue
for b in bullets[:]:
if e.rect().colliderect(b):
if b in bullets:
bullets.remove(b)
destroyed = e.hit(
b.centerx
)
if destroyed:
enemies.remove(e)
score += 1
break
# ------------------
# 플레이어
# ------------------
pygame.draw.rect(
screen,
WHITE,
player
)
# ------------------
# HUD
# ------------------
hud = font.render(
f"Score: {score} Lives: {lives}",
True,
WHITE
)
screen.blit(hud, (20,20))
tip = font.render(
"Drag mouse / Hold click",
True,
WHITE
)
screen.blit(tip, (850,20))
# ------------------
# Game Over
# ------------------
if lives <= 0:
txt = font.render(
"GAME OVER",
True,
RED
)
screen.blit(
txt,
(WIDTH//2-80,
HEIGHT//2)
)
pygame.display.flip()
pygame.time.wait(3000)
running = False
pygame.display.flip()
pygame.quit()
댓글 0