반응형

백준 11559 Puyo Puyo

https://www.acmicpc.net/problem/11559

 

11559번: Puyo Puyo

총 12개의 줄에 필드의 정보가 주어지며, 각 줄에는 6개의 문자가 있다. 이때 .은 빈공간이고 .이 아닌것은 각각의 색깔의 뿌요를 나타낸다. R은 빨강, G는 초록, B는 파랑, P는 보라, Y는 노랑이다.

www.acmicpc.net

Comment
기본적인 탐색 + 구현 시뮬레이션 문제
확실히 구현이 들어가니까 실수하는게 생기고 실수했을 때 원인찾기가 어렵다. 지금은 디버깅으로 가능하지만 코테에서는 신경을 많이 써서 해야할 듯?


Hint

  • 기본적인 BFS이동을 담을 movePoint
  • 방문체크 visited
  • 게임판 puyoMap
  • 연쇄횟수
  • 매 라운드가 있어야하기에 puyoClear(boolean)
  • 연쇄가 발생했을 때 뿌요를 지울 ArrayList

아직 깔끔하게 구현은 못하겠지만 어느걸 써야할지는 계속 생각하며 풀어야 함


Solution
1. 탐색하고 탐색된 좌표 저장
2. 탐색된 좌표가 4개 이상일 때 puyoClear = true, 실제로 지울 clearPuyoMap List에 추가
3. clearPuyoMap 기반으로 뿌요들을 아래로 내려줌
4. 한번 지워진 상태(puyoClear == true)라면 1~3 반복

큰 흐름은 위의 순서로 진행된다.
값 초기화 잘 신경써서 해주면 크게 어렵지는 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package Baekjoon.gold;
 
import java.io.*;
import java.util.*;
 
