0. 과제 소개
이번주의 과제는 장기 미션이었다. 사이클 1, 2로 끝날 일정이었지만, 대다수가 2주안에 끝나지 않아서 필수 미션 연장을 선택해 3주 동안 진행됐다.
1) 장기 과제 사이클 1
2) 장기 과제 사이클 2
3) 장기 과제 사이클 연장 미션


1.1 장기 과제
1) 과제 이해하기
이번 과제의 주제는 장기 - OOP, 영속화였다.
앞에 미션인 블랙잭 때보다 기능 요구사항도 복잡해지고, DB를 연동하는 과제도 추가되었다.
지난주와 마찬가지 순서로 진행된다.
(사전 학습 → 토론 조와 규칙 정하기 → 페어 프로그래밍 (사이클 1) → 리뷰 받기 → 개인 구현(사이클2) → 리뷰 받기)
1.2 사이클 1 : OOP
OOP - 불변과 캡슐화, 다형성(인터페이스, 추상 클래스)
1.1단계 - 보드 초기화 : 처음 보드판 장기말 초기화
1.2단계 - 기물 이동 : 각 기물의 규칙에 맞게 이동시키기
사이클 1은 위의 기능을 구현하는 것이 목표였다.
1) 보드 초기화
1.1 단계에서 가장 고민했던 것은 어떻게 초기화할 것이가, 빈 공간을 어떻게 표시할 것인가였다.
배열을 사용하면 안된다는 규칙이 있었기 때문에, dict 형태로 보드판을 저장하였다.
// Board.java
public class Board {
private final Map<Position, Piece> board;
}
처음 장기말의 위치는 enum으로 저장해두고, PieceFactory를 사용해서 기물들을 생성해주었다. 이때, 마와 상의 위치를 사용자에게 입력받아야하기 때문에(게임 룰), enum에서 마와 상은 초기 값이 없다.
// PieceFactory.java
public class PieceFactory {
private static final Map<PieceType, Function<Country, Piece>> factory = Map.of(
PieceType.CHA, Cha::new,
PieceType.MA, Ma::new,
PieceType.SANG, Sang::new,
PieceType.SA, Sa::new,
PieceType.JANG, Jang::new,
PieceType.PO, Po::new,
PieceType.JOL, Jol::new,
PieceType.NONE, c -> None.INSTANCE
);
public static Piece createPiece(PieceType pieceType,Country country){
return factory.get(pieceType).apply(country);
}
}

그리고 처음에 빈 공간은 그저 Map에 key 값이 없는 상태로 표현되지만, 이후에 장기말이 이동하면 그곳을 None 객체로 채워주었다. 이때는 None 객체에 대한 이해도가 없어서 두 방법이 섞였는데, 지금 다시 리팩토링한다면, 빈 공간에 모두 None 객체를 채우는 방향으로 수정할 것 같다. None 객체를 사용하면 키값이 있는지 없는지 확인하는 if문을 없애고, 모든 칸에 대해 동일한 로직을 적용할 수 있다. 👍
2) 기물 이동
1.2단계에서 고민했던 것은 각 기물별로 규칙이 다른데 이를 어떻게 구현할 것인가였다.
우선 모든 기물이 "나라", "기물 종류"라는 공통 속성을 가지고 있기 때문에, 추상클래스를 사용하기로 하였다. 그리고 이 추상클래스를 상속받아서 모든 기물을 클래스로 만들었다.

