20231103_기록_카드 뒤집기 게임

2023. 11. 3. 20:52IT/TIL

오늘 한 것들

 

알고리즘 코드카타 풀이

카드 뒤집기 게임 버그 수정

발표 참가

1시간 만에 정복하는 코딩테스트 합격법 수강하기

LeetCode 문제 풀기

 

 

오늘은 어제 마무리 했었던 카드 뒤집기 게임의 최종 버전을 테스트하면서 발생한 버그를 수정하였다.

 

- 10초 이하에서 화면이 빨갛게 점멸하는 애니메이션 버그이 재시작 시에도 유지되는 버그

게임에서 남은 시간이 10초 이하가 되는 경우 배경음의 속도가 빨라지고 화면이 빨갛게 점멸하는 애니메이션을 구현했는데 배경음의 경우 pitch를 사용하여 속도를 조절했다.

(SoundManager.instance.bgSound.pitch = 1f) 와 (SoundManager.instance.bgSound.pitch = 1.2f)

 

화면이 빨갛게 점멸하는 애니메이션은 유니티의 애니메이터 기능으로 구현했는데

애니메이터에 Bool 값을 대입하여 그 값을 true와 false로 하여 남은 시간이 10초 이하로되면 점멸하는 애니메이션이 작동하게 구현하였다.

(bg.GetComponent<Animator>().SetBool("warning", true)) 와 (bg.GetComponent<Animator>().SetBool("warning", false);)

이 과정에서 하나의 위치가 아닌 여러 위치(Update와 GameOver)에서 사용하려고 하자 오류가 생겨

public bool 값을 지정하고 그 값에 따라서 애니메이터가 true와 false 상태로 변경되게 조정하여 오류를 해결하였다.

 

 

이후에 팀 프로젝트 과제였던 카드 뒤집기 게임의 발표회에 참가하였는데

많은 팀들이 각자의 개성을 살려 게임을 만들었는데, 비슷한 게임이 하나도 없이 각각 다른 게임이 구현된 것이 신기하였다.

팀별로, 팀원별로 중요하다고 생각하는 것이 다르듯이 구현한 전체적인 게임의 틀도, 세부적으로 구현한 기능들도 다 달랐다.

이를 놓치기 쉬웠는데 튜터님들께서 특징으로 잡은 것들을 말씀해주시면서 피드백하는 과정에서 알지 못했던 부분들을 알 수 있었다.

 

개인적으로 아쉬웠던 부분은 게임을 기획하면서 출제자의 의도를 파악하지 못했다고 느끼는 부분인데

과제를 "4주차 강의를 듣고 최대한 비슷하게 게임을 만들되 challenge 부분의 기능을 구현할 수 있으면 추가해라" 라는

느낌으로 이해하고 기본적인 틀을 최대한 유지하려고 노력했었다. (너무 고차원적으로 하지 않도록 주의했다)

하지만 다른 팀들의 발표물은 상상이상으로 차원이 높은 게임들(실제로 앱스토어에 등록할 수 있을 만큼의)이었다.

우리 팀도 잘 만들 수 있는데 문제를 잘못 이해하여 퀄리티를 낮춘 것은 아닌가 하는 아쉬움이 들었다.

 

 

발표 이후에는 약간 붕 뜨는 시간이 생겨서 어떤 것을 할까 고민하다 캠프에서 제공하는 강의인

1시간 만에 정복하는 코딩테스트 합격법 수강하기

를 수강하며 LeetCode와 Github를 연결하고 코딩 테스트를 하기 위한 환경을 구축했다.

 

강의 내용 필기한 TMI

더보기

[무료] 1시간 만에 정복하는 코딩테스트 합격법

코딩 테스트

1. 코딩 테스트가 무엇인지 알고 문제 풀이 환경 구축
2. 최단기간 합격을 위한 올바른 알고리즘 학습법 알아보기
3. 기업별 출제 경향을 파악하고 알고리즘 노트를 만든다

코딩 테스트란
채용을 위한 시험 -> 지원자 수를 줄이기 위한 툴
코딩 테스트 = 기술 역량 + 문제 해결 능력 + 코드 구현 능력

