본문 바로가기
활동/코드스쿼드 마스터즈 선발 과제

코드스쿼드 선발과제(2024, 백엔드 과정)

by lucid_07 2023. 12. 6.
반응형
12월 1일부터 12월 5일까지 진행된 2023년 코드스쿼드 선발 과제 진행 후기입니다.

https://gist.github.com/dearmysolitude/467392c4331d8bd348be3b2fee61ab90

해당 gist의 README.md 와 동일한 내용입니다.

 

Intro

  • 5 일 동안 과제를 진행하며, 첫째 날 늦게 시작하여 마지막 전날에 완료하였음.
  • 마지막 날은 과제 외에 제출을 요구하는 소개서와 설문, 그리고 리드미 정리를 진행하였다.
  • 주어진 과제 기간이 여유롭지 않아 구현을 빠르게 시작하였다: 그러다보니 설계에는 별로 시간을 사용하지는 못함.

 

기능 요구사항

  • 1 단계에서는 게임 로직을 구현
  • 2 단계에서는 2 인이 플레이할 수 있는 로직 변경과 콘솔 입출력 실행의 추가 기능
  • 3 단계에서는 지금껏 구현한 프로그램을 Java에서 지원하는 GUI 도구들을 가지고 GUI로 구현하라는 과제가 주어졌다.
  • 코드는 모두 한 파일에 작성하며, 작업 단계마다 커밋하여 단계별 진척상황을 제출하는 방식이다: 물론 제출 커밋 외의 커밋은 단위 기능이 추가될 때마다 하는 것이 좋다.

 

구현 환경

  • Java 17, Gradle 8.2, IntelliJ 2023.2.2
  • 3 단계에서는 resources 디렉터리 밑에 0~8.png 파일을 추가하여 카드 이미지로 사용하였음.

 

1 단계: 카드 뒤집기 게임 구현

1 단계 커밋 해시: 31c2fbdc31359083b2da42d2da827fa6b10f9279

 

구현 중 주요 내용

Card 클래스

필드로 number와 status를 가지고 있음

status: 게임 시작시 TRUE로 초기화되며 게임이 진행되며 카드가 선택되어 짝이 맞추어지면 FALSE로 deactivate한다.

CardManager 클래스

Card 객체를 게임 환경에 맞게 초기화하여 List형태로 Card 객체를 가지고 있는 클래스이다.

사용자 이름도 입력받으며, 1, 2단계의 경우 콘솔로 입력을 받기 때문에 유의미하지 않은 입력이 나타나는 경우 이를 예외처리 해야 자바 프로그램의 작동이 멈추지 않도록 할 수 있었음.

원래는 카드 클래스에 status를 추가하지 않고 같은 크기의, 같은 인덱스를 가지는 리스트를 하나 더 만들어서 관리하려고 하였으마, 자바에서는 객체 관리가 용이하므로 이를 고려하여 Card 클래스 내에 status 필드를 추가하였다.

게임 로직을 구현할 때 한가지 주의점이 있었는데, contains 메서드와 관련되어 있다: 게임을 계속할 것인지 파악하는데 사용하였다.

  • contains메서드는 List내에 주어진 인자와 동일한 객체가 존재하는지 파악하여 존재한다면 true를 반환하는 메서드이다. 하지만 이 메서드를 이 게임에서 사용할 경우, 리스트에 동일한 값을 가지는 다른 객체들 뿐 아니라, 자기 자신을 포함하고 있어도 true를 반환하였다. 즉, 게임이 계속된다.
  • 이를 의도에 맞추어 사용하기 위해, contains 메서드가 사용하는 isEqual 메서드를 overrapping하여 재정의 하였다. equal 메서드는 hashCode메서드, toString메서드와 함께 기본 객체 클래스인 Object 클래스에서 제공하는 메서드로, 용도에 따라 재정의하는 것이 가능하도록 제공하는 것이다.
  • 그래서, Card 클래스의 equal 메서드를 자기 자신을 포함하지 않도록 재정의 하여 contains 메서드가 의도대로 작동하도록 하였다: 다만, cotains메서드나 equal메서드를 자기 자신을 포함해야 할 때에는 이를 꼭 고려해야 한다는 것을 주지하고 사용해야 했다. 

InputManager 클래스

게임 로직과 더불어 가장 많은 시간을 쏟은 부분으로, 자바는 예외가 발생하면 프로그램 실행을 거부하므로, 예외가 발생하지 않도록 예외처리를 하는 것에 시간이 많이 들었다. 그 외에 의도하지 않게 입력한 경우에도 예외를 발생시켜 다시 입력하도록 하는 로직을 추가하였다.

처음에는 입력받은 String의 index를 찾아 가져오는 방식을 사용하였으나, 입력의 validate를 위해 모두 가져와서 첫글자를 괄호로 시작하고 마지막 글자를 괄호로 마무리하며, 이를떼고 파싱하였을 때 범위 내의 숫자로 형변환이 되는지 점검하는 로직을 적용하였다.

입력 자체는 Scanner 클래스의 nextLine을 사용하였으며, 우테코 프리코스에서 우테코 측이 제공한 래퍼 클래스를 사용하여 메모리가 관리 되도록 하였다.

OutputManager 클래스

출력할 때에는 gameManager를 주입하지 않고, 게임 결과를 인자로 전달하여 결합도를 낮추고자 하였다.

 

입출력 예시