// Piece.java
public abstract class Piece{
private final Country country;
private final PieceType pieceType;
public Piece(Country country, PieceType pieceType) {
this.country = country;
this.pieceType = pieceType;
}
public Optional<Position> move(Position start, Direction direction) {
int startX = start.getX();
int startY = start.getY();
int forward = country.getForward();
int dx = direction.getDx();
int dy = direction.getDy();
try {
return Optional.of(Position.create(startX + dx * forward, startY + dy * forward));
} catch(IllegalArgumentException e) {
return Optional.empty();
}
}
public abstract List<Position> getAvailableRoute(Position start, PieceFinder finder);
public boolean isDifferentCountry(Country endCountry) {
return !country.equals(endCountry);
}
// (생략)
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Piece piece = (Piece) o;
return country == piece.country && pieceType == piece.pieceType;
}
@Override
public int hashCode() {
return Objects.hash(country, pieceType);
}
}
각 기물에서 스스로 본인의 규칙에 따라 이동을 하고, 이동 가능한 좌표를 반환하게끔 코드를 구현하고 싶었다. 그래서 아래의 과정으로 좌표를 판단하도록 코드를 구현했다.
이동 가능 판단 전략
1. 추상클래스 Piece에 현재 위치와 방향을 입력받아 1칸 이동하는 move()를 정의한다.
2. 기물별로 상하좌우 방향으로 탐색한다.
1) 졸, 장, 사는 1칸 이동한다. (졸은 '하' 방향 제외)
2) 차, 포는 while문을 사용해서 보드 경계선까지 이동한다.
3) 마, 상은 1칸 전진 후 대각선으로 이동한다.
3. 1칸 이동 후, 해당 칸에서 검증한다.
1) 도착칸에 기물이 없다면 이동 가능
2) 도착칸에 기물이 있을 때, 다른 나라라면 이동 가능 (단, 이후 전진 불가)
3) 도착칸에 기물이 있을 때, 같은 나라라면 이동 불가능 (while이면 break하기)
*포는 추가적인 검증 필요 (isNotPo(), hasNotPoAmongPath(), hasOnePieceAmongPath())
그런데 여기서 문제가 있었다. 이동 가능한지를 알려면 현재 보드판의 상태가 필요한데, 이는 Board가 알고 있다. Board를 piece를 가지고 있는데, piece가 Board의 정보를 알려고 하면 순환참조 문제가 발생한다.
Board → Piece
Piece → Board
이를 해결하기 위해서 인터페이스를 사용했다.
Board는 보드의 정보를 반환해주는 PieceFinder 인터페이스를 기물에게 파라미터로 넘겨준다. 기물은 이 인터페이스를 통해 보드의 정보를 간접적으로 알 수 있다. 즉, Piece는 Board가 아닌 PieceFinder에게 물어보는 것이다.
Board → Piece
Piece → PieceFinder
// Board.java
public List<Position> findAvailablePositions(Position start) {
Piece startPiece = board.getOrDefault(start, None.INSTANCE);
PieceFinder finder = new PieceFinder() {
@Override
public Piece find(Position position) {
return board.getOrDefault(position, None.INSTANCE);
}
};
return startPiece.getAvailableRoute(start, finder);
}
덕분에 각 기물 객체에서 1칸씩 이동하며 이동가능한 좌표를 반환해줄 수 있었다.
1.2 사이클 2 : 영속화
영속화 - DB 연동 (JDBC, Connection, PreparedStatement, ResultSet)
2.1단계 - 기물의 확장 : 궁성 영역 추가, 점수 계산 추가
2.2단계 - DB 적용 : 데이터베이스 연결 및 저장
사이클 2은 위의 기능을 구현하는 것이 목표였다.
1) 궁성 영역 추가
궁성 영역에서는 대각선이 있다. 따라서, 몇몇의 기물들이 대각성으로 이동할 수 있는 특수한 규칙을 추가로 갖는다.

## 궁성이니?
Position 클래스에서 궁성에 대한 판단을 하기로 하였다.
그래서 장과 사는 현재 위치 Position을 통해 궁성 안이니?를 물어볼 수 있게 하였다.

## 궁성 이동 규칙
이제 궁성 안이라면 대각 이동이 가능해야한다. 그러기 위해서는 탐색하는 Direction에 대각선도 추가해주면 간단하게 수정이 된다.

아래와 같이 방향을 추가할 수 있다.