기술 역량 -> 알고리즘 이해도, 자료 구조 이해도, 프로그래밍 언어 이해도
문제 해결 능력 -> 문제를 논리적으로 분석할 수 있는 능력
코드 구현 능력 -> 분석한 내용을 코드로 구현할 수 있는 능력

1.
코딩 테스트 유형
HackerRank(여기가 더 주로 사용됨), LeetCode

코딩 테스트 문제 파악하기
문제 영역 -> 문제에 대한 설명, 예시, 주석 등
코드 작성 칸 -> 문제 풀이 칸
실행 -> run tests, run code -> 테스트, 코드
감독관 -> 감독관이 있으면 대화하거나 확인하는 등의 상태로 시험보는 경우도 있음
constraints - 제약조건
sample input - intput (입력되는 값)
sample output - output (기대되는 결과 값)

2.
기업별 코딩 테스트 유형 분석 -> 가고 싶은 기업의 빈출 문제를 확인하기(족보)
SQL이 출제되는 경우도 존재(데이터베이스에서 데이터를 가져오는 경우)

알고리즘 학습법 1 - 기술 역량
자료 구조 - data structure(데이터를 효율적으로 저장, 사용을 위한 것)(요리로 따지면 재료)
Array/List, Linked List, Stack, Queue, Dequeue, Priority queue, Hash Table, Graph, Tree, Heap
알고리즘- algorithm
Simulation/Implementation(구현), Search, Sort, Greedy(가장 유리한 방법 찾기), Dynamic programming, Dijkstra, Floyd-Warshall, Prim, Kruscal, DFS, BFS(너비우선, 깊이우선 탐색) (볼드체가 우선 순위 높은 것)

알고리즘 학습법 2 - 문제 해결 능력
주어진 제시문을 잘 이해하고, 문제를 분석하여, 해결책을 찾아 내는 능력
-> 풀이를 말로 설명해본다. 논리적으로 빈약한 부분을 찾기도 함.
(주석으로 문제푸는 순서 적어보기 등)
기술 면접에서 알고리즘 풀이를 왜 이런 방식으로 했는지 물어봄 -> 미리 설명하는 풀이를 연습해보면 좋음

★ 문제를 풀다가 막히면 답안을 보는게 좋을까? 문제를 계속 푸는게 좋을까?
-> 제한 시간을 정하고 그 시간이 지나면 답안을 보는게 좋다.
(많은 문제를 보기위해 적은 수의 문제를 계속 잡고 있는 것은 좋지 않다)

알고리즘 학습법 3 - 코드 구현 능력
1) 기본 문법 학습
2) 기초 알고리즘 문제 풀이
3) 기초 문제 풀이
구준히 오래 풀어야 실력이 상승한다!


문제 풀이 비밀 노트
1. LeetCode 회원 가입
2. 알고리즘 포트폴리오 만들기
3. Pycharm IDE 설치하기
4. 나만의 알고리즘 노트 만들기

알고리즘 노트 -> 빈출 문제에 대한 뼈대 코드(이를 응용하여 문제를 풀면 빠르고 정확하게 풀이 가능)

주의점
숨어있는 테스트 케이스(edge case) 조심하기
-> 오답이 나오는 경우가 발생
-> 모든 테스트 케이스를 전부 해결할 수 있게 준비하기

DFS / BFS에서 문제를 주의해서 읽을 것

타임 아웃 조심하기
-> 불필요한 연산 제거 + 반복문 줄이기 + 중복되는 계산 재사용하기(함수 등으로 정리하기)
+ 내장 함수를 이용하기

코딩을 끝낸 후에 마무리하는 과정에서 체크 리스트로써 사용하여 좀 더 아름다운 코드로 만들기

이것이 코딩 테스트다 by Python과 겹치는 부분은 책으로 보면서 잘 이해되지 않았던 부분을 강의를 통해 이해할 수 있었고

LeetCode와 Github를 연결하는 Leethub를 알고는 있었지만 연동하지 못하고 미루고 있었는데

이번 기회에 필요한 환경들을 구축하는 시간을 가질 수 있었다.

 

또, 문제를 푸는 과정도 이전과 다르게 좀 더 체계적으로 구축할 수 있었는데,