public class p11559 {
    static final int[][] movePoint = {{10}, {-10}, {01}, {0-1}};
    static boolean[][] visited = new boolean[12][6];
    static char[][] puyoMap = new char[12][6];
    static int count = 0;
    static int n = 12;
    static boolean puyoClear = false;
    static ArrayList<int[]> accumulatePuyoMap = new ArrayList<>();
    static ArrayList<int[]> clearPuyoMap = new ArrayList<>();
 
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < n; i++) {
            puyoMap[i] = br.readLine().toCharArray();
        }
 
        do {
            puyoClear = false;
            puyoGame();
            if (puyoClear) {
                count++;
                puyoMove();
            }
            clearPuyoMap.clear();
            visited = new boolean[12][6];
        } while (puyoClear);
        System.out.println(count);
 
    }
 
    static void puyoGame() {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < 6; j++) {
                if (puyoMap[i][j] != '.' && !visited[i][j]) {
                    bfs(i, j, puyoMap[i][j]);
                }
            }
        }
    }
 
    static void bfs(int x, int y, char puyoColor) {
        Queue<int[]> q = new LinkedList<>();
        visited[x][y] = true;
        q.add(new int[]{x, y});
        accumulatePuyoMap.clear();
        accumulatePuyoMap.add(new int[]{x, y});
 
        while (!q.isEmpty()) {
            int[] nowXY = q.poll();
            for (int[] moveXY : movePoint) {
                int nowX = nowXY[0+ moveXY[0];
                int nowY = nowXY[1+ moveXY[1];
                if (nowX >= 0 && nowY >= 0 &&
                        nowX < 12 && nowY < 6 &&
                        puyoColor == puyoMap[nowX][nowY] &&
                        !visited[nowX][nowY]) {
                    visited[nowX][nowY] = true;
                    q.add(new int[]{nowX, nowY});
                    accumulatePuyoMap.add(new int[]{nowX, nowY});
                }
            }
        }
        if (accumulatePuyoMap.size() >= 4) {
            puyoClear = true;
            clearPuyoMap.addAll(accumulatePuyoMap);
        }
    }
 
    static void puyoMove() {
        //4개 이상인 뿌요를 .으로 초기화
        for (int[] clearXY : clearPuyoMap) {
            puyoMap[clearXY[0]][clearXY[1]] = '.';
        }
 
        //.을 탐색하며 뿌요를 위로 올려줌
        int[] firstDotPlace = new int[2];
        for (int i = 0; i < 6; i++) {
            boolean firstDotCheck = false;
            Queue<Character> upMovePuyo = new LinkedList<>();
            for (int j = 11; j >= 0; j--) {
                if (puyoMap[j][i] == '.') {
                    if (firstDotCheck) {
                        continue;
                    }
                    firstDotPlace[0= j;
                    firstDotPlace[1= i;
                    firstDotCheck = true;
                } else if (firstDotCheck) {
                    upMovePuyo.add(puyoMap[j][i]);
                    puyoMap[j][i] = '.';
                }
            }
            int nowX = firstDotPlace[0];
            int nowY = firstDotPlace[1];
            while (!upMovePuyo.isEmpty()) {
                puyoMap[nowX][nowY] = upMovePuyo.poll();
                nowX--;
            }
        }
    }
}
 
cs

 

반응형
반응형

https://www.acmicpc.net/problem/2310

2310번 어드벤처 게임

 

2310번: 어드벤처 게임

입력은 여러 개의 미로로 주어진다. 각 미로의 첫 줄에는 미로의 방 수를 나타내는 정수 n(1 ≤ n ≤ 1000)이 주어진다. 다음 n 줄에는 각 방의 정보가 주어진다. 각 방의 정보는 방의 내용물을 나타

www.acmicpc.net


Comment
문제 이해하는데 한참 걸렸던 문제 생각보다 구현은 엄청 어렵지는 않았다.
문제 이해를 돕자면 각 줄이 1, 2, 3, 4번 방으로 쭉 나열되는 것이고, E,L,T의 상태를 가진 방 으로 생각하면 편할 것 같다.
다들 쉽게 이해하겠지만 저는 좀 힘들었습니다..


Hint

  • 기본적인 방의 번호와 상태를 담은 mapList
  • 방문을 확인할 배열 visited
  • n번방에 도달했는지 확인하는 success

Solution
문제 이해하기까지도 오래 걸리고, 아직 문해력이 부족한건지 모호했던 부분이 많았던 문제 그래도 직접 해결해서 뿌듯하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package Baekjoon.gold;
 
import java.io.*;
import java.util.*;
 
 
/**
 * 3
 * E 0 2 0
 * L 10 3 0
 * T 15 1 2 0
 *
 * E 빈방, L 레프리콘, T 트롤
 * 두번째 0, 10, 15 금액
 * 레프리콘은 10원 기준
 * 트롤은 15원 기준
 *
 * 1번방 출발 빈방시작
 * 이후의 값은 다 다른 방과 이어지는 번호
 *
 * 처음 모험가가 시작하는 방 번호 1
 *
 * 연결리스트로 추가하기
 *
 */
public class p2310 {
 
    static ArrayList<Room> mapList;
    static boolean[] visited;
    static int n;
    static StringBuilder sb = new StringBuilder();
    static boolean flag;
    static boolean success;
 
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
        n = Integer.parseInt(br.readLine());
        while(n > 0){
            mapList = new ArrayList<>();
            visited = new boolean[n+1];
            int myMoney = 0;
            flag = true;
            success = false;
            //0번방의 필요없는 데이터 주입
            mapList.add(new Room(-1,"NOTHING",-1));
            //
 
            for(int roomNumber = 1; roomNumber <= n; roomNumber++) {
                StringTokenizer st = new StringTokenizer(br.readLine());
                Room roomInit = new Room(
                        roomNumber,
                        st.nextToken(),
                        Integer.parseInt(st.nextToken())
                );
 
                int inputNum = Integer.parseInt(st.nextToken());
                while(inputNum > 0){
                    roomInit.roomNumberAdd(inputNum);
                    inputNum = Integer.parseInt(st.nextToken());
                }
                mapList.add(roomInit);
            }
 
            dfs(1, myMoney);
 
            if(!success){
                sb.append("No").append("\n");
            }else{
                sb.append("Yes").append("\n");
            }
            n = Integer.parseInt(br.readLine());
        }
 
        System.out.println(sb);
 
    }
 
    static void dfs(int roomNum, int myMoney){
        if(success){
            return;
        }
        visited[roomNum] = true;
 
        Room nowRoom = mapList.get(roomNum);
        myMoney = nowRoom.visitRoomEvent(myMoney);
        if(myMoney >= 0){
            if(n == roomNum){
                success = true;
                return;
            }
            for(int roomNumber : nowRoom.getLinkedRoomList()){
                if(!visited[roomNumber]) {
                    dfs(roomNumber, myMoney);
                }
            }
        }else{
            visited[roomNum] = false;
            return;
        }
    }
 
    private static class Room{
        int roomNum;
        String roomType;
        int roomMoney;
        final ArrayList<Integer> roomList;
        Room(int roomNumber, String type, int money){
            this.roomMoney = money;
            this.roomNum = roomNumber;
            this.roomList = new ArrayList<>();
            this.roomType = type;
        }
 
        void roomNumberAdd(int number){
            roomList.add(number);
        }
 
        ArrayList<Integer> getLinkedRoomList(){
            return roomList;
        }
 
        int visitRoomEvent(int money){
            if(roomType.equals("L")){
                if(money < this.roomMoney){
                    return this.roomMoney;
                }
            }
            if(roomType.equals("T")){
                if(money < this.roomMoney){
                    return -1;
                }
                money -= roomMoney;
                return money;
            }
            return money;
        }
    }
}
cs

 

반응형
반응형

Chapter07 함께 모으기

코드와 모델을 밀접하게 연관 시키는 것은 코드에 의미를 부여하고 모델을 적절하게 한다.

  • 에릭 에반스 -

마틴 파울러의 객체 지향 설계 세가지 관점

UML Distilled 2판을 통해서 객체 지향 설계에 존재하는 세가지 관점에 관해 설명한다.

  • 개념 관점(Conceptual Perspective)
  • 명세 관점(Specification Perspective)
  • 구현 관점(Implementation Perspective)

개념 관점

설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다. 소프트웨어는 도메인에 존재하는 문제를 해결하기 위해 개발되는데, 이 관점이 사용자가 도메인을 바라보는 관점을 반영한다. 때문에 사용자에 필요한 소프트웨어를 만들기 위해서는 실제 도메인의 규칙과 제약을 최대한 유사하게 반영해야 한다.

명세 관점

사용자의 영역인 도메인을 벗어나 개발자의 영역인 소프트웨어로 초점이 옮겨진다.
명세 관점은 도메인의 개념이 아니라 실제 소프트웨어 안의 객체들의 책임에 초점을 맞춘다. 즉 객체의 인터페이스를 바라보게 된다.

명세 관점은 즉, 프로그래머가 객체가 협력을 위해 ‘무엇’을 할 수 있는가에 초점을 맞춘다.

인터페이스와 구현을 분리하는 것이 훌륭한 객체 지향 설계를 낳는 가장 기본적인 원칙이다.
이 것이 설계의 관점에서는 명세 관점과 구현 관점을 분리하는 것이다.

구현 관점

프로그래머인 우리에게 가장 익숙한 관점으로, 실제 작업을 수행하는 코드와 연관되어 있다.
객체의 책임을 ‘어떻게’ 수행할 것인가 에 초점을 맞추며 인터페이스를 구현하는 데 필요한 속성과 메서드를 클래스에 추가한다.

세 가지 관점의 융합설계

이 세 관점을 통해 개념 → 명세 → 구현 관점 순으로 소프트웨어를 개발하는 것처럼 오해할 수 있는데, 동일한 클래스를 세 가지 다른 방향에서 바라보며 설계하는 것을 의미한다.

클래스는 세 가지 관점이라는 안경을 통해 설계와 관련된 다양한 측면을 드러낼 수 있다. 클래스가 은유하는 개념은 도메인, 공용 인터페이스는 명세, 속성과 메서드는 구현 등과 같이 각각 관점을 반영한다.

이 관점에 맞게 세 가지 관점을 쉽게 식별할 수 있도록 깔끔하게 분리하는 것이 좋은 객체지향 설계를 했다고 볼 수 있다.

커피 전문점 도메인

커피 전문점 예제를 통해 어떤 형식으로 설계하고 구현해나갈지 살펴보기

커피 전문점 세상

커피 전문점을 구성하는 요소들에 관해 생각해보자. 객체 지향 패러다임의 가장 중요한 도구는 객체이므로 커피 전문점을 객체들로 구성된 작은 세상으로 쪼개본다면, 메뉴판, 메뉴판에 있는 커피메뉴가 있을 때 메뉴판 안에 네 개의 메뉴 항목으로 구성돼 있는데 메뉴 항목들 역시 객체로 볼 수 있다. 따라서 메뉴판은 네 개의 메뉴 항목 객체들을 포함하는 객체라고 볼 수 있다. 이를 토대로 도메인 모델을 만들어보자

도메인 모델 설계 과정

image

객체들 간의 관계를 상태와 무관하게 동일한 행동을 하는 객체를 동일한 타입으로 분류

손님 = {손님}
바리스타 = {바리스타}
메뉴판 = {메뉴판}
메뉴 항목 = {카라멜 마키아또, 아메리카노, 카푸치노, 에스프레소}

타입 간의 관계 확인

메뉴판 → 메뉴 항목 은 하나의 단위로 움직이기 때문에 메뉴 항목은 메뉴판 객체에 포함(Containment) 되어있다. 합성(Composition) 으로도 부른다.

바리스타 → 커피

!바리스타와 메뉴판이나 메뉴 항목과 포함 관계가 아닌 것을 유의해야 한다.

image

최종적으로 이렇게 관계 형성까지 마무리가 되면 이것이 도메인 모델이 된다. ⇒ 개념 관점 설계

주의해야 할 점으로는, 실제 도메인 모델을 작성하는 단계에서 어떤 관계가 포함이고 어떤 관계가 연관인지 보다 어떤 타입이 도메인을 구성하는지 파악하는 것이 제대로 도메인을 이해하는 것이다.

설계하고 구현하기

협력 찾기(명세 관점 설계)

협력을 잘 설계하는 것은 메시지가 객체를 선택해야한다.

  1. 시스템💬 : 커피 주문(아메리카노) → 손님
  2. 손님💬 : 메뉴 항목을 찾아라(아메리카노) → 메뉴판
  3. 메뉴판☕(아메리카노) : → 손님 (메시지x)
  4. 손님💬 : 커피를 제조해라(아메리카노) → 바리스타
  5. 바리스타💬 : 커피 생성하라(아메리카노) → 커피
  6. 커피☕(아메리카노) : 생성완료 → 바리스타 (메시지x)
  7. 커피☕(아메리카노) : 커피 제조완료 → 손님(메시지x)

형태로 메시지가 전달되는 객체의 인터페이스가 만들어진다. 객체가 수신한 메시지가 객체의 인터페이스를 결정하는데, 이렇게 협력을 통해 각 객체가 메시지를 주고받는 것을 객체의 인터페이스 라고 한다. 객체의 인터페이스를 통해 명세 관점 설계를 한다.

이를 토대로 만들어진 인터페이스

class Customer {
        public void order(String menuName){}
}

class MenuItem {
}

class Menu {
        public MenyItem choose(String name){}
}

class Barista {
        public Coffee makeCoffee(MenuItem menuItem) {}
}

class Coffee {
        public Coffee(MenuItem menuItem) {}
}

구현하기

각 클래스의 인터페이스를 식별했으므로 오퍼레이션을 수행하는 방법을 메서드로 구현하는 것을 예시로 들자면

class Customer{
        public void order(String menuName, Menu menu, Barista barista){
                MenuItem menuItem = menu.choose(menuName);
                Coffee coffee = barista.makeCoffee(menuItem);
                ...
        }
}

다음과 같이 실제 메시지를 객체전달을 하기 위해 메시지에 객체를 넣어서 참조 문제를 해결하기도 하고, 내부 구현 또한 각 객체에 맞게 구현한다.

결국 구현은 설계와 다를 수밖에 없다. 이를 생각해서 협력을 구상할 때 너무 오랜 시간 쏟는 것이 아니라 코드를 구현 후에 설계가 구현 가능한지, 이상이 없는지 등 확인해봐야 할 것이다.

코드와 세 가지 관점

코드는 세 가지 관점을 모두 제공해야 한다.

  • 개념 관점 : Customer, Menu, MenuItem, Barista, Coffee 클래스
  • 명세 관점 : public 메서드(메시지, 협력이 가능한 공용 인터페이스)
  • 구현 관점 : 클래스 내부의 메서드나 속성 등

개념 관점을 잘 지켜야 직관적으로 어느 로직이 어느 곳에 들어갈지 파악이 쉽고, 명세 관점을 잘 지켜야 변화에 안정적인 설계가 될 수 있고, 구현 관점을 잘 지켜야 외부의 클래스가 알지 못하게 각 객체 내부에서 요구사항이 변경되어도 잘 돌아갈 수 있는 객체 지향 설계가 될 수 있을 것이다.

도메인 개념을 참조하는 이유

도메인 개념을 잘 지켜야 도메인에 대한 지식을 기반으로 코드의 구조와 의미를 쉽게 유추할 수 있기에 유지보수성에 큰 영향을 끼친다.

인터페이스와 구현을 분리하라

명세 관점과 구현 관점이 섞이면 좋은 코드가 아니다.

명세 관점 → 인터페이스

구현 관점 → 내부 구현

명세 관점인 인터페이스를 통해 내부 구현을 숨기고, 구현 관점을 통해 구현하라.

반응형

+ Recent posts