Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5eec398
[1, 2단계 - 체스] 재즈(함석명) 미션 제출합니다. (#703)
seokmyungham Mar 25, 2024
44befb2
refactor(Board): equals & hashcode 를 제거하고 보드 초기화 테스트 리팩토링
seokmyungham Mar 27, 2024
67070ed
feat(Board): 각 팀의 점수를 계산하는 기능 추가
seokmyungham Mar 27, 2024
42cfa33
refactor(BoardFactory): 테스트를 위해 보드 초기화 로직이 map을 반환하도록 리팩토링
seokmyungham Mar 29, 2024
3cf2780
feat: 게임 점수를 계산하고 게임 결과를 출력하는 기능 추가
seokmyungham Mar 29, 2024
2cb887d
refactor(ChessGameController): 상태 Command를 사용하도록 리팩토링
seokmyungham Mar 29, 2024
5c47f78
refactor(Row): enum의 인덱스를 역순에서 순서대로 변경
seokmyungham Mar 30, 2024
972d9d8
feat(BoardRepository): 기존 Board를 데이터베이스에 접근하는 BoardRepository로 변경
seokmyungham Mar 31, 2024
19d005c
feat(Room): 체스 게임 방 도메인 Room 생성
seokmyungham Mar 31, 2024
e495e9e
feat: 체스 상황 저장 및 체스 게임방 생성 기능 추가
seokmyungham Mar 31, 2024
5062766
refactor: repository를 생성자로 주입하도록 변경
seokmyungham Mar 31, 2024
0777b71
refactor: fake repository 패키지 구조 변경
seokmyungham Mar 31, 2024
b4ce577
feat: fake repository 추가
seokmyungham Mar 31, 2024
ba433da
test: 서비스 클래스 테스트 추가
seokmyungham Mar 31, 2024
d144ce7
refactor: fake 객체를 주입하도록 리팩토링
seokmyungham Mar 31, 2024
3d97e2d
docs: 구현 기능 목록 업데이트
seokmyungham Mar 31, 2024
c9d5677
refactor: repository 리팩토링
seokmyungham Mar 31, 2024
6ba1c2d
refactor(OutputView): 코드 포맷팅
seokmyungham Mar 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,56 @@
## 우아한테크코스 코드리뷰

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)


# 기능 구현 목록

## 체스 게임
- [X] : 체스 게임 시작 문구를 출력한다.
- [X] : 사용자는 체스 게임 방을 생성할 수 있다.
- [X] : 체스 게임 방 목록중 원하는 방을 선택해서 게임을 시작할 수 있다.
- [X] : 시작 명령어를 입력 받는다.
- [X] : 명령어 형식이 올바르지 않으면 예외를 터트리고 재입력 받는다.
- [X] : start 입력 시 게임을 시작한다.
- [X] : end 입력 시 게임을 종료한다.
- [X] : status 입력 시 각 팀의 점수와 어느 팀이 우세한지 출력한다.
- [X] : move 명령어를 통해 기물을 움직일 수 있다.
- [X] : 보드판을 초기화한다.
- [X] : 소문자, 대문자로 팀을 구분한다.
- [X] : 각 팀은 총 16개의 말을 갖는다.
- [X] : 폰 8개, 룩 2개, 나이트 2개, 비숍 2개, 퀸 1개, 킹 1개
- [X] : 왕이 잡히면 게임이 종료되고 게임 결과를 출력한다.


## 체스 기물
### 방향
- [X] : 북, 북동, 동, 동남, 남, 남서, 서, 북서쪽의 총 8방향을 갖는다.
- [X] : 방향에 따라 방향 가중치 (1, -1, 0)을 갖는다.

### 위치
- [X] : 행, 열 enum을 갖는다.
- [X] : 현재 위치를 기준으로 방향에 따른 최대 가중치를 계산한다.
- [X] : 기물이 고유로 갖는 이동 방향과 가중치를 이용하여 이동할 수 있는 위치들을 반환한다.

### 기물
- [X] : 기물은 기물 타입을 갖는다.
- [X] : 기물은 팀 색깔을 갖는다.

### 기물 타입
- [X] : 기물 타입은 이동 전략을 갖는다.
- [X] : 각 기물의 이동 전략 구현체는 기물의 현재 위치, 방향, 가중치를 가지고 이동할 수 있는 후보 위치들을 계산한다.
- [X] : 룩은 기본적으로 북, 동, 남, 서 방향으로 이동할 수 있다.
- [X] : 비숍은 기본적으로 북동, 동남, 남서, 북서 방향으로 이동할 수 있다.
- [X] : 퀸은 기본적으로 모든 방향으로 이동할 수 있다.
- [X] : 킹은 기본적으로 모든 방향으로 이동할 수 있다.
- [X] : 폰은 기본적으로 팀 색깔에 따라 북 또는 남 방향으로만 이동할 수 있다.
- [X] : 나이트는 기본적으로 북북동, 동북동, 동남동, 남남동, 남남서, 서남서, 서북서, 북북서 방향으로 이동할 수 있다.