(물론 저 방향을 호출하는 메서드를 빼면, 아래와 같이 더 간결하게 표현가능하다)
// Direction.java
public static List<Direction> getDiagonalDirections() {
return List.of(UP_LEFT,UP_RIGHT,DOWN_LEFT,DOWN_RIGHT);
}
// Jang.java
if (start.isPalaceDiagonal()){
directions.addAll(Direction.getDiagonalDirections());
}
이와 같이 나머지 사 역시 directions를 추가해주기만 하면 알아서 검토가 가능하다.
## 차,포
차포는 여기서 한가지 더 다른 점이 있다. 바로 대각 이동이 두번까지 가능하다는 점이다. 이걸 하나하나의 움직임으로 보기 어렵다는 판단이 들었다.
대각선 이동이면 그건 궁성이라는 건데!
아 그러면 궁성 대각선이 가능한 곳에 start 위치해있으면 for문 로직에서 그게 궁성 안인지 체크하면 되겠구나!
// 대각선 이동인데 궁성 안에 없다? 이건 안되지~
if (Direction.getDiagonalDirections().contains(direction) && (!position.get().isInPalace())){
break;
}
2) DB 연동
이번에는 DB를 연동하는 과정이 미션으로 나왔다. 스프링에서는 항상 yml 파일로 설정해서 DB연결에 대해 신경쓸 일이 없었는데, 이번 미션을 통해 순수 자바에서 직접 DB를 붙여보는 경험을 할 수 있었다.
## DB 선택 이유
H2가 기본으로 내장되어있어서, 설치없이 사용할 수 있는 H2를 선택했다.
Application을 실행시키면 "janggiGame.mv.db"파일이 로컬에 저장되고, H2 콘솔 브라우저가 열린다.
http://localhost:8082/ 를 통해 H2 콘솔 브라우저를 접속할 수 있다.
## DB 저장 시점
- 사용자가 초나라, 한나라의 마상마상을 선택했을 때→보드 정보 초기화하여 DB에 INSERT
- 한 턴이 끝났을 때 → 바뀐 보드 정보와 다음 순서를 DB에 UPDATE
## DB 연결 과정
H2 의존성 추가 (build.gradle) → config 파일 작성 → db 연결
// config/ConnectionManager.java
package config;
public class ConnectionManager {
private static final String DB_URL = "jdbc:h2:~/janggiGame";
private static final String USER_NAME= "sa";
private static final String PASSWORD = "";
public static Connection getConnection(){
try{
return DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD );
} catch (SQLException e){
throw new IllegalStateException("DB connection Error",e);
}
}
public static void closeConnection(Connection conn){
if (conn != null){
try{
conn.close();
} catch (SQLException e){
throw new IllegalStateException("Connection close error", e);
}
}
}
}
더 자세한 연결 과정 : https://velog.io/@wldud8297/%EC%9A%B0%ED%85%8C%EC%BD%94-%EC%9E%A5%EA%B8%B0%EA%B2%8C%EC%9E%84-%EC%B6%94%EC%83%81
[우테코] 장기 - H2 데이터베이스 DB 연동
1. 기물 이동 가능 좌표 반환 1) 설계 사용자가 기물을 선택하면, 해당 기물이 이동 가능한 좌표를 반환해주도록 설계하였다. > ### 이동 가능 판단 전략 추상클래스 Piece에 현재 위치와 방향을 입
velog.io
1.3 우리만의 테코톡
우테코에는 기술에 대해서 짧게 발표하는 테코톡(techo-talk)이 있다.
여기서 이름을 따와서 '우리끼리 더 많이 토론해보자'는 취지로 "우리만의 테코톡"을 한다.(이건 걍 우리 조가 따로 하는거😁) 그냥 _ _에 대해서 저 테코톡할게요라고 말하면 시간이 가능한 팀원들이 모여서 얘기하는 것이다.
나는 이번에 DB를 붙이면서 배웠던 Connection 생성 및 Transaction 관리에 대해서 얘기해보고 싶어서, 이 주제로 선정했다.
## 나의 테코톡을 간단히 요약해보자면,
1. Service 계층에서 Connection을 생성 메서드를 호출하자.
2. Connection 생성과 종료를 담당하는 ConnectionManager → DriverManager을 이용해서 Conncetion을 생성한다.
3. 트랜잭션 관리를 담당하는 TransactionManager → 트랜잭션에 필요한 Connection 생성 호출, 커밋, 롤백, Connection 닫기를 수행한다.
우리들만의 테코톡 후기를 얘기해보자면 내가 아는 것을 상대방에게 제대로 설명하는 것이 참 어렵구나를 많이 느꼈다. 자료를 준비하며 공부했지만, 모르는 것이 많다보니 자꾸 딴길로 새서 핵심이 흩어진 것 같다. 공부한 것에 비해 제대로 설명하지 못해 조금 아쉬움이 남았다.
테코톡이 끝나고 팀원들에게 추가로 피드백을 받았다.
설명할 때 주로 나의 경험을 바탕으로 구성했는데, 이러다보니 "나는 이렇게 했어요"와 같은 정보 전달 식의 발표가 되었던 것 같다. 이런 방식의 발표는 상대방이 나의 의견을 객관적으로 받아들이기 어렵게 만들었던 것 같다. 그래서, 다음에는 공식문서를 기반으로 자료 조사를 해서 객관적인 관점으로 발표를 해야겠다고 생각했다.
1.4 결과물 제출
제출 링크:
Q. 지난 2주 동안의 활동을 돌아보고, 느낀 점이나 변화한 점을 정리해 작성해 주세요.
1) 추상화
기물별로 행동이 유사한 듯 하면서 다르다보니, "어떻게 유사한거끼리 묶을까"라는 고민 과정에서 추상화를 많이 생각했다.
설계적으로 바로 떠오르지 않아서, 직접 하드 코딩 하듯 구현을 해보고 중복되는 코드를 보면서 추상화 설계를 하려고 많이 했다.
기물별 움직임을 크게 추상화 시킨다면 "한칸 전진"이라는 하나의 개념이 나온다.
결국 대각선도 (위 + 오른쪽)과 같이 (한칸 + 한칸) 전진이기 때문에, 이 움직임을 추상화 시키기 위해 MoveStrategy라는 인터페이스를 만들었다. 그리고 MoveStrategy를 implemetns해서 방향이 정해져있을 때(위, 아래, 오른쪽, 왼쪽), 한 칸 전진하는 StraightMovement와 대각선 전진(따져보자면 두 칸 전진) DiagonalMovement를 구현했다. 이렇게 추상화를 하고 나니 기물 별로 재사용할 수 있고, 모든 움직임을 MoveStrategy로 구현할 수 있다보니 확장성도 개선되었다.
추상화는 결국 유사한 행동을 묶어서 재사용하기 위한 좋은 전략인 것 같다. 좋은 추상화를 위해서 코드를 많이 짜보고, 여러 방면으로 추상화를 해보면서 경험하는 것이 중요할 것 같다. 사이클 2를 완성하고 나면, 다른 크루들의 코드를 보며 어떻게 추상화를 하였는지 확인하며 나의 관점을 더 명확히 해보고 싶다.
2) 추상 클래스
우테코 전에는 추상 클래스를 한번도 써보지 못했는데, 이번 과정으로 추상클래스와 인터페이스에 대해서 알게되었다.
기물 종류와 나라라는 정보를 가지고 있기 때문에, 추상클래스로 Piece를 만들고, 각 기물별로 상속받아서 구현을 했다.
추상 클래스에서 메서드를 정의해두고, 각 기물에서 오버라이딩해서 구현하니 같은 메서드여도 기물별로 다른 행동이 가능했다.
이렇게 코드를 짜보니, 설계를 잘 하면 코드가 훨씬 간결해진다는 것을 느낀 것 같다.
나는 원래 구현을 먼저 하는 타입이었는데, 이 경험을 바탕으로 다음번에는 설계를 먼저 해보는 것도 나에게 도움이 될 것 같다.
'우테코' 카테고리의 다른 글
| [우테코 8기] 3주차 : 선택 미션 & 공통 교육 (0) | 2026.04.01 |
|---|---|
| [우테코 8기] 2주차 : 블랙잭 과제 (사이클 2) (0) | 2026.03.29 |
| [우테코 8기] 1주차 : 연극 시연 & 블랙잭 과제 (0) | 2026.03.09 |
| [우테코 8기] 온보딩 미션 : 제미나이 Canvas로 웹앱 만들기 (0) | 2026.03.02 |
| [우테코 8기] 최종 코딩 테스트 준비 과정 & 최종 합격 후기 (6) | 2026.01.23 |