⬇️게임 실행하기⬇️
https://disosaur-game.onrender.com/
🦖미니 공룡게임🦖
🦖미니 공룡게임🦖 ➡️ 앞으로 이동 🔄 게임 초기화
disosaur-game.onrender.com
https://tuesday04.tistory.com/87
방향 전환 수학 이해를 위한 미니 게임
https://github.com/veryyounng/disosaur_game GitHub - veryyounng/disosaur_gameContribute to veryyounng/disosaur_game development by creating an account on GitHub.github.com 💡 제작 계기백준 알고리즘 문제 3190번 - 뱀을 풀던 중,뱀이 방
tuesday04.tistory.com
https://github.com/veryyounng/disosaur_game.git
GitHub - veryyounng/disosaur_game
Contribute to veryyounng/disosaur_game development by creating an account on GitHub.
github.com
1) 게임 규칙 요약
- 보드는 5×5, 공룡은 (0,0) 에서 시작 (머리 1칸)
- 방향 벡터로 이동: right=(1,0), left=(-1,0), up=(0,-1), down=(0,1)
- 벽은 시작 시 랜덤 배치
- 사과(🍎) 를 먹으면 길이 +1 (꼬리를 지우지 않음)
- 반대 방향(180°) 즉시 전환은 금지(길이 ≥ 2일 때) → 즉시 자기충돌 방지
- 집(🏠) = (4,4) 에 도달하면 승리: “🏁 공룡이 무사히 도착했어요!” 표시 후 입력 막음
- 충돌(벽/경계/자기몸)이면 게임 오버
< 구현 화면 >

방향키 조절하는 법

위, 아래, 왼쪽, 오른쪽 방향키를 클릭하면 방향이 전환된다.
원하는 방향으로 이동한다.

충돌

벽/경계/자기몸과 닿으면 충돌 !
게임 오버
사과(🍎) 를 먹으면 길이 +1

반대 방향(180°) 즉시 전환은 금지

집(🏠)