## 기물 이동
- [X] : 각 기물이 생성한 후보 위치를 이용하여 실제 이동 가능한 위치인지 판단한다.
- [X] : 위치에 기물이 존재하지 않으면 이동 가능한 위치이므로 포함한다.
- [X] : 위치에 우리팀 기물이 존재할 경우 이동할 수 없는 위치이므로 포함하지 않고 다음 방향으로 넘어간다.
- [X] : 위치에 상대팀 기물이 존재할 경우 이동가능한 위치이므로 포함하고 다음 방향으로 넘어간다.
- [X] : 명령어 입력으로 들어온 목적지 위치가 실제 이동 가능한 위치인지 판단한다.
- [X] : 실제 이동 가능한 위치에 포함되어 있는 경우 이동한다.
- [X] : 실제 이동 가능한 위치에 포함되어 있지 않은 경우 에러를 발생시킨다.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dependencies {
testImplementation platform('org.assertj:assertj-bom:3.25.1')
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation('org.assertj:assertj-core')

runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
}

java {
Expand Down
Empty file removed src/main/java/.gitkeep
Empty file.
21 changes: 21 additions & 0 deletions src/main/java/chess/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package chess;

import chess.controller.ChessGameController;
import chess.repository.BoardRepository;
import chess.repository.BoardRepositoryImpl;
import chess.repository.RoomRepository;
import chess.repository.RoomRepositoryImpl;
import chess.service.BoardService;
import chess.service.GameService;

public class Application {
public static void main(String[] args) {
RoomRepository roomRepository = new RoomRepositoryImpl();
BoardRepository boardRepository = new BoardRepositoryImpl();

GameService gameService = new GameService(roomRepository, boardRepository);
BoardService boardService = new BoardService(roomRepository, boardRepository);
ChessGameController chessGameController = new ChessGameController(gameService, boardService);
chessGameController.run();
Comment on lines +13 to +19
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추상화가 잘 되어 있고 의존관계가 복잡해지는 만큼 의존성 주입자 Config 클래스를 운용해보는 것도 나쁘지 않을 듯!
과할수도 있다고 생각할 수도 있으려나? 근데 나는 현재 코드 볼륨에 적용하기 괜찮을 것 같아 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 나도 같은 생각을 하긴 했었는데, 리비도 그렇게 느끼는구나

아마 2단계 들어가면 이 주제로 대화해볼 기회가 있지 않을까 싶어👍

}
}
54 changes: 54 additions & 0 deletions src/main/java/chess/controller/ChessGameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package chess.controller;

import chess.controller.command.Command;
import chess.controller.command.CommandRouter;
import chess.domain.game.Room;
import chess.service.BoardService;
import chess.service.GameService;
import chess.view.InputView;
import chess.view.OutputView;

public class ChessGameController {

private final GameService gameService;
private final BoardService boardService;

public ChessGameController(GameService gameService, BoardService boardService) {
this.gameService = gameService;
this.boardService = boardService;
}

public void run() {
OutputView.printRoomNames(gameService.findAllRoomNames());
Room room = createRoom();
OutputView.printStartMessage(room.getName());
process(gameService, boardService, room.getId());
}

private Room createRoom() {
try {
String input = InputView.readRoomName();
return gameService.loadRoom(input);
} catch (RuntimeException error) {
OutputView.printError(error);
return createRoom();
}
}
Comment on lines +28 to +36
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사실 코드를 처음보는 입장에서 가장 먼저 하게 되는 일은 메서드 시그니처를 읽고 해당 메서드가 어떤 일을 하는지 예측하는 것인 것 같아. (적어도 나는 ㅋㅋ)

현재 createRoom()이라는 메서드 만 보았을 때는 Room을 하나 만들겠구나 예상 되는 데 안의 동작을 보면 서비스에서 게임을 로드하는 것으로 보여

좋은 시그니처가 가독성 좋은 코드로 이어진다고 믿는 사람으로써 재즈도 이러한 부분을 무게감 있게 생각해보면 좋을 것 같음! 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 맞아.. 내 pr 가서 읽어보면 아마 웃음이 나올 수도..

레디 리뷰에 시그니처 관련 리뷰를 잔뜩 달아놓고 정작 내가 더 문제가 심각한듯,, 반성중이야..


private void process(GameService gameService, BoardService boardService, Long roomId) {
State state = State.RUNNING;
do {
state = executeCommand(gameService, boardService, state, roomId);
} while (state != State.END);
}

private State executeCommand(GameService gameService, BoardService boardService, State state, Long roomId) {
try {
Command command = CommandRouter.findCommendByInput(InputView.readCommend());
return command.execute(gameService, boardService, roomId);
} catch (RuntimeException error) {
OutputView.printError(error);
return state;
}
}
Comment on lines +45 to +53
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요청을 처리하기 위해 복수의 서비스 클래스가 필요하네.
하나의 요청을 처리하기 위해 복수의 서비스 클래스가 필요한 것은 분리가 필요함을 이야기하고 있는 것은 아닐까?

요청을 수행하는 것을 하나의 트랜잭션으로 보고 해당 트랜잭션 내부에서 여러 서비스 클래스가 필요하다면 응용 서비스로직이 많이 어려워질 수 있다는 생각이야.

그런데 내가 보기에도 쉽지 않은 개선인 것 같긴 하네 🫠

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰어께도 추상화가 적절히 이루어졌는지 의문이 든다는 피드백을 받았는데, 확실히 다시 고민해봐야할 부분인 것 같아. 정확하게 잘 짚어준듯👍

}
7 changes: 7 additions & 0 deletions src/main/java/chess/controller/State.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package chess.controller;

public enum State {

RUNNING,
END
}
10 changes: 10 additions & 0 deletions src/main/java/chess/controller/command/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package chess.controller.command;

import chess.controller.State;
import chess.service.BoardService;
import chess.service.GameService;

public interface Command {

State execute(GameService gameService, BoardService boardService, Long roomId);
}
34 changes: 34 additions & 0 deletions src/main/java/chess/controller/command/CommandRouter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package chess.controller.command;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public enum CommandRouter {
START("start", Start::new),
END("end", End::new),
MOVE("move", Move::new),
STATUS("status", Status::new);

private static final int COMMAND_INDEX = 0;

private final String value;
private final Function<List<String>, Command> command;

CommandRouter(String value,
Function<List<String>, Command> command) {
this.value = value;
this.command = command;
}

public static Command findCommendByInput(List<String> commandInput) {
if (commandInput == null || commandInput.size() == 0) {
throw new IllegalArgumentException("빈 값 입력을 허용하지 않습니다.");
}
return Arrays.stream(values())
.filter(commandRouter -> commandRouter.value.equals(commandInput.get(COMMAND_INDEX)))
.map(commandRouter -> commandRouter.command.apply(commandInput))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("올바르지 않은 명령어 입력입니다."));
}
}
22 changes: 22 additions & 0 deletions src/main/java/chess/controller/command/End.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package chess.controller.command;

