로보테크AI

융합_로보테크 AI 자율주행 로봇 개발자 과정-26/01/07

steezer 2026. 1. 7. 18:30

역할: player

3-1. 플레이어 상태 초기화: 스테이지 시작 시 플레이어 좌표를 시작 지점으로 설정(4)하고, 체력을 기본값(3)으로 설정한다.

 

3-2. 입력 처리(스킬 분기): 사용자의 키 입력을 받아 w/a/s/d는 이동 스킬로, r은 공격 스킬로 처리한다. 그 외 입력은 무시한다(플레이어 상태 변화 없음).

 

3-3. 이동 스킬(w/a/s/d): 입력 방향으로 1칸 이동을 시도한다. 이동 가능 여부는 맵 모듈의 판정 결과를 따른다(이동 불가면 좌표 유지).

 

3-4. 공격 스킬(r): “몬스터와 마주친 상태”에서만 공격을 적용한다. 공격 성공 시 대상(인접 몬스터)의 체력을 1 감소시키도록 몬스터 처리 함수를 호출한다. 인접 대상이 없으면 변화 없이 종료한다.

 

3-5. 피격/체력 갱신: 몬스터로부터 피격 이벤트가 발생했을 때 체력을 1 감소시킨다. 체력이 0이 되면 게임 오버 상태를 상위 루프에 알리고, 플레이어를 스테이지 시작 상태로 초기화한다(좌표/체력).

 

3-6. 스테이지 종료 조건 전달: 플레이어가 도착 지점(5)에 도달했는지 여부를 판정(또는 외부 판정값 수신)하여 “스테이지 종료” 신호를 상위 루프에 반환한다.


player = 플레이어

P로 화면에 표시

W,A,S,D = 이동

R = 공격 스킬

 

start(): 상태 초기화, 좌표 시작 지점 설정, 체력 3 설정

handle(): wasd 입력, r 공격, 그 외 무시

move(): handle() 입력으로 이동 가능하면 T, 안되면 F 반환

t_damage(): 피격 이벤트 발생시 체력 1 감소

dead(): 체력 0되면 게임 오버

reset(): 게임 오버시 스테이지 시작 상태로 초기화(좌표, 체력, 아이템, 몬스터)

render(): P로 화면에 표시

 


객체지향 구조 / 단순 if 처리 X

임시 코드

player.py

# player.py
# 변수(필드)
# _x, _y: 플레이어 좌표
# _hp: 체력(0~3)
# attack_requested: 공격 요청 플래그
#
# 상수(타일 코드)
# WALL=0, START_TILE=5, EXIT_TILE=6, MONSTER_TILE=12
#
# 함수(메서드)
# start(grid) -> (x, y) : 시작 위치로 좌표 설정, 체력 3, 공격 플래그 False
# handle(key, grid) -> (x, y) : W/A/S/D 이동, R 공격 플래그 True, 그 외 무시
# move(key, grid) -> True/False : 이동 가능하면 이동 후 True, 불가하면 False
# t_damage() -> hp : 피격 시 체력 1 감소
# dead() -> True/False : 체력 0이면 True
# reset(grid) -> (x, y) : start와 동일
# render() -> "P" : 화면 표시 문자
# is_exit(grid) -> True/False : 현재 칸이 도착지점인지 확인
# get_attack_targets(grid) -> [(x,y), ...] : 플레이어 기준 인접 8칸(대각선 포함) 몬스터 좌표 목록
# get_x/get_y/get_hp/get_attack_requested : getter
# set_x/set_y/set_hp/set_attack_requested : setter
#
# 호출 방법 ex)
# from player import Player
# p = Player()
# x, y = p.start(grid)
# x, y = p.handle(key, grid)
# if p.get_attack_requested():
#       targets = p.get_attack_targets(grid)
#       공격 처리 후 p.set_attack_requested(False)
# if p.dead(): 게임 오버 처리
# if p.is_exit(grid): 스테이지 처리(맵에서 체크)

class Player:
    WALL = 0
    START_TILE = 5
    EXIT_TILE = 6
    MONSTER_TILE = 12

    MOVE_CODE = {"w": 0, "a": 1, "s": 2, "d": 3}

    def __init__(self):
        self._hp = 3
        self._x = 0
        self._y = 0
        self._attack_requested = False

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = int(x)

    def get_y(self):
        return self._y

    def set_y(self, y):
        self._y = int(y)

    def get_hp(self):
        return self._hp

    def set_hp(self, hp):
        hp = int(hp)
        if hp < 0:
            hp = 0
        if hp > 3:
            hp = 3
        self._hp = hp

    def get_attack_requested(self):
        return self._attack_requested

    def set_attack_requested(self, v):
        self._attack_requested = bool(v)

    def start(self, grid):
        self.set_hp(3)
        self.set_attack_requested(False)

        x, y = self._find_tile(grid, self.START_TILE)
        self.set_x(x)
        self.set_y(y)
        return (self.get_x(), self.get_y())

    def handle(self, key, grid):
        k = (key or "").lower().strip()

        if k in self.MOVE_CODE:
            self.move(k, grid)
            return (self.get_x(), self.get_y())

        if k == "r":
            self.set_attack_requested(True)
            return (self.get_x(), self.get_y())

        return (self.get_x(), self.get_y())

    def move(self, key, grid):
        k = (key or "").lower().strip()
        if k not in self.MOVE_CODE:
            return False

        dx, dy = self._dir_delta(k)
        nx = self.get_x() + dx
        ny = self.get_y() + dy

        if not self._in_bounds(grid, nx, ny):
            return False

        t = self._tile(grid, nx, ny)

        if t == self.WALL:
            return False

        if t == self.MONSTER_TILE:
            return False

        self.set_x(nx)
        self.set_y(ny)
        return True

    def t_damage(self):
        self.set_hp(self.get_hp() - 1)
        return self.get_hp()

    def dead(self):
        return self.get_hp() <= 0

    def reset(self, grid):
        return self.start(grid)

    def render(self):
        return "P"

    def is_exit(self, grid):
        return self._tile(grid, self.get_x(), self.get_y()) == self.EXIT_TILE

    def get_attack_targets(self, grid):
        targets = []
        x0 = self.get_x()
        y0 = self.get_y()

        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                if dx == 0 and dy == 0:
                    continue

                x = x0 + dx
                y = y0 + dy

                if not self._in_bounds(grid, x, y):
                    continue

                if self._tile(grid, x, y) == self.MONSTER_TILE:
                    targets.append((x, y))

        return targets

    def _tile(self, grid, x, y):
        return grid[x][y]

    def _in_bounds(self, grid, x, y):
        w = len(grid)
        h = len(grid[0]) if w > 0 else 0
        return 0 <= x < w and 0 <= y < h

    def _find_tile(self, grid, tile):
        w = len(grid)
        h = len(grid[0]) if w > 0 else 0
        for x in range(w):
            for y in range(h):
                if grid[x][y] == tile:
                    return (x, y)
        return (0, 0)

    def _dir_delta(self, key):
        if key == "w":
            return (0, -1)
        if key == "s":
            return (0, 1)
        if key == "a":
            return (-1, 0)
        if key == "d":
            return (1, 0)
        return (0, 0)

1) 모듈 개요

 

player.py는 플레이어의 좌표(x, y), 체력(hp), 공격 입력 상태를 관리하는 모듈이다.

메인(map.py)은 이 모듈을 import해서 매 턴 입력을 전달하고, 플레이어의 현재 위치와 상태를 확인한다.

 

이 모듈이 직접 처리하는 것은 다음과 같다.

W/A/S/D 입력에 따른 이동(벽/몬스터 칸 이동 금지 포함)

R 입력 발생 여부 저장(공격 요청 플래그 세팅)

피격 이벤트 발생 시 체력 감소

체력 0 도달 여부(게임 오버 판정)

시작/리셋 시 초기 상태 세팅

화면 출력에서 플레이어를 나타내는 문자("P") 제공

 

이 모듈이 직접 처리하지 않는 것은 다음과 같다.

공격 실행(몬스터 제거/길로 변경 등) 자체는 skill 모듈이 담당한다.

스테이지 클리어 신호는 player가 직접 전달하지 않고, map이 is_exit()로 체크한다.

 

2) 변수(필드) 설명

_x, _y (플레이어 좌표)

 

플레이어의 현재 위치를 저장한다.

grid는 grid[x][y] 방식으로 접근하므로 _x는 첫 번째 인덱스, _y는 두 번째 인덱스를 의미한다.

외부에서 바로 접근하지 않고 getter/setter로 읽고/설정하도록 구성되어 있다.

 

_hp (플레이어 체력)

 

플레이어의 체력을 저장한다.

체력은 항상 최대 3 기준이며, 내부적으로는 0~3 범위로 제한된다.

피격 이벤트 발생 시 t_damage()로 1 감소한다.

dead()는 _hp가 0인지 검사해 게임 오버 판정에 사용된다.

 

_attack_requested (공격 요청 플래그)

플레이어가 R 키를 눌렀는지 기록하는 플래그이다.

handle()에서 R 입력이 들어오면 True로 설정된다.

실제 공격 처리(대상 확인, 몬스터 제거 등)는 skill 모듈에서 수행하며, 처리가 끝난 뒤에는 map 또는 skill에서 set_attack_requested(False)로 직접 꺼준다.

이 플래그는 “공격이 발생했다”가 아니라 “공격을 실행해야 한다”를 의미한다.

 

3) 상수(타일 코드) 설명

WALL = 0

벽을 의미한다.

이동하려는 칸이 0이면 플레이어는 그 칸으로 이동할 수 없다.

 

START_TILE = 5

시작 지점을 의미한다.

start(grid)는 맵 전체를 탐색하여 5의 위치를 찾아 플레이어 좌표를 그 위치로 설정한다.

 

EXIT_TILE = 6

도착 지점을 의미한다.

player는 도착 신호를 직접 반환하지 않으며, map이 is_exit(grid)를 호출해 현재 좌표가 도착인지 검사한다.

 

MONSTER_TILE = 12

몬스터를 의미한다.

이동 시 몬스터 칸(12)으로는 이동할 수 없도록 막는다.

공격 대상 탐색 시 인접 8칸에서 12를 찾아 공격 대상으로 반환한다.

 

4) 함수(메서드) 설명

start(grid) -> (x, y)

 

스테이지 시작 시 호출한다.

하는 일은 다음과 같다.

체력을 3으로 초기화한다.

공격 요청 플래그를 False로 초기화한다.

grid 전체에서 시작 타일(5)을 찾아 그 좌표로 플레이어 위치를 설정한다.

반환값은 (x, y) 튜플이며, map은 이 값을 받아 플레이어 좌표를 사용할 수 있다.

 

handle(key, grid) -> (x, y)

매 턴 입력을 처리하는 메인 함수다.

map은 입력을 받은 후 handle()에 전달한다.

입력 규칙은 다음과 같다.

W/A/S/D: move()를 호출해 이동을 시도한다.

R: 공격 요청 플래그를 True로 설정한다. (공격 실행은 skill 담당)

그 외: 아무 것도 하지 않고 무시한다.

반환은 항상 (x, y) 튜플이다.

즉, map은 handle 결과로 좌표만 받도록 고정된다.

 

move(key, grid) -> True/False

이동을 실제로 수행하는 함수다.

key가 W/A/S/D 중 하나이면 이동 방향을 계산한다.

다음 조건이면 이동 실패(False)로 처리한다.

맵 범위를 벗어나는 경우

이동 대상 칸이 벽(0)인 경우

이동 대상 칸이 몬스터(12)인 경우

이동 가능하면 좌표를 갱신하고 True를 반환한다.

 

t_damage() -> hp

피격 이벤트가 발생했을 때 호출한다.

체력을 1 감소시키고, 감소 후 체력 값을 반환한다.

체력은 0 미만으로 내려가지 않는다.

 

dead() -> True/False

게임 오버 판정 함수다.

현재 체력이 0이면 True, 아니면 False를 반환한다.

map은 매 턴 또는 피격 후 dead()를 확인해 게임 오버 처리를 한다.

 

reset(grid) -> (x, y)

게임 오버 후 스테이지 재시작 시 호출한다.

내부 동작은 start(grid)와 동일하다.

반환값도 (x, y)로 통일한다.

render() -> "P"

화면 출력용 표시 문자 반환 함수다.

map이 화면을 그릴 때 플레이어 위치에 "P"를 출력한다.

 

is_exit(grid) -> True/False

현재 플레이어 위치가 도착 지점(6)인지 확인한다.

True면 도착 지점, False면 그 외 칸이다.

스테이지 클리어 처리는 player가 직접 하지 않고, map이 이 함수 결과로 판단한다.

 

get_attack_targets(grid) -> [(x, y), ...]

공격 대상 계산 함수다.

플레이어 기준 인접 8칸(상/하/좌/우 + 대각선 4칸)을 검사한다.

인접 칸 중 몬스터 타일(12)이 있는 좌표만 리스트로 모아 반환한다.

반환된 좌표 리스트는 skill 모듈이 받아서 몬스터를 길로 바꾸거나(직접 grid 수정) 처리를 수행한다.

몬스터가 없으면 빈 리스트를 반환한다.

 

5) getter / setter 목록과 의미

Getter

get_x(), get_y() : 현재 좌표 읽기

get_hp() : 현재 체력 읽기

get_attack_requested() : 공격 요청 플래그 읽기

 

Setter

set_x(x), set_y(y) : 좌표 설정

set_hp(hp) : 체력 설정(0~3으로 자동 제한)

set_attack_requested(True/False) : 공격 요청 플래그 설정

getter/setter를 통해 외부 모듈(map/skill)이 플레이어 상3태를 안정적으로 읽고 변경할 수 있다.



맵 20 * 20(400px)

 

구조: Map(Player(skill), Monster, Item)

 

MAP

스테이지 별로 20 *20 으로 구성된 미로의 시작 지점과 도착지점 구성

 

Player

벽(0)에 부딪히면 상호작용 X, 길(1)로 wasd로 한칸씩 이동

아이템에 닿으면 자동으로 인벤토리에 저장, 사용 가능

skill(r)로 몬스터와 마주쳤을 때 공격 가능

플레이어 기준 주변 8칸(대각선 포함)까지 공격 범위

체력은 3으로 1번 피격마다 1씩 감소

 

Skill

player가 r입력시 동작

주변 8칸 내 monster가 있다면 monster의 체력 1 감소

주변에 monster가 없다면 자동으로 입력 안함 

 

Monster

player가 다가왔을 때 일정 확률로 공격

스테이지마다 체력 증가

플레이어의 길 막기

피격 범위는 player와 같음

 

Item

H: 체력 +1(한 스테이지 적용)

B: 보호막 +1(일회용)

A: 공격력 +1(일회용)


메모

오늘은 추상화 + 맵 코드(박선준 님) 보고 기본적인 함수 구현까지

skill 담당 조혜림 님에게 함수 이름 전달해서 r 상호작용 잘 되나 확인해야 함

 

getter, setter 사용

wasd는 return 1, 2, 3, 4형식으로 반환, map 말고

입력은 main인 map에서 하고, player는 들어온 입력으로 이동이랑 상호작용

 

플레이어가 벽 너머로 공격할 수 없도록 피격 범위는 애초에 주변 8칸이라 예외 필요없음

플레이어가 아이템 먹을 경우 사라지는 구현?

공격 당했을 경우 피 감소, 아이템으로 피 회복 구현?

 

map(main)에는 x, y 좌표값만 반환 -> 나머지는 player 내에서 아니면 다른 모듈이랑?