안녕하세요. 카드 맞추기 게임을 시작하겠습니다.
좌표를 입력할 때에는 반드시 괄호안에 쉼표/띄어쓰기로 구분하여 입력해주세요. 예시: (3, 1)
좌표의 범위는 (1, 1)부터 (3, 6) 입니다.
X X X X X X 
X X X X X X 
X X X X X X 

<시도 0, 남은 카드: 18> 좌표를 두 번 입력하세요.
입력 1? (1, 2)
입력 2? (3, 1)
X 1 X X X X 
X X X X X X 
3 X X X X X 

X X X X X X 
X X X X X X 
X X X X X X 
<시도 1, 남은 카드: 18> 좌표를 두 번 입력하세요.
입력 1?

 

2 단계: 2 인 게임으로 구현

2 단계 커밋 해시: a93f9018b5808df251f9c90d32cf891bfb879c74

구현 중 주요 내용

Player 클래스

처음에 구현할 때에는 이 클래스가 어디까지 필드를 가지고 있을지 결정하는 것이 시간이 걸렸다.

결국, 이름과 점수, combo를 가지고 있도록 하였으며, 게임을 연속으로 맞추고 잇는지는  GameManager 클래스에서 consecutive(boolean)으로 관리하도록 하였다.

GameManger 클래스

plyaer1, player2, whosTurn, consecutive를 필드로 추가하였으며, player1과 player2는 이름을 입력하면 유효성 검사 후 player 객체를 초기화 하는데 사용하였다.

whosTurn은 같은 Player 클래스이지만, 누구의 턴인지 GameManager가 파악하는데 사용하도록 player1과 player2 사이를 참조하는 변수로 사용하였다.

기본 로직은: 이름 입력-> Player 객체 2개 생성 -> whosTurn은 먼저 입력된 객체를 가르킴 -> 게임 로직을 불러와 게임을 시작하여 입력하면, 카드 처리후, player 객체의 상태를 업데이트 한다.(이 때, 문제를 맞췄다면 점수를 update하고 consecutive를 on 한다. 점수를 못 맞췄다면 consecutive를 off한다. Player 내부에서는 consecutive 여부를 받은 후, 현재 반영할 Combo를 업데이트한 후, 점수를 더한다.) -> 게임은 이전과 같이 진행 여부를 턴마다 점검하고 게임이 종료되면 게임 정보와 승자, 점수를 출력하고 종료한다.

 

입출력 예시

게임 시작 시 출력

안녕하세요. 카드 맞추기 게임을 시작하기 전에 참가자 두명의 이름을 입력해 주세요.
게이머 1의 이름을 입력하세요.
맛집투어
게이머 2의 이름을 입력하세요.
카페투어
좌표를 입력할 때에는 반드시 괄호안에 쉼표/띄어쓰기로 구분하여 입력해주세요. 예시: (3, 1)
좌표의 범위는 (1, 1)부터 (3, 6) 입니다.
X X X X X X 
X X X X X X 
X X X X X X 

맛집투어의 차례입니다. 현재 점수: 0, 콤보: x1
<시도 0, 남은 카드: 18> 좌표를 두 번 입력하세요.
입력 1?

콤보 시 출력

맛집투어의 차례입니다. 현재 점수: 0, 콤보: x1
<시도 7, 남은 카드: 12> 좌표를 두 번 입력하세요.
입력 1? (2, 5)
입력 2? (2, 6)
X   X       
    X X 5 5 
X X X X X X 

X   X       
    X X     
X X X X X X 
맛집투어의 차례입니다. 현재 점수: 10, 콤보: x1
<시도 8, 남은 카드: 10> 좌표를 두 번 입력하세요.
입력 1? (3, 1)
입력 2? (3, 2)
X   X       
    X X     
7 7 X X X X 

X   X       
    X X     
    X X X X 
맛집투어의 차례입니다. 현재 점수: 30, 콤보: x2
<시도 9, 남은 카드: 8> 좌표를 두 번 입력하세요.

게임 종료 시 출력

13번 시도 만에 완료하였습니다: 게임 종료

<결과 출력>
축하합니다! 승자는 카페투어 입니다.
카페투어: 70 점
맛집투어: 60 점

 

3 단계: GUI로 구현

3 단계 커밋 해시: 149d35d281cf496a95dec8069cf209504dd285c4

게임 로직은 이미 구현하였으므로, 이를 어떻게 보여주어야 하는지가 문제였다.

Java의 swing과 awt를 사용하여 화면에 UI를 뿌려주었다.

화면 구성은 3단계로 하기로 하였다: GameFrame(JFrame)에 StartPanel/GamePanel/ResultPanel(JPanel), 그리고 카드들은 JButton을 확장하는 클래스로 리팩터링하여 panel에 grid로 뿌려주는 것으로 하였다.

조금 혼란스러웠던 점은, 기존에 콘솔로 좌표를 입력받아 예외처리를 모두 해주고 이를 적절한 정보로 바꾸어 GameManager에 전달해주어야 했던 1, 2단계와 다르게, GUI를 사용하게 되면, 클릭 이벤트가 발생하면 이를 받아 특정 로직을 시행하도록 구현해야 했으므로 InputManager의 역할이 필요 없었다: 이름 입력의 경우에도, String으로 입력받아 바로 파싱할 필요 없이 유효성만 검사하였으므로.

결과적으로, 입출력 클래스는 더이상 사용되지 않고 컨트롤러의 역할도 축소되었고, 모든 로직은 초기화 후 GameManager, 즉 서비스 단에서 모두 해결되었다.

 

반응형