import chess.controller.State;
import chess.service.BoardService;
import chess.service.GameService;
import java.util.List;

public class End implements Command {

private static final int END_COMMAND_SIZE = 1;

public End(List<String> commandInput) {
if (commandInput.size() != END_COMMAND_SIZE) {
throw new IllegalArgumentException("게임 종료 명령어 입력 형식이 올바르지 않습니다.");
}
}

@Override
public State execute(GameService gameService, BoardService boardService, Long roomId) {
return State.END;
}
}
61 changes: 61 additions & 0 deletions src/main/java/chess/controller/command/Move.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package chess.controller.command;

import chess.controller.State;
import chess.domain.board.position.Column;
import chess.domain.board.position.Position;
import chess.domain.board.position.Row;
import chess.service.BoardService;
import chess.service.GameService;
import chess.service.dto.ChessGameResult;
import chess.view.OutputView;
import chess.view.mapper.ColumnMapper;
import chess.view.mapper.RowMapper;
import java.util.List;
import java.util.regex.Pattern;

public class Move implements Command {

private static final Pattern POSITION_REGEX = Pattern.compile(""
+ "move\\s+([a-h][1-8])\\s+([a-h][1-8])");
Comment on lines +18 to +19
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정규식... 고수인가?

정규식 나중에 알려주라 👊

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조금 복잡한 정규식은 지쌤..(gpt) 도움을 많이 받긴해 😂

이걸 다 외우는건 효율적으로도 말이 안된다고 생각함 하하


private final Position from;
private final Position to;

public Move(List<String> commandInput) {
validateMoveCommandPattern(commandInput);
String fromPosition = commandInput.get(1);
String toPosition = commandInput.get(2);
this.from = createPosition(fromPosition.substring(0, 1), fromPosition.substring(1, 2));
this.to = createPosition(toPosition.substring(0, 1), toPosition.substring(1, 2));
}

private void validateMoveCommandPattern(List<String> commandInput) {
String moveCommand = String.join(" ", commandInput);
if (!POSITION_REGEX.matcher(moveCommand).matches()) {
throw new IllegalArgumentException("게임 이동 명령어 입력 형식이 올바르지 않습니다.");
}
}

@Override
public State execute(GameService gameService, BoardService boardService, Long roomId) {
if (boardService.isCheckmate(to, roomId)) {
moveAndPrintBoard(gameService, boardService, roomId);
ChessGameResult chessGameResult = gameService.generateGameResult(roomId);
OutputView.printChessGameResult(chessGameResult);
return State.END;
}
moveAndPrintBoard(gameService, boardService, roomId);
return State.RUNNING;
}

private void moveAndPrintBoard(GameService gameService, BoardService boardService, Long roomId) {
boardService.movePiece(from, to, roomId);
OutputView.printBoard(boardService.getAllPieces(roomId));
}

private Position createPosition(String requestColumn, String requestRow) {
Column column = ColumnMapper.findByInputValue(requestColumn);
Row row = RowMapper.findByInputValue(requestRow);
return new Position(row, column);
}
}
24 changes: 24 additions & 0 deletions src/main/java/chess/controller/command/Start.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package chess.controller.command;

