본문 바로가기
Learning-log/Algorithm 문풀

(Java) SWEA 5650. 핀볼 게임

by why제곱 2023. 4. 5.

1. 문제 조건

어릴 때 하던 핀볼 게임을 코드로 구현해야 하는 문제.

핀볼 게임 판이 테스트케이스로 주어지며, 각 번호마다 핀볼 판의 상태를 나타내준다.

0번은 아무 블록도, 홀도 없는 상태

1번 ~ 5번은 각 번호에 해당되는 블록이 존재하며 블록에 모양에 따라 핀볼이 꺾이는 방향이 달라진다.(대각선 부분은 90도로, 아닌 부분은 반대방향으로 핀볼이 튕겨나가게 됨)

6번 ~ 10번은 나온다면 웜홀로 항상 두 개씩 나오며 같은 번호인 웜홀로 핀볼의 방향은 유지한 채 이동하게 된다.

-1번은 블랙홀이며 블랙홀을 지나가면 핀볼이 빨려들어감으로써 게임이 끝나게 된다.

 

게임이 끝나는 조건은 처음 위치에 핀볼이 되돌아오거나 핀볼이 빨려들어가거나 두가지이다. (명심할 것)

또한, 핀볼은 상하좌우 이렇게 네 방향으로만 이동 가능하다.

 

위 조건에서 핀볼게임이 진행될 때, 가장 많은 점수를 획득할 수 있는 경우의 점수를 출력해야 한다.

점수는 벽에 부딪히거나, 1번부터 5번에 해당되는 블록에 부딪히는 경우 1점씩 증가하며 웜홀이나 블랙홀은 점수가 부여되지 않는다.

 

2. 아이디어

우선, 어느 위치에서 최대 점수가 나올지 모르므로 블록, 웜홀, 블랙홀이 없는 즉, 핀볼 게임판에서 번호가 0인 모든 지점에서 핀볼게임을 해보고 점수의 최댓값을 찾아가야겠다고 생각했다.

또한 핀볼이 나아갈 수 있는 방향은 상하좌우이므로 이를 방향벡터로 만들어 핀볼을 이동시키고, 블록을 만날 때 꺾이는 방향은 1번~5번을 만나고 핀볼 방향이 상하좌우일 때 각각 어떻게 방향이 바뀌는 지를 배열로 미리 작성해두어 활용하려고 생각했다.(예전 백준의 주사위 게임 문제에서 써보고 착안한 아이디어였다.)

핀볼을 만날 때 꺾이는 방향을 배열로 만들어 두는 것만 생각하면 나머지는 생각한 대로 구현하다보면 구현이 되던 문제였던 것 같다. 

 

3. 구현

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class 핀볼2 {

	    static int[] dr = {-1, 0, 1, 0}; //상, 좌 , 하, 우
	    static int[] dc = {0, -1, 0, 1};
	    static int N;
	    static int[][] map;
	    static int stRow;
	    static int stColumn;
	    static boolean flag;
	    //1번부터 5번 블록을 만났을 때 방향의 변화 시켜주는 배열
	    //상좌하우일 때
	    static int[][] block = {
	            {2,0,3,1}, {3,2,0,1}, {1,3,0,2}, {2,3,1,0}, {2,3,0,1}};    
	    
	    
	    static int score;

	    public static void main(String[] args) throws NumberFormatException, IOException {
	        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	        StringTokenizer st;
	
	        int T = Integer.parseInt(br.readLine().trim());
	        
	        for(int t=1; t<=T; t++) {
	            N = Integer.parseInt(br.readLine().trim());
	            map = new int[N][N];
	            
	            for(int r=0; r<N; r++) {
	            	st = new StringTokenizer(br.readLine().trim());
	                for(int c=0; c<N; c++) {
	                    map[r][c] = Integer.parseInt(st.nextToken());
	                }
	            }
	            
	            score = 0;
	            int max = 0;
	            for(int r=0; r<N; r++) {
	              outer: for(int c=0; c<N; c++) {
	                    for(int dir=0; dir<4; dir++) {
	                    	flag = false;
	                    	if(map[r][c]!=0) continue outer;
	                        stRow = r;
	                        stColumn = c;
	                        play(r, c, dir);
	                        if(score > max) max = score;
	                        score = 0;
	                    }
	                }
	            }
	        
	            
	            System.out.println("#"+t+" "+max);
	        
	        

	        }
	    }
	    
	    static void play(int r, int c, int dir) {
	        if(flag && r==stRow && c == stColumn) return;
	        flag = true;
	        //벽에 막히면 반대방향으로 가기
	        if(r >= N || r <0 || c>=N || c<0  ) {
	            dir=(dir+2)%4;
	            score++;
	        }
	        //블록을 만나면 방향 바꾸기
	        else if(map[r][c]>= 1 && map[r][c]<=5) {
	            dir = block[map[r][c]-1][dir];
	            score ++;
	        }
	        
	        else if(map[r][c] >=6 && map[r][c]<=10){
	            int[] hole = findHole(map[r][c], r, c);
	            r = hole[0];
	            c= hole[1];
	            
	        }else if(map[r][c]==-1) return;
	        int du = dr[dir];
	        int dv = dc[dir];
	        play(r+du, c+dv, dir);
	            
	    }
	    

	    static int[] findHole(int num, int r, int c) {
	        int[] result = new int[2];
	        for(int i=0; i<N; i++) {
	            for(int j=0; j<N; j++) {
	                if(map[i][j]==num && (i!=r || j!=c)) {
	                    result[0] = i;
	                    result[1] = j;
	                }
	            }
	        }
	        
	        return result;
	    }
	}

이 문제에서 시행착오를 겪었던 부분은 크게 두 부분이 있는데, 하나는 play메서드 내에서 int du = dr[dir], int dv = dc[dir]을 정의하는 위치에 대한 부분과, 다른 하나는 findHole 메서드의 조건 부분이었다.

play메서드에서 dir을 바꾼 후 정의해줘야 내가 바꾼 방향으로 핀볼이 이동할 수 있는데 이 부분에서 시행착오를 겪었었다.

두번째로는 내가 기존에 들어간 웜홀을 제외하기 위해서 처음에 (i != r && j != c) 조건을 사용했는데, 그렇게 되면 두 웜홀이 같은 행이나 같은 열에 존재하는 경우 나와 다른 웜홀을 잡지 못 해 결과를 제대로 반환하지 못하는 문제가 생긴다. 반드시 || 조건을 사용해야 한다.

또한 상하좌우 순이 아닌 방향벡터를 상좌하우 순으로 쓴 이유는 반대방향을 표현할 때 (dir+2)%4를 사용하기 위해서였다.