집에 무사히 도착하면 게임 승리!
2) 핵심 설계: “다음 위치 = 현재 위치 + 방향 벡터”
모든 움직임은 2D 벡터 합으로 표현한다.
dx, dy = DIRECTION
nx, ny = head_x + dx, head_y + dy
- 이동: 새 머리를 앞에 추가하고(리스트 맨 앞),
- 사과를 먹지 않으면 꼬리를 하나 제거(길이 유지)
- 사과를 먹으면 꼬리를 제거하지 않음(=성장)
이 간단한 규칙만으로 방향 전환 시 몸이 자연스럽게 꺾여 따라오는 효과가 생긴다.
3) 핵심 코드
전역 상태
# constants
BOARD_SIZE = 5
N_WALLS = 3
GOAL = (4, 4) # 집 좌표
GAME_OVER = False
DIR_MAP = {
"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0)
}
DIRECTION = DIR_MAP["right"]
SNAKE = [[1, 1]] # 머리만 1칸 시작
WALLS = [] # [(x,y), ...]
APPLE = None # (x,y) or None
def in_bounds(x, y): return 0 <= x < BOARD_SIZE and 0 <= y < BOARD_SIZE
def random_empty_cell(excludes:set[tuple[int,int]]):
cand = [(x,y) for x in range(BOARD_SIZE) for y in range(BOARD_SIZE)
if (x,y) not in excludes]
return random.choice(cand) if cand else None
def opposite(a, b): # a가 b와 정반대?
return a[0] == -b[0] and a[1] == -b[1]
초기화 & 랜덤 배치
def reset_game():
global SNAKE, WALLS, DIRECTION, APPLE, GAME_OVER
DIRECTION = DIR_MAP["right"]
SNAKE = [[1, 1]]
WALLS = []
GAME_OVER = False
occupied = {(x, y) for (x, y) in SNAKE}
occupied.add(GOAL) # 집에는 아무것도 두지 않음
# 랜덤 벽
while len(WALLS) < N_WALLS:
x, y = random.randint(0,4), random.randint(0,4)
if (x,y) not in occupied and (x,y) not in WALLS:
WALLS.append((x,y))
occupied.add((x,y))
# 사과
APPLE = random_empty_cell(occupied)
상태 조회
@app.get("/api/state")
def get_state():
board = [["" for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
for (x,y) in WALLS: board[y][x] = "🧱"
gx, gy = GOAL; board[gy][gx] = "🏠"
if APPLE: board[APPLE[1]][APPLE[0]] = "🍎"
for i, (x,y) in enumerate(SNAKE):
board[y][x] = "🦖" if i==0 else "🦕"
return dict(board=board, snake=SNAKE, apple=APPLE, walls=WALLS,
direction=DIRECTION, goal=GOAL, game_over=GAME_OVER)
방향 전환: 반대방향 금지(길이≥2)
class DirectionRequest(BaseModel):
direction: str
@app.post("/api/change_direction")
def change_direction(req: DirectionRequest):
global DIRECTION, GAME_OVER
if GAME_OVER: return {"status": "finished"}
d = req.direction.lower()
if d not in DIR_MAP: return {"status": "invalid"}
new_dir = DIR_MAP[d]
# 핵심: 길이가 2칸 이상이면 즉시 반대방향 금지 (자기충돌 방지)
if len(SNAKE) >= 2 and opposite(new_dir, DIRECTION):
return {"status": "invalid", "reason": "opposite turn not allowed"}
DIRECTION = new_dir
return {"status": "ok", "direction": DIRECTION}
이동: 성장/충돌/목표 도달
@app.post("/api/move")
def move():
global SNAKE, APPLE, GAME_OVER
if GAME_OVER: return {"status": "finished", "game_over": True}
dx, dy = DIRECTION
head_x, head_y = SNAKE[0]
nx, ny = head_x + dx, head_y + dy
# 1) 경계/벽 충돌
if not in_bounds(nx, ny) or (nx, ny) in WALLS:
GAME_OVER = True
return {"status": "crash", "game_over": True}
# 2) 목표 도달 (집)
if (nx, ny) == GOAL:
SNAKE = [[nx, ny]] + SNAKE[:-1] if len(SNAKE) > 0 else [[nx, ny]]
GAME_OVER = True
return {"status": "goal", "game_over": True, "snake": SNAKE}
will_grow = (APPLE is not None and (nx, ny) == APPLE)
# 3) 자기충돌 (단, 성장하지 않고 '꼬리 자리'로 이동하는 건 허용)
body_set = set(map(tuple, SNAKE))
tail = tuple(SNAKE[-1])
if (nx, ny) in body_set and not (not will_grow and (nx, ny) == tail):
GAME_OVER = True
return {"status": "crash", "game_over": True}
# 4) 이동/성장
new_head = [nx, ny]
if will_grow:
SNAKE = [new_head] + SNAKE # 꼬리 유지 → 성장(+1)
occupied = set(map(tuple, SNAKE)).union(WALLS, {GOAL})
APPLE = random_empty_cell(occupied) # 새 사과
return {"status": "ok", "event": "eat", "snake": SNAKE, "apple": APPLE}
else:
SNAKE = [new_head] + SNAKE[:-1] # 꼬리 제거 → 길이 유지
return {"status": "ok", "event": "move", "snake": SNAKE, "apple": APPLE}
4) “반대방향 금지”는 왜 필요할까?
길이가 2칸 이상일 때 즉시 U턴을 허용하면, 다음 이동이 곧바로 자기 몸(바로 뒤 segment) 으로 들어가 즉시 게임오버가 난다.
그래서 전통적인 스네이크 룰은 즉시 반대 전환 금지를 채택한다.
필요하면 변형 룰도 가능:
(A) 완전 허용(자살 가능), (B) ‘이번 턴 꼬리로 돌아가기’만 허용, (C) 즉시 몸을 뒤집어 Head↔Tail 스왑 등.
5) 배운 점
이번 공룡게임 구현을 통해 방향 벡터, 예외 처리(반대 방향 이동 방지), 큐/리스트를 활용한 자료구조 관리 같은 알고리즘 개념이 얼마나 중요한지 체감했습니다.
또한 조건 분기(사과 먹기, 충돌 판정, 도착 여부)를 통해 게임 규칙을 코드로 옮기는 과정에서 문제 해결력을 키울 수 있었습니다.
'개발로 사고하기' 카테고리의 다른 글
| 📊 경기도 인증 지역서점 데이터 시각화 — 지역별 분포 Bar Chart 구현기 (1) | 2025.08.25 |
|---|---|
| 경기데이터드림- 경기도 지역서점 데이터 OpenAPI 연동기 (3) | 2025.08.25 |
| 미니 공룡 게임에 방향 전환 기능 추가 (0) | 2025.08.19 |
| 방향 전환 수학 이해를 위한 미니 게임 (3) | 2025.08.14 |
| Tic Tac Toe 게임- 미니맥스(Minimax) 알고리즘 (2) | 2025.08.13 |