BOJ 17081

RPG Extreme

문제출처

문제 해석

  • 주어지는 입력대로 시뮬레이션을 진행하여, 최종 상태를 출력하는 문제입니다.
  • NxM차원에서 진행되며, 아래와 같은 것들이 있을 수 있습니다.
    1. 빈칸(.): 자유롭게 입장할 수 있는 칸입니다. 주인공(@)은 처음에 빈칸에서 시작합니다.
    2. 벽(#): 막힌칸입니다. 이 칸으로 움직일 수 없습니다.
    3. 아이템 상자(B)
      • 아이템 상자에는 무기(W),갑옷(A),장신구(O) 중 하나가 들어있습니다.
      • 무기와 갑옷의 경우 해당 장비로 바꾸고, 장신구의 경우 착용할 수 있는 칸이 남았고 동일한 효과의 장신구를 착용하지 않은 경우 해당 장신구를 착용합니다.
      • 아이템 상자가 열리고 나면, 해당 칸은 빈칸이 됩니다.
      • 무기: 한개만 착용가능합니다. 각 무기는 공격력 값을 가지며, 이 값이 주인공의 공격력에 더해집니다.
      • 방어구: 무기와 같습니다.
      • 장신구: 최대 4개까지 착용가능합니다. 각 장신구에는 아래의 효과가 있습니다.
        • HR: 전투에서 승리할 때마다 체력 3을 회복합니다.(최대 체력 수치를 넘지 않습니다)
        • RE: 주인공이 사망했을 때 소멸하며, 최대체력으로 부활시켜준 뒤, 주인공을 첫 시작위치로 돌려보냅니다. 전투중이던 몬스터가 있으면 해당 몬스터의 체력도 최대치로 회복됩니다.
        • CO: 전투시, 첫번째공격에서 주인공의 공격력이 두배로 적용됩니다.
        • EX: 얻는 경험치가 1.2배가 됩니다.
        • DX: 가시함정에 입는 데미지가 1로 고정되고, CO와 함께 착용할 경우 CO의 공격력 효과가 세배로 적용됩니다.
        • HU: 보스몬스터와 전투시, 체력을 최대치까지 회복하고, 보스몬스터의 첫 공격에 0 데미지를 입습니다.
        • CU: 아무 능력이 없습니다.
    4. 가시함정(^)
      • 함정을 밟았을 때 5의 데미지를 입습니다.
      • 함정은 밟아도 사라지지 않습니다.
      • 주인공이 제자리에 있는 경우도 함정을 밟은 것으로 판정이 됩니다.
    5. 몬스터(&)
      • 몬스터는 이름,공,방,체,얻을 수 있는 경험치 값을 갖고 있습니다.
      • 해당 칸에 입장할 경우, 주인공이 선공을 하며, 번갈아가며 공격합니다.
      • max(1,내 공격력 - 상대의 방어력)만큼 데미지를 입힙니다.
      • 한쪽의 체력이 0 이하가 될 경우 전투는 종료됩니다.
      • 몬스터가 사망할 경우, 해당 자리는 빈칸이 됩니다.
    6. 보스(M): 몬스터와 같습니다.
  • 주인공의 정보는 아래와 같습니다.
    • 체력,공격,방어의 초기값은 각 20,2,2입니다.
    • 경험치
      • 초기레벨은 1이며, N->N+1이 되기 위한 필요 경험치는 5xN입니다.
      • 레벨업시, 체력+5, 공+2,방+2가 됩니다.
      • 또한, 현재 경험치는 0으로 초기화됩니다.
  • 게임 결과는 다음과 같습니다.
    • 보스 몬스터를 처치했을 경우: YOU WIN!
    • 죽었을 경우 : YOU HAVE BEEN KILLED BY (몬스터의 이름 or 가시함정)..
    • 입력이 끝났을 경우 : Press any key to continue.

설계(30)

  • 게임을 구현하는 정도로 아주 복잡한 구현력이 요구되는 시뮬레이션 문제입니다.
  • 주인공, 몬스터, 아이템 별 로 체크해야하는 변수들을 생각하는데 오래 걸렸습니다.
  • 구조체를 이용해서, 주인공이 가지고 있어야 할 상태의 변수들을 선언합니다.
  • (현재좌표, 레벨, 현재 체력, 최대 체력, 주인공 공격력, 무기 공격력, 주인공 방어력, 무기 방어력, 현재 경험치, 최대 경험치, 죽었는지 체크할 변수)
  • 2차원 배열을 이용하여 몬스터가 있는 좌표에 (공,방,체,최대체력, 경험치,이름)의 정보를 저장해놓습니다.
  • 마찬가지로 아이템에 있는 좌표에 (아이템종류,능력치,효과)의 정보를 저장해놓습니다.
  • 장신구의 효과를 인덱스로 치환하기 위해서 map자료구조를 사용합니다.
  • 한번 이동하고 주어진 조건대로 실행한 뒤, 상태를 체크해서 알맞게 변환시켜줍니다.

구현(130)

코드
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 102
struct MONSTER{
    int W,A,cur_H,max_H,E;
    string name;
};
struct ITEM{
    string cat;
    int num;
    string eff;
};
map<string,int> mp;
string acc_effect[]={"HR","RE","CO","EX","EX","HU","CU"};
bool finish;
MONSTER monster_arr[MAXN][MAXN];
ITEM item_arr[MAXN][MAXN];
char arr[MAXN][MAXN];
string dirs;
int N,M;
int sx,sy;
int dx[] = {0,0,-1,1};
int dy[] = {-1,1,0,0};
struct STATUS{
    int x,y;
    int level;
    int cur_hp;
    int max_hp;
    int att;
    int w_att;
    int def;
    int a_def;
    int cur_exp,max_exp;
    int death;
    string death_info;
    STATUS(){
       level = 1;
       cur_hp = max_hp = 20;
       att = def = 2;
       max_exp = 5;
       w_att = a_def = cur_exp =death = 0;
    };
    bool acc[8];
    int acc_cnt;
    void check_status(){
        // 전투했을시
        if (arr[x][y]=='&' || arr[x][y] =='M'){
            MONSTER m = monster_arr[x][y];
            // 사망한 경우
            if (cur_hp<=0){
                if (acc[mp["RE"]]){
                    acc[mp["RE"]]=false;
                    acc_cnt--;
                    x = sx;
                    y = sy;
                    m.cur_H = m.max_H;
                    cur_hp = max_hp;
                }
                else {
                    cur_hp = 0;
                    finish = true;
                    death = 1;
                    death_info = m.name;
                }
            }
            // 몬스터가 죽은 경우
            else{
                if (acc[mp["EX"]]) cur_exp +=1.2*m.E;
                else cur_exp += m.E;
                if (arr[x][y] =='M'){
                    finish = true;
                }
                arr[x][y] = '.';
                if (acc[mp["HR"]]){
                    cur_hp = min(cur_hp+3,max_hp);
                }
            }
        }
        // 가시함정
        else if (arr[x][y] == '^'){
            if (cur_hp <=0){
                if (acc[mp["RE"]]){
                    acc[mp["RE"]] = false;
                    acc_cnt--;
                    x = sx;
                    y = sy;
                    cur_hp = max_hp;
                }
                else {
                    cur_hp = 0;
                    finish = true;
                    death = 1;
                    death_info = "SPIKE TRAP";
                }
            }
        }
        else if (arr[x][y] =='B') arr[x][y]='.';
        // 레벨업 시
        if (cur_exp >= max_exp){
            level++;
            cur_exp = 0;
            max_exp = level*5;
            max_hp +=5;
            att +=2;
            def += 2;
            cur_hp = max_hp;
        }

    }
};
STATUS ch;


bool is_range(int x, int y){
    return (x>=0 && y>=0 && x <N && y <M);
}
void fight(MONSTER m,int mode){
    int idx = 0;
    if (mode == 1 && ch.acc[mp["HU"]]) ch.cur_hp = ch.max_hp;
    while (!(ch.cur_hp<=0 || m.cur_H <=0)){
        if (!idx&& ch.acc[mp["CO"]]){
            if (ch.acc[mp["DX"]]) {
                m.cur_H-= max(1,(ch.att+ch.w_att)*3-m.A);
            }
            else m.cur_H -= max(1,(ch.att+ch.w_att)*2-m.A);
        }
        else{
            if (idx%2==0){
                m.cur_H -= max(1,(ch.att+ch.w_att)-m.A);
            }
            else{
                if (!(mode==1 && idx==1 && ch.acc[mp["HU"]])) ch.cur_hp -= max(1,(m.W-(ch.def+ch.a_def)));
            }
        }
        idx++;
    }
}
void equip(ITEM item){
    if (item.cat == "A"){
        ch.a_def = item.num;
    }
    else if (item.cat == "W"){
        ch.w_att = item.num;
    }
    else{
        if (ch.acc_cnt>=4 || ch.acc[mp[item.eff]]) return;
        ch.acc[mp[item.eff]] = true;
        ch.acc_cnt++;
    }
}
void go(int dir){
    int nx = ch.x + dx[dir];
    int ny = ch.y + dy[dir];
    if (!is_range(nx,ny)|| arr[nx][ny]=='#') nx = ch.x,ny=ch.y;
    // 몬스터 만나는 경우
    if (arr[nx][ny]=='&'){
        fight(monster_arr[nx][ny],0);
    }
    // 보스 만나는 경우
    else if (arr[nx][ny] =='M'){
        fight(monster_arr[nx][ny],1);
    }
    // 아이템 상자 만나는 경우
    else if (arr[nx][ny] == 'B'){
        equip(item_arr[nx][ny]);
    }
    // 가시함정 만나는 경우
    else if (arr[nx][ny] == '^'){
        if (ch.acc[mp["DX"]]) ch.cur_hp--;
        else ch.cur_hp-=5;
    }
    ch.x = nx;
    ch.y = ny;
    ch.check_status();
}
int main(){
    ios::sync_with_stdio(false);
    for (int i = 0 ;i < 7; i++){
        mp[acc_effect[i]] = i+1;
    }
    cin >> N >> M;
    int m_cnt = 0;
    int b_cnt = 0;
    for (int i = 0;i<N ;i++){
        cin >> arr[i];
        for (int j =0 ; j < M; j++){
            if (arr[i][j]=='@'){
                ch.x = i;
                ch.y = j;
                sx = i;
                sy = j;
                arr[i][j] = '.';
            }
            else if (arr[i][j] == '&'|| arr[i][j] =='M'){
                m_cnt++;
            }
            else if (arr[i][j] =='B') b_cnt++;
        }
    }
    cin >> dirs;
    while (m_cnt--){
        int x,y,W,A,H,E;
        string name;
        cin >> x >> y >> name >> W >> A >> H >> E;
        x--;
        y--;
        monster_arr[x][y]={W,A,H,H,E,name};
    }
    while (b_cnt--){
        int x,y;
        string cat;
        cin >> x >> y >> cat;
        x--;
        y--;
        if (cat=="W" || cat == "A"){
            int num;
            cin >> num;
            item_arr[x][y]={cat,num,""};
        }
        else{
            string eff;
            cin >> eff;
            item_arr[x][y]={cat,0,eff};
        }
    }
    int i;
    for (i = 0 ; i < dirs.size()&&!finish; i++){
        int dir;
        if (dirs[i] == 'L') dir = 0;
        else if (dirs[i] =='R') dir = 1;
        else if (dirs[i] == 'U') dir=2;
        else dir = 3;
        go(dir);
    }
    if (!(finish &&ch.death)) arr[ch.x][ch.y] = '@';
    for (int j = 0;j< N; j++){
        for (int k = 0 ;k<M; k++){
            cout << arr[j][k] ;
        }
        cout <<"\n";
    }
    cout << "Passed Turns : "<< i <<"\n";
    cout << "LV : " << ch.level<<"\n";
    cout << "HP : " << ch.cur_hp <<"/"<<ch.max_hp<<"\n";
    cout << "ATT : " << ch.att <<"+"<<ch.w_att<<"\n";
    cout << "DEF : " << ch.def <<"+" << ch.a_def <<"\n";
    cout << "EXP : " << ch.cur_exp <<"/"<<ch.max_exp<<"\n";

    if (!finish){
        cout << "Press any key to continue.";
    }
    else{
        if (ch.death){
            cout << "YOU HAVE BEEN KILLED BY " << ch.death_info <<"..";
        }
        else{
            cout <<"YOU WIN!";
        }
    }
    return 0;

}

디버깅
  • HR효과를 구현할 때, max(현재체력+3,최대체력)으로 잘못 구현하는 실수를 하였습니다. -> min(현재체력+3,최대체력)
  • RE효과를 구현할 때, 전투중이던 몬스터의 상태를 체크해야하는 것을 고려하지 않았다가 나중에 수정하였습니다.
  • 주인공이 사망 시, 0으로 초기화해주지 않는 실수를 하였습니다.(음수가 출력됨)
  • map자료구조에 장신구 효과를 인덱싱할때, 인덱스 번호 0부터 시작해서 0 인덱스가 무조건 true로 되어있는 오류를 발견하였습니다. -> 인덱스가 1부터 시작하게 수정하였습니다.
제출결과
  • 0 ms

마무리

  • 정말 하라는 데로만 하되, 주어진 설명에서 언급하지는 않았지만 체크되어야 할 것들을 잘 체크해주면 구현이 가능한 문제였습니다.
  • 그럼에도 불구하고, 구현할 것이 너무 많고 복잡하여 구현을 하는데 오랜 시간이 걸렸습니다.
  • 구현력을 기르기에 아주 좋은 문제라고 생각합니다.