문제를 읽고 -> 문제를 내 언어로 적으며 이해하고 -> 문제를 푸는 방법을 한글로 적어서 만들어둔 후에

(코딩을 하는 과정에서 머리속으로 잡아놨던 개념이나 풀이 순서가 헷갈리거나 흐트러질 수 있으므로)

-> 시도하는 방법을 간략히 적고 코딩 -> 해당 부분에 문제가 생기면 그 부분을 수정

-> 코딩이 끝난 후에는 Run을 통해서 답이 맞는지 확인

-> 테스트를 통과하더라도 중복으로 사용되는 불필요한 연산은 없는지, 반복문이 많아서 런타임 오류가 날 수 있지 않은지

    필요없는 구문은 있지 않은지, 내장 함수로 대체할 수 있는 구문은 없는지 등의 간략화 과정을 거치고

-> 마지막으로 반례가 있지 않을지 고민한 후에 제출

하는 식으로 구체화되지 않았던 코딩 테스트의 과정을 구체화할 수 있었다.

 

아래는 예시로 풀었던 Two Sum을 풀이하는 과정을 위의 과정에 따라서 코딩한 내용이다.

public class Solution {
    public int[] TwoSum(int[] nums, int target) {

        int[] output = new int[2];

        for (int i = 0; i < nums.Length; i++)
        {
            int i_val = nums[i];            
            for (int j = 1; j < nums.Length; i++)
            {
                int j_val = nums[j];
                if(i == j)
                {
                    break;
                }
                if(i_val + j_val == target)
                {
                    output[0] = i;
                    output[1] = j;
                    return output;
                }
            }
        }        
    }
}

 

위의 코드에서 문제를 보고 별다른 생각 없이 문제를 풀이하였으며,

처음에는 if (i == j) { break; }

구문이 없어서

error CS0161: 'Solution.TwoSum(int[], int)': not all code paths return a value (in Solution.cs)

오류가 발생했었다.

 

그 뒤에는 런타임 오류가 계속하여 발생하여 처음부터 문제를 다시 풀기 시작하였는데

아래의 코드와 같이 문제를 이해하며 그 것을 주석으로 정리한 후에

문제를 푸는 방법을 구체적으로 생각했다.

public class Solution {
    public int[] TwoSum(int[] nums, int target) {

        // 두 수를 더했을 때 target이 되는 두 수를 찾아야됨
        // 찾은 두 수의 index를 배열 형식으로 표시하게 return 해야됨
        
        // 해결방법1 nums에서 두 수를 불러와서 더하는 식을 만들기
        // 위의 식의 결과 값을 target과 비교하기
        // 결과 값이 target이면 해당 두 수의 index를 반환하기

        int n = nums.Length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[i] + nums[j] == target) {
                    return new int[] { i, j };
                }
            }
        }
        // 적절한 조합을 찾지 못한 경우
        return new int[0]; 
    }
}

 

런타임 에러를 겪지 않기 위해 for 문에서 값을 구한 뒤에 i를 구하는 것이 아닌,

for문 안에 for문을 바로 넣어 연산을 줄였다.

또, 연산을 줄이기위해 nums.Length를 우선 구하여 정수값으로 도출해낸 후에 for 문에서 사용하도록 했다.

 

또, output이라는 배열을 선언하는 것이 아니라, 답을 구하는 순간 그 숫자를 배열로 만들도록 하였으며,

답이 없는 경우에는 바로 빈 배열을 만들도록 하였다.

 

위와 같은 과정을 거쳐서 문제를 해결할 수 있었다.

 

후에 알아보니 좀 더 간략하게 풀 수 있는 방법을 찾았는데,

public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        Dictionary<int, int> map = new Dictionary<int, int>();
        for (int i = 0; i < nums.Length; i++) {
            int complement = target - nums[i];
            if (map.ContainsKey(complement)) {
                return new int[] { map[complement], i };
            }
            map[nums[i]] = i;
        }
        // 적절한 조합을 찾지 못한 경우
        return new int[0];
    }
}

 

이는 아직은 잘 이해하지 못한 Dictionary와 ContainsKey를 사용하는 방법으로 지금은 답이라고 봐도 잘 이해못하지만

이 식도 자유자제로 쓸 수 있게 공부할 것이다.