로보테크AI

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

steezer 2026. 1. 8. 17:50

프로젝트 코드 수정

main

import msvcrt
import os
import random
from operator import is_not_none

from game_package import map_module
from game_package import player
from game_package import monster
from game_package import item

char_x = 4
char_y = 4
is_not_dead = True

#스테이지 파일 절대경로
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 오른쪽에 표시될 게임 메뉴얼
MANUAL_LINES = [
    "🎮 GAME MANUAL 🎮",
    "",
    "이동 : W A S D",
    "",
    "공격 : R (주변 8칸)",
    "",
    "",
    "꙰ 아이템 🗲",
    "",
    "✞ : HP +1 (최대 3)",
    "",
    "🗲 : 다음 공격력 2 (1회)",
    "",
    " ꙰ : 몬스터 공격 1회 무효",
]



def draw_with_hp():
    os.system("cls")

    # HP 표시
    hp = player_.get_hp()
    max_hp = player_.max_hp
    hearts = "♥ " * hp + "♡ " * (max_hp - hp)
    print(f"HP: {hearts}")

    # 버프 표시 (A / B)
    buffs = []
    if hasattr(player_, "buff_attack") and player_.buff_attack:
        buffs.append("🗲")
    if hasattr(player_, "buff_block") and player_.buff_block:
        buffs.append("꙰")

    print("BUFF:", " ".join(buffs) if buffs else "-")
    print()
    # 스테이지 정보
    print(f"STAGE : {current_stage_index + 1} / {len(stage_files)}")
    print()

    map_.draw_map(MANUAL_LINES) # 기존 map_.draw_map 대신 draw_with_hp() -> 체력 표시 반영


def start():
    draw_with_hp()

def load_stage(stage_path):
    global map_, monsters_, player_

    # 맵 로드
    map_ = map_module.Map(stage_path)

    # 아이템 시스템 갱신
    global item_system
    item_system = item.Item(map_.get_map_array())

    # 몬스터 초기화
    monsters_ = []

    # 플레이어 / 몬스터 위치 세팅
    for i in range(20):
        for j in range(20):
            if map_.get_map_array()[i][j] == "5":
                player_.set_position(j, i)

            elif map_.get_map_array()[i][j] == "7":
                m = monster.Monster(3, 1)
                m.set_position(j, i)
                monsters_.append(m)
    draw_with_hp()

def update():

    map_.change_map(0,0,6,1,1,3)
    #print(map_.get_map_array)
    draw_with_hp()

# ---------------- Stage 관리 ----------------
stage_files = [
    os.path.join(BASE_DIR, "stage1.txt"),
    os.path.join(BASE_DIR, "stage2.txt"),
    os.path.join(BASE_DIR, "stage3.txt"),
]
current_stage_index = 0

#Awake--------------------------------------------------------------------------//
#Class 인스턴스 초기화

map_ = map_module.Map(stage_files[current_stage_index])

item_system = item.Item(map_.get_map_array()) # 아이템 처리

player_ = player.Player(3,3,1,False)


monsters_ = []


draw_with_hp()


#Start--------------------------------------------------------------------------//

start()

load_stage(stage_files[current_stage_index])


#Update--------------------------------------------------------------------------//

while is_not_dead:


    if msvcrt.kbhit():
        key = msvcrt.getch()

        # ---------------------------atack----------------------------------------
        if key in (b'r', b'R'):
            attack_positions = player_.get_attack_targets(map_.get_map_array())

            for m in monsters_[:]:#삭제 대비 복사
                if m.get_position() in attack_positions:
                    damage = player_.attack_power #플레이어 공격 +1 ->2 1회성
                    m.take_damage(damage)
                    player_.consume_attack_buff()
                    if m.is_dead():
                        monsters_.remove(m)
                        mx, my = m.get_position()
                        map_.change_map(my, mx, 1, my, mx, 1)

            draw_with_hp()
            continue
        # -----------------------------player------------------------------------------
        origin_x, origin_y = player_.get_position()
        movecode = player_.movement(key, map_.get_map_array())

        if movecode != "f":
            nx, ny = player_.get_position()
            target = map_.get_map_array()[ny][nx]

            # ---------------- Item 처리 ----------------

            # H 아이템 (15): 체력 +1 (최대면 효과 없음), 아이템은 사라짐
            if target == "15":
                player_.heal()
                map_.change_map(origin_y, origin_x, 1, ny, nx, 5)
                draw_with_hp()
                continue

            # A 아이템 (16): 다음 공격력 2, 1회성
            if target == "16":
                player_.attack_power = 2
                player_.buff_attack = True
                map_.change_map(origin_y, origin_x, 1, ny, nx, 5)
                draw_with_hp()
                continue

            # B 아이템 (17): 몬스터 공격 1회 무효
            if target == "17":
                player_.buff_block = True
                map_.change_map(origin_y, origin_x, 1, ny, nx, 5)
                draw_with_hp()
                continue

            # 도착지
            if target == "6":

                # 마지막 스테이지면 게임 클리어
                if current_stage_index == len(stage_files) - 1:
                    print("\n 모든 스테이지를 클리어했습니다!")
                    print("게임 클리어!")
                    os.system("pause")
                    exit(0)

                # 다음 스테이지로 이동
                current_stage_index += 1
                print(f"\n ▶ 다음 스테이지로 이동합니다 ({current_stage_index + 1})")
                os.system("pause")

                load_stage(stage_files[current_stage_index])
                continue

            map_.change_map(origin_y, origin_x, 1, ny, nx, 5)
        #-----------------------------monster------------------------------------------

        for m in monsters_:
            m_origin_position = m.get_position()  # monster origin position
            m_movecode = m.monster_movement(map_.get_map_array())  # monster new position

            if m_movecode != "f":
                 map_.change_map(m_origin_position[1], m_origin_position[0], 1,
                        m.get_position()[1], m.get_position()[0], 7)

        # ---------------------- monster attack ----------------------
        px, py = player_.get_position()

        for m in monsters_:
            mx, my = m.get_position()

            # 플레이어 주변 8칸 이내
            if abs(mx - px) <= 1 and abs(my - py) <= 1:
                # 50% 확률 공격
                if random.random() < 0.5:
                    if player_.consume_block_buff():
                        continue  # 이번 공격 무효

                    player_.t_damage()
                    draw_with_hp()

                    if player_.dead():
                        draw_with_hp()
                        print("\nGAME OVER")
                        os.system("pause")
                        exit(0)
        # -------------------------------------------------------------

        draw_with_hp()

player

# 플레이어 모듈
#
# W/A/S/D 이동, R 공격 요청 플래그 세팅
# 벽/장애물/몬스터 칸 이동 금지
# 피격 시 체력 감소, 체력 0이면 dead()
# 시작 타일(5)에서 시작, 도착 타일(6) 판정
#
# 호환성 포인트
# map_module: grid[x][y] 접근(첫 인덱스가 '행(row)') 규칙 사용
# item.py: player.hp / player.max_hp / player.attack_power / player.defense 속성 사용
#
# 좌표 규칙
# x = row(행), y = col(열)
# grid[x][y]로 타일을 읽음
# w: (x-1,y), s:(x+1,y), a:(x,y-1), d:(x,y+1)
#
# 공격 처리 규칙
# 'r' 입력 시 attack_requested=True만 세팅
# 실제 공격(몬스터 피해/제거)은 skill/monster 모듈이 처리
# 공격 처리 후 반드시 player.set_attack_requested(False)로 리셋


class Player :

    x = 0
    y = 0
    # 유효 이동 키
    MOVE_CODE = {"w": 0, "a": 1, "s": 2, "d": 3}

    def __init__(self, max_hp_ : int, hp_ : int, attack_count_ : int, defense_ : bool):

        # item.py가 직접 접근하는 필드들
        self.max_hp = max_hp_ # 3
        self.hp = hp_ # 3
        self.attack_count = attack_count_ # 1
        self.defense = defense_ # False

        # 'r' 입력 시 True
        self._attack_requested = False

        self.attack_power = 1 #기본 공격력
        self.buff_attack = False #A 아이템 상태
        self.buff_block = False #B 아이템 상태

    def set_position(self, x, y):
        self.x = x
        self.y = y

    def get_position(self):
        return self.x, self.y

    def get_hp(self):
        return int(self.hp)

    def set_hp(self, hp):
        # hp는 0~max_hp로 제한
        hp = int(hp)
        if hp < 0:
            hp = 0
        if hp > int(self.max_hp):
            hp = int(self.max_hp)
        self.hp = hp

    def get_attack_requested(self):
        return bool(self._attack_requested)

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

    def movement(self, key: bytes, grid: list) -> str:
        walk_able = ("1", "6", "15", "16", "17")

        if key == b'w':
            if self.y - 1 >= 0 and grid[self.y - 1][self.x] in walk_able:
                self.y -= 1
                return "w"

        elif key == b'a':
            if self.x - 1 >= 0 and grid[self.y][self.x - 1] in walk_able:
                self.x -= 1
                return "a"

        elif key == b's':
            if self.y + 1 < 20 and grid[self.y + 1][self.x] in walk_able:
                self.y += 1
                return "s"

        elif key == b'd':
            if self.x + 1 < 20 and grid[self.y][self.x + 1] in walk_able:
                self.x += 1
                return "d"

        return "f"

    def t_damage(self):
        # 피격: hp 1 감소(최소 0)
        self.set_hp(self.get_hp() - 1)
        return self.get_hp()

    def dead(self):
        # 사망: hp <= 0
        return self.get_hp() <= 0

    def reset(self, grid):
        # 현재는 start와 동일(스테이지 시작 상태)
        return self.start(grid)

    def render(self):
        # 출력용 문자(오버레이 출력 시 사용 가능)
        return "P"

    def is_exit(self, grid):
        # 현재 칸이 도착 타일(6)인지 확인
        g = self._normalize_grid(grid)
        return self._tile(g, self.get_x(), self.get_y()) == self.EXIT_TILE

    def get_attack_targets(self, grid):
        # 인접 8칸(대각 포함) 중 몬스터(12) 좌표만 반환
        g = self._normalize_grid(grid)
        targets = []
        x0, y0 = self.get_x(), 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(g, x, y):
                    continue

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

        return targets

    def _normalize_key(self, key):
        # key가 bytes/str/None 어떤 형태로 와도 소문자 문자열로 통일
        if key is None:
            return ""
        if isinstance(key, (bytes, bytearray)):
            try:
                key = key.decode("utf-8", errors="ignore")
            except Exception:
                return ""
        return str(key).lower().strip()

    def _normalize_grid(self, grid):
        # 2D 리스트 또는 Map 객체(get_map_array)를 2D 리스트로 통일
        # Map.get_map_array가 "프로퍼티"면 그대로 값이 나오고,
        # "함수"면 callable이므로 아래에서 호출됨
        if hasattr(grid, "get_map_array"):
            grid = getattr(grid, "get_map_array")
        if callable(grid):
            grid = grid()

        if (not isinstance(grid, list)) or (not grid) or (not isinstance(grid[0], list)):
            raise ValueError("grid must be a 2D list or a Map-like object with get_map_array")
        return grid

    def _tile(self, grid, x, y):
        # grid 내부가 "5" 같은 문자열이어도 int로 변환해 반환
        return int(grid[x][y])

    def _in_bounds(self, grid, x, y):
        # grid 범위 안인지 확인
        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):
        # 특정 타일 코드(예: START_TILE=5) 위치 탐색, 못 찾으면 (0,0)
        w = len(grid)
        h = len(grid[0]) if w > 0 else 0
        for x in range(w):
            for y in range(h):
                if int(grid[x][y]) == int(tile):
                    return (x, y)
        return (0, 0)

    def _dir_delta(self, key):
        # 좌표 규칙: x=row, y=col
        if key == "w":
            return (-1, 0)
        if key == "s":
            return (1, 0)
        if key == "a":
            return (0, -1)
        if key == "d":
            return (0, 1)
        return (0, 0)

    def get_attack_targets(self, grid):
        # 주변 8칸 좌표를 리스트로 반환 (유효 범위만)
        targets = []
        for dy in (-1, 0, 1):
            for dx in (-1, 0, 1):
                if dx == 0 and dy == 0:
                    continue  # 자기 자신 제외
                nx, ny = self.x + dx, self.y + dy
                if 0 <= ny < len(grid) and 0 <= nx < len(grid[0]):
                    targets.append((nx, ny))
        return targets

    def heal(self): # 회복 +1
        if self.hp < self.max_hp:
            self.hp += 1
            return True
        return False

    def consume_attack_buff(self): # 공격 아이템
        if self.buff_attack:
            self.buff_attack = False
            self.attack_power = 1

    def consume_block_buff(self): # 방어 아이템
        if self.buff_block:
            self.buff_block = False
            return True
        return False

 

아이템

import random

#아이템 코드 정의
item_heal=15    #H
item_attack=16  #A
item_defense=17 #B

item_symbol={item_heal:'H', item_attack:'A', item_defense:'B'}

#플레이어 상태(참조)
class Player_state:
    def __init__(self):
        self.hp=3
        self.max_hp=3
        self.attack_power=1
        self.defense=False