import chess.controller.State;
import chess.service.BoardService;
import chess.service.GameService;
import chess.view.OutputView;
import java.util.List;

public class Start implements Command {

private static final int END_COMMAND_SIZE = 1;

public Start(List<String> commandInput) {
if (commandInput.size() != END_COMMAND_SIZE) {
throw new IllegalArgumentException("게임 시작 명령어 입력 형식이 올바르지 않습니다.");
}
}

@Override
public State execute(GameService gameService, BoardService boardService, Long roomId) {
OutputView.printBoard(boardService.getAllPieces(roomId));
return State.RUNNING;
}
}
24 changes: 24 additions & 0 deletions src/main/java/chess/controller/command/Status.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package chess.controller.command;

import chess.controller.State;
import chess.service.BoardService;
import chess.service.GameService;
import chess.view.OutputView;
import java.util.List;

public class Status implements Command {

private static final int STATUS_COMMAND_SIZE = 1;

public Status(List<String> commandInput) {
if (commandInput.size() != STATUS_COMMAND_SIZE) {
throw new IllegalArgumentException("게임 점수 명령어 입력 형식이 올바르지 않습니다.");
}
}

@Override
public State execute(GameService gameService, BoardService boardService, Long roomId) {
OutputView.printTeamScore(gameService.generateGameResult(roomId));
return State.RUNNING;
}
}
56 changes: 56 additions & 0 deletions src/main/java/chess/domain/board/BoardFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package chess.domain.board;

import chess.domain.board.position.Column;
import chess.domain.board.position.Position;
import chess.domain.board.position.Row;
import chess.domain.piece.Color;
import chess.domain.piece.Piece;
import chess.domain.piece.PieceType;
import java.util.HashMap;
import java.util.Map;

public class BoardFactory {

public Map<Position, Piece> initialize() {
Map<Position, Piece> map = new HashMap<>();
initializeBlackTeam(map);
initializeWhiteTeam(map);
return map;
}

private void initializeBlackTeam(Map<Position, Piece> map) {
initializePawn(map, Row.SEVEN, Color.BLACK);
initializeHighValuePiece(map, Row.EIGHT, Color.BLACK);
}

private void initializeWhiteTeam(Map<Position, Piece> map) {
initializePawn(map, Row.TWO, Color.WHITE);
initializeHighValuePiece(map, Row.ONE, Color.WHITE);
}

private void initializePawn(Map<Position, Piece> map, Row row, Color color) {
for (Column column : Column.values()) {
Position position = new Position(row, column);
if (color == Color.WHITE) {
map.put(position, new Piece(PieceType.WHITE_PAWN, color));
}
if (color == Color.BLACK) {
map.put(position, new Piece(PieceType.BLACK_PAWN, color));
}
}
Comment on lines +32 to +40
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뎁스가 깊어진다고 해도 이러한 형태로 풀어쓰는 것이 개인적으로 가독성 좋다고 생각 👍

}

private void initializeHighValuePiece(Map<Position, Piece> map, Row row, Color color) {
map.put(new Position(row, Column.A), new Piece(PieceType.ROOK, color));
map.put(new Position(row, Column.H), new Piece(PieceType.ROOK, color));

map.put(new Position(row, Column.B), new Piece(PieceType.KNIGHT, color));
map.put(new Position(row, Column.G), new Piece(PieceType.KNIGHT, color));

map.put(new Position(row, Column.C), new Piece(PieceType.BISHOP, color));
map.put(new Position(row, Column.F), new Piece(PieceType.BISHOP, color));

map.put(new Position(row, Column.D), new Piece(PieceType.QUEEN, color));
map.put(new Position(row, Column.E), new Piece(PieceType.KING, color));
}
}
Loading