gpt에게 요구한 내용들
•
간단한 게임 만들라고 했음
•
중간에 에러나서 에러 메세지 복붙함.
최종 화면
최종 코드
import run
import streamlit as st
import numpy as np
from PIL import Image
# =========================
# 기본 설정
# =========================
st.set_page_config(page_title="🧱 Streamlit Tetris Mini", layout="centered")
st.title("🧱 Streamlit Tetris (Mini)")
st.caption("버튼으로 조작: ⬅️➡️ 이동, ⟳ 회전, ⬇️ 내리기 · 자동 낙하 / 점수·레벨 지원")
# =========================
# 게임 상수
# =========================
W, H = 10, 20 # 보드 크기
BLOCK = 24 # 렌더링 블록 픽셀
BG = 0 # 빈칸
COLORS = {
0: (20, 20, 25),
1: (0, 255, 255), # I
2: (0, 0, 255), # J
3: (255, 165, 0), # L
4: (255, 255, 0), # O
5: (0, 255, 0), # S
6: (128, 0, 128), # T
7: (255, 0, 0), # Z
8: (90, 90, 100), # 고정된 블록 윤곽 강조(섞임 방지용)
}
# 7가지 테트리미노(회전 시에는 np.rot90 사용)
SHAPES = {
1: np.array([[1,1,1,1]]), # I
2: np.array([[2,0,0],
[2,2,2]]), # J
3: np.array([[0,0,3],
[3,3,3]]), # L
4: np.array([[4,4],
[4,4]]), # O
5: np.array([[0,5,5],
[5,5,0]]), # S
6: np.array([[0,6,0],
[6,6,6]]), # T
7: np.array([[7,7,0],
[0,7,7]]), # Z
}
# =========================
# 유틸 함수
# =========================
def new_board():
return np.zeros((H, W), dtype=int)
def spawn_piece():
kind = np.random.choice(list(SHAPES.keys()))
mat = SHAPES[kind].copy()
x = (W - mat.shape[1]) // 2
y = 0
return {"kind": kind, "mat": mat, "x": x, "y": y}
def collide(board, piece, dx=0, dy=0, rot_mat=None):
mat = rot_mat if rot_mat is not None else piece["mat"]
x, y = piece["x"] + dx, piece["y"] + dy
h, w = mat.shape
# 경계
if x < 0 or x + w > W or y + h > H:
return True
# 충돌
region = board[y:y+h, x:x+w]
return np.any((mat > 0) & (region > 0))
def lock_piece(board, piece):
mat = piece["mat"]
x, y = piece["x"], piece["y"]
h, w = mat.shape
region = board[y:y+h, x:x+w]
mask = mat > 0
region[mask] = mat[mask]
board[y:y+h, x:x+w] = region
return board
def clear_lines(board):
full = np.where(np.all(board > 0, axis=1))[0]
n = len(full)
if n > 0:
board = np.delete(board, full, axis=0)
board = np.vstack([np.zeros((n, W), dtype=int), board])
return board, n
def render(board, piece=None):
img_grid = np.zeros((H, W, 3), dtype=np.uint8)
# 고정 보드
for val, color in COLORS.items():
if val == 0: continue
mask = board == val
img_grid[mask] = color
# 낙하 중인 조각 오버레이
if piece is not None:
mat = piece["mat"]; x = piece["x"]; y = piece["y"]
h, w = mat.shape
for r in range(h):
for c in range(w):
if 0 <= y+r < H and 0 <= x+c < W and mat[r, c] > 0:
img_grid[y+r, x+c] = COLORS[piece["kind"]]
# 최근 채워진 줄 하이라이트(선택)도 가능하지만 간단 버전
img = Image.fromarray(img_grid, mode="RGB")
img = img.resize((W*BLOCK, H*BLOCK), resample=Image.NEAREST)
return img
def try_rotate(board, piece, cw=True):
rot = np.rot90(piece["mat"], -1 if cw else 1)
# 간단한 벽킥: 제자리, 좌(-1), 우(+1) 시도
for shift in [0, -1, 1, -2, 2]:
if not collide(board, piece, dx=shift, dy=0, rot_mat=rot):
piece["mat"] = rot
piece["x"] += shift
return True
return False
def gravity_interval_ms(level):
# 레벨이 올라갈수록 빨라짐(하한선 120ms)
return max(120, 700 - (level-1)*60)
def score_for_lines(n, level):
base = {1:40, 2:100, 3:300, 4:1200}.get(n, 0)
return base * level
# =========================
# 세션 상태 초기화
# =========================
if "board" not in st.session_state:
st.session_state.board = new_board()
if "piece" not in st.session_state:
st.session_state.piece = spawn_piece()
if "next_piece" not in st.session_state:
st.session_state.next_piece = spawn_piece()
if "score" not in st.session_state:
st.session_state.score = 0
if "lines" not in st.session_state:
st.session_state.lines = 0
if "level" not in st.session_state:
st.session_state.level = 1
if "paused" not in st.session_state:
st.session_state.paused = False
if "game_over" not in st.session_state:
st.session_state.game_over = False
if "tick" not in st.session_state:
st.session_state.tick = 0
# =========================
# 자동 낙하 (autorefresh)
# =========================
interval = gravity_interval_ms(st.session_state.level)
if not st.session_state.paused and not st.session_state.game_over:
st.experimental_rerun = st.autorefresh(interval=interval, limit=1, key="tetris_autorefresh")
# 아래로 한 칸 시도
if not collide(st.session_state.board, st.session_state.piece, dy=1):
st.session_state.piece["y"] += 1
else:
# 고정/줄삭제/새 조각
st.session_state.board = lock_piece(st.session_state.board, st.session_state.piece)
st.session_state.board, cleared = clear_lines(st.session_state.board)
if cleared:
st.session_state.lines += cleared
st.session_state.score += score_for_lines(cleared, st.session_state.level)
# 10줄마다 레벨업
new_level = 1 + st.session_state.lines // 10
if new_level > st.session_state.level:
st.session_state.level = new_level
st.session_state.piece = st.session_state.next_piece
st.session_state.next_piece = spawn_piece()
# 스폰 즉시 충돌이면 게임오버
if collide(st.session_state.board, st.session_state.piece):
st.session_state.game_over = True
# =========================
# 좌측: 보드 렌더
# 우측: 정보/다음 조각/조작
# =========================
left, right = st.columns([3, 1])
with left:
st.image(render(st.session_state.board, st.session_state.piece), use_column_width=False)
with right:
st.markdown(f"**Score:** {st.session_state.score}")
st.markdown(f"**Lines:** {st.session_state.lines}")
st.markdown(f"**Level:** {st.session_state.level}")
st.markdown("---")
st.markdown("**Next**")
# 다음 조각 미니 프리뷰
nxt = st.session_state.next_piece
# 미니보드 4x4에 투영
preview = np.zeros((4, 4), dtype=int)
m = nxt["mat"]; h, w = m.shape
oy = (4 - h)//2; ox = (4 - w)//2
preview[oy:oy+h, ox:ox+w] = np.where(m>0, nxt["kind"], 0)
# 미니 렌더
img_prev = Image.new("RGB", (4*BLOCK, 4*BLOCK), (20,20,25))
arr = np.zeros((4, 4, 3), dtype=np.uint8)
for val, color in COLORS.items():
if val == 0: continue
arr[preview==val] = color
Image.fromarray(arr, "RGB").resize((4*BLOCK, 4*BLOCK), Image.NEAREST).paste(img_prev)
st.image(Image.fromarray(arr, "RGB").resize((4*BLOCK, 4*BLOCK), Image.NEAREST))
st.markdown("---")
c1, c2, c3 = st.columns(3)
with c1:
if st.button("⬅️ Left"):
if not collide(st.session_state.board, st.session_state.piece, dx=-1):
st.session_state.piece["x"] -= 1
with c2:
if st.button("⟳ Rotate"):
try_rotate(st.session_state.board, st.session_state.piece, cw=True)
with c3:
if st.button("➡️ Right"):
if not collide(st.session_state.board, st.session_state.piece, dx=1):
st.session_state.piece["x"] += 1
if st.button("⬇️ Soft Drop"):
if not collide(st.session_state.board, st.session_state.piece, dy=1):
st.session_state.piece["y"] += 1
if st.button("⏬ Hard Drop"):
# 끝까지
while not collide(st.session_state.board, st.session_state.piece, dy=1):
st.session_state.piece["y"] += 1
# 즉시 고정
st.session_state.board = lock_piece(st.session_state.board, st.session_state.piece)
st.session_state.board, cleared = clear_lines(st.session_state.board)
if cleared:
st.session_state.lines += cleared
st.session_state.score += score_for_lines(cleared, st.session_state.level)
new_level = 1 + st.session_state.lines // 10
if new_level > st.session_state.level:
st.session_state.level = new_level
st.session_state.piece = st.session_state.next_piece
st.session_state.next_piece = spawn_piece()
if collide(st.session_state.board, st.session_state.piece):
st.session_state.game_over = True
c4, c5 = st.columns(2)
with c4:
if st.button("⏸ Pause / Resume"):
st.session_state.paused = not st.session_state.paused
with c5:
if st.button("🔄 Reset"):
st.session_state.board = new_board()
st.session_state.piece = spawn_piece()
st.session_state.next_piece = spawn_piece()
st.session_state.score = 0
st.session_state.lines = 0
st.session_state.level = 1
st.session_state.paused = False
st.session_state.game_over = False
if st.session_state.game_over:
st.error("💥 Game Over! 🔄 Reset 버튼으로 다시 시작하세요.")
# 작은 도움말
with st.expander("도움말"):
st.markdown(
"""
- **자동 낙하**: 레벨에 따라 속도가 빨라집니다.
- **조작**: ⬅️➡️ 이동, ⟳ 회전, ⬇️ 소프트드롭, ⏬ 하드드롭, ⏸ 일시정지, 🔄 리셋
- **점수**: 1/2/3/4줄 동시 삭제 시 40/100/300/1200 × 레벨
"""
)
Python
복사