class Item:
    def __init__(self, map_data):
        self.map_data = map_data
        self.items={}

    #아이템 생성
    def item_create(self, item_position):
        item_type=[item_heal, item_attack, item_defense]

        for y,x in item_position:
            if self.map_data[y][x]==1:
                item=random.choice(item_type)
                self.items[(y,x)]=item
                self.map_data[y][x]=item

    #플레이어 아이템 습득 체크(플레이어 위치에 아이템이 존재)
    def check_pickup(self, player_y, player_x, player):
        pos=(player_y, player_x)
        if pos not in self.items:   #플레이어의 위치에 아이템이 존재하지 않을 시
            return

        item=self.items[pos]
        if item==item_heal:
            self.apply_heal(player)
        elif item==item_attack:
            self.apply_attack(player)
        elif item==item_defense:
            self.apply_defense(player)

        #습득시 아이템 제거
        del self.items[pos]
        self.map_data[player_y][player_x]=1

    #회복아이템(item_heal, H, 15)
    def apply_heal(self, player):
        if player.hp<player.max_hp:
            player.hp+=1

    #공격아이템(item_attack, A, 16)
    def apply_attack(self, player):
        player.attack_power=2
        player.buff_attack =True #공격 1회 후 자동 복구 + 중첩 불가

    #방어아이템(item_defense, B, 17)
    def apply_defense(self, player):
        player.buff_block=True

    #공격아이템 일회성
    def attack_buff(self, player):
        attacks=player.attack_power     #현재 공격횟수 저장/기본:1, 아이템:2
        player.attack_power=1   #아이템효과 초기화
        return attacks

    #방어아이템 일회성
    def defense_buff(self, player):
        if player.defense:   #플레이어에게 방어막이 있는지 확인
            player.defense=False #방어막 소모
            return True     #이번 데미지는 무효처리
        return False    #정상 데미지 처리

    def get_item_symbol(self, y, x):
        if (y,x) in self.items:     #해당 좌표에 아이템 존재 여부 확인
            return item_symbol[self.items[(y,x)]]
        return None     #아이템이 없으면 출력x

 

몬스터

import random

class Monster:

    x = 0
    y = 0
    hp = 0
    damage = 0

    def __init__(self, hp_ : int, damage_ : int) a-> None:
        self.x = 0
        self.y = 0
        self.hp = hp_  # 3
        self.damage = damage_

    def set_position(self, x, y):
        self.x = x
        self.y = y

    def get_position(self):
        return self.x, self.y

    def get_hp(self):
        return int(self.hp)

    def take_damage(self, amount: int):
        self.hp -= amount

    def is_dead(self):
        return self.hp <= 0


    def monster_movement(self, grid : list) -> str:

        direction_int =  random.randint(0, 4)

        # [ 0 , 위 ] [ 1 , 아래] [ 2 , 좌 ] [ 3 , 우 ]

        if direction_int == 0:
            if grid[self.y - 1][self.x] == "1" and self.y - 1 >= 0:
                self.y -= 1
                return "w"

        elif direction_int == 1:
            if grid[self.y + 1][self.x] == "1" and self.y + 1 < 20:
                self.y += 1
                return "s"

        elif direction_int == 2:
            if grid[self.y][self.x - 1] == "1" and self.x - 1 >= 0:
                self.x -= 1
                return "a"

        elif direction_int == 3:
            if grid[self.y][self.x + 1] == "1" and self.x + 1 < 20:
                self.x += 1
                return "d"


        return "f"



 

import os

class Map:

    dict_enum = {
        "0": " ■ ",     #Wall
        "1": "   ",     #Road
        "2": " ? ",
        "3": " ? ",
        "4": " ? ",
        "5": " ဝိူ ",     #Player
        "6": " ☆ ",     #Exit
        "7": " 𖢥 ",     #Monster
        "8": " ? ",
        "9": " ? ",
        "10": " P ",
        "11": " ? ",
        "15": " ✞ ",    #Heal
        "16": " 🗲 ",    #Attack
        "17": "  ꙰ "    #Defense
    }


    def __init__(self, file_path: str):
        self.file_path = file_path
        self.array = []        # 인스턴스 변수
        self.monster_num = 0   # 인스턴스 변수

        with open(file_path, "r") as f:
            for line in f:
                line = line.replace(" ", "").replace("\n", "")
                self.array.append(line.split(","))


    def get_map_array(self) -> list:

        return self.array


    def change_map(self, orign_x : int , orign_y : int,
                   change_num : int ,
                   new_x : int, new_y : int, new_num :int ):
        self.array[orign_x][orign_y] = str(change_num)
        self.array[new_x][new_y] = str(new_num)


    def get_monster_num(self) -> int:
        return self.monster_num

    def draw_map(self, manual_lines=None):
        self.monster_num = 0

        for i in range(20):
            row = ""
            for j in range(20):
                if self.array[i][j] == "7":
                    self.monster_num += 1
                row += self.dict_enum[self.array[i][j]]

            # 오른쪽 메뉴얼 붙이기
            if manual_lines and i < len(manual_lines):
                print(row + "   " + manual_lines[i])
            else:
                print(row)

#if __name__ != "__main__":

 

실행 화면

 

 

메모

코드는 전부 구현 완료

내일 오전에 마무리

 

오늘 추가로 더 해볼 것:

1. 새로고침 눈 아파서 화면에서 달라지는 부분만 깜빡이도록 시도

2. 코드 최적화