Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
504a565
feat(PieceValue): PieceValue 캐싱
jongmee Mar 29, 2024
782a065
build: mysql 의존성 설정
jongmee Mar 29, 2024
eb6ef8d
feat(MySqlConnection): mysql 연결 설정 클래스 생성
jongmee Mar 29, 2024
4cf7ab7
feat(ChessBoardDao): 체스보드를 생성하고 id로 조회한다
jongmee Mar 29, 2024
bac7a37
feat(PieceDao): 특정 ChessBoard의 Piece를 저장하고 수정한다
jongmee Mar 30, 2024
b78d303
feat(TurnDao): 게임 차례를 저장하고 조회한다
jongmee Mar 30, 2024
0572b66
refactor(StatementExecutor, ResultSetMapper): dao 리팩토링
jongmee Mar 30, 2024
0388c70
refactor: dao 메소드 이름 변경
jongmee Mar 30, 2024
004f830
feat(DataAccessException): dao 관련 커스텀 예외 생성
jongmee Mar 30, 2024
89c3a2e
refactor: respository 패키지 구조 변경
jongmee Mar 30, 2024
2330ed8
fix: ChessBoard의 id 를 long type으로 변경
jongmee Mar 30, 2024
9ce63d3
feat(ChessGameService): 체스보드, 기물, 게임 차례를 어플리케이션에서 저장한다
jongmee Mar 30, 2024
722a81d
feat(ChessBoardDao): 가장 최근 저장된 체스 보드 id를 조회한다
jongmee Mar 30, 2024
f9528f3
fix(ChessBoardDao): 가장 최근에 생성된 체스 보드를 조회한다.
jongmee Mar 30, 2024
0bd4e1a
fix(ChessGameService): 이미 생성된 게임 차례가 있다면 새로 생성하지 않는다
jongmee Mar 30, 2024
f438b00
refactor(ChessBoardDao): ResultSetMapper 로직 리팩토링
jongmee Mar 30, 2024
802c781
fix(ChessGameService): 게임 차례를 바꿀 때 기존 차례를 삭제한다
jongmee Mar 30, 2024
4910aa9
feat(ChessBoardDao): ChessBoard에 게임 결과를 저장한다
jongmee Mar 30, 2024
4adc0d9
fix(ChessBoardDao): 최근 체스보드 조회시 '진행중'인 것만 조회한다
jongmee Mar 30, 2024
5653b5b
feat(ChessBoardDao): 게임 결과를 저장하고 조회한다
jongmee Mar 30, 2024
336534e
feat: 최종 게임 결과를 판단하고 저장한다
jongmee Mar 30, 2024
4fea615
feat: 이전 체스 게임 결과 목록을 조회한다
jongmee Mar 30, 2024
121644a
chore(OutputView): 게임 안내 문구 수정
jongmee Mar 30, 2024
cbe6fa3
refactor: 불필요한 싱글톤 제거
jongmee Mar 30, 2024
fdadbd7
chore(MySqlConnection): 환경 변수 설정
jongmee Mar 30, 2024
56d6e99
build: mysql docker 설정 및 MySqlConnector 설정 변경
jongmee Mar 30, 2024
47ab38a
chore(ChessBoardDao): 쿼리 문자열 수정
jongmee Mar 30, 2024
7aef379
docs: step4 기능 추가
jongmee Mar 30, 2024
d63dc73
remove(TurnMapper): TurnMapper getter로 대체
jongmee Mar 30, 2024
aa7d3ef
remove(ChessBoardGenerator): ChessBoardGenerator 인터페이스 삭제
jongmee Mar 30, 2024
5fe009b
style: sql 포매팅
jongmee Mar 30, 2024
15fa090
remove(PropertyLoadException): 사용하지 않는 exception 제거
jongmee Mar 30, 2024
561f7d2
feat: 플레이어가 서로 합의하여 무승부가 될 수 있다
jongmee Mar 31, 2024
dd52c74
chore(OutputView): 게임 시작 문구 변경
jongmee Mar 31, 2024
7f28d79
refactor(GameState): Move, Summarize state 생성
jongmee Mar 31, 2024
bb5087c
test(ChessGameServiceTest): ChessGameService 통합 테스트 추가
jongmee Mar 31, 2024
1fb6789
chore: test용 ddl 별개의 파일로 분리
jongmee Apr 1, 2024
d32ea66
refactor: repository 패키지의 utility 패키지명 변경
jongmee Apr 1, 2024
612db51
test(ChessBoardDaoTest): Optional 사용 로직 수정
jongmee Apr 1, 2024
cb48c6f
test: DB 연동 테스트 시 BeforeEach를 AfterEach로 변경
jongmee Apr 1, 2024
07d3742
feat: turn 테이블 삭제, chess_board에 turn 속성 추가
jongmee Apr 1, 2024
6e6f9be
refactor: 초기 체스판 조회시 게임 차례도 함께 조회
jongmee Apr 1, 2024
8323757
refactor(MySqlConnector): 테스트와 프로덕션 db connection을 따로 관리
jongmee Apr 1, 2024
f64e100
chore: chess_board id 컬럼명 변경
jongmee Apr 1, 2024
a418e73
refactor(ChessBoardDao): 쿼리에서 컬럼 이름 변경
jongmee Apr 1, 2024
8edc646
fix: ddl 오타 수정
jongmee Apr 1, 2024
99ef558
fix: ddl 오타 수정
jongmee Apr 1, 2024
9dfa5b9
refactor: repository에서 dao로 패키지명 변경
jongmee Apr 2, 2024
8f32164
remove(MySqlConnectionTest): db 연결 테스트 삭제
jongmee Apr 2, 2024
6b24c62
fix: db connection과 ddl 실행하는 계정 변경
jongmee Apr 2, 2024
a8677e5
docs: db 스키마 다이어그램 추가
jongmee Apr 2, 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ out/

### VS Code ###
.vscode/

## 설정 파일 ##
/src/main/resources
/src/test/resources
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# java-chess

## 게임 실행 방법
- 도커를 실행한 후 어플리케이션을 실행한다.
```
docker-compose -p chess up -d
```
- 도커를 종료한다.
```
docker-compose -p chess down
```

## DB 스키마 다이어그램
<div align = "center">
<img width="496" alt="스크린샷 2024-04-02 오후 2 35 45" src="https://github.com/jongmee/java-chess/assets/101439796/369730ad-ed12-4101-8bc4-b20eac01869a">
</div>

## 규칙 정리

- Rank: 체스판의 가로 줄 (1 ~ 8)
Expand All @@ -19,10 +34,10 @@

#### 입력

- [게임 준비] 시작/종료 명령어를 입력한다.
- start/end가 아니면 예외가 발생한다.
- [게임 중] move, 종료, 점수 출력 명령어를 입력한다.
- 각각 `move (source위치) (target위치)`, `end`, `status` 형식이 아니면 예외가 발생한다.
- [게임 준비] 시작/종료/게임로그 출력 명령어를 입력한다.
- start/end/logs가 아니면 예외가 발생한다.
- [게임 중] move, 종료, 합의 무승부, 점수 출력 명령어를 입력한다.
- 각각 `move (source위치) (target위치)`, `end`, `tie`, `status` 형식이 아니면 예외가 발생한다.

#### 출력

Expand All @@ -32,9 +47,12 @@

- 현재 차례의 남아 있는 기물들의 점수 합을 출력한다.

- 종료된 체스 게임 결과을 최신순 대로 출력한다.

### 비즈니스 기능

- **[Prepare]** 체스판을 초기화한다.
- 이전에 승부가 나지 않은 체스가 있다면 체스판을 새로 생성하지 않고 이전 체스판을 불러온다.
- 체스판은 8x8로 이루어져 있다.
- 체스판에서 말의 위치 값은 가로 위치는 왼쪽부터 `a ~ h`이고, 세로는 아래부터 위로 `1 ~ 8`이다.
- 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다.
Expand Down Expand Up @@ -87,7 +105,7 @@
- 자기 자신을 기준으로 상하좌우 대각선으로 움직인다.
- 1칸씩 움직일 수 있다.

- **[Run]** 현재 남아 있는 기물에 대한 점수를 구할 수 있다.
- **[Status]** 현재 남아 있는 기물에 대한 점수를 구할 수 있다.
- Queen: 9점
- Rook: 5점
- Bishop: 3점
Expand All @@ -96,3 +114,10 @@
- Pawn: 1점 (세로줄에 같은 진영의 Pawn은 각 0.5점)

- **[End]** 한 진영의 King이 잡히면 게임이 종료된다.
- 게임이 종료되기 전, 승패가 저장된다.

- **[Tie]** 플레이어가 서로 합의하여 무승부가 될 수 있다.
- 두 플레이어의 King이 모두 체스 보드에 있는 상황에서 합의하여 무승부가 될 수 있다.

- **[Logs]** 이전 게임 결과들을 조회할 수 있다.
- 최신 순대로 게임 시작 시간과 결과를 조회한다.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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")

Choose a reason for hiding this comment

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

runtimeOnly는 어떤 기능으로 동작할까요? implementation, compileOnly, api와 무슨 차이가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

compileOnly은 컴파일에 의존하는 모듈들을 컴파일패스에 설정하고, runtimeOnly는 런타임에 의존하는 모듈들을 런타임패스에 설정하는 것이 좋습니다. 따라서 runtimeOnly는 DB 의존성에 사용하는 것이 좋습니다.

implementation과 api의 차이는 잘 몰라서 추가로 찾아보았습니다!
implementation과 api는 컴파일패스, 런타임패스에 모두 저장하는데, api는 전이 의존성을 허용하고 implementation은 허용하지 않습니다. implementation이 컴파일이 빠르고 재컴파일 횟수가 줄어들어 더 권장됩니다. 또한 전이 종속성에 실수로 빠지지 않습니다.

Choose a reason for hiding this comment

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

전이 종속성에 실수로 빠지지 않습니다

api의 경우 해당 문제때문에 의존성의 방향이 한단계 밑으로 내려가서 사용을 지양하는 편이긴해요. 대부분의 예제가 그런 이유로 implementation을 예제로 뿌려주기도하구요.

}

java {
Expand Down
26 changes: 26 additions & 0 deletions docker/db/mysql/init/ddl_prod.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
GRANT ALL PRIVILEGES ON *.* TO 'user'@'%';
FLUSH PRIVILEGES;

CREATE DATABASE IF NOT EXISTS `chess` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

USE chess;

CREATE TABLE chess_board
(
id BIGINT NOT NULL AUTO_INCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
game_result VARCHAR(15) NOT NULL,
turn VARCHAR(10) NOT NULL,
PRIMARY KEY (id)
);

CREATE TABLE piece
(
file VARCHAR(10) NOT NULL,
`rank` INT NOT NULL,

Choose a reason for hiding this comment

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

(반영하지 않으셔도 됩니다.) 예약어는 현재처럼(`를 붙여야하는) 제약사항이 존재하기 때문에 가능하면 db에서 네이밍은 예약어를 피하려는 편입니다.

Copy link
Author

Choose a reason for hiding this comment

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

level2에서 db를 많이 활용할 때 예약어를 피해서 설계해보도록 하겠습니다! 감사합니다 😊

type VARCHAR(10) NOT NULL,
chess_board_id BIGINT NOT NULL,
side VARCHAR(10) NOT NULL,
FOREIGN KEY (`chess_board_id`) REFERENCES `chess_board` (`id`),
PRIMARY KEY (file, `rank`, chess_board_id)
);
23 changes: 23 additions & 0 deletions docker/db/mysql/init/ddl_test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CREATE DATABASE IF NOT EXISTS `chess_test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

USE chess_test;

CREATE TABLE chess_board
(
id BIGINT NOT NULL AUTO_INCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
game_result VARCHAR(15) NOT NULL,
turn VARCHAR(10) NOT NULL,
PRIMARY KEY (id)
);

CREATE TABLE piece
(
file VARCHAR(10) NOT NULL,
`rank` INT NOT NULL,
type VARCHAR(10) NOT NULL,
chess_board_id BIGINT NOT NULL,
side VARCHAR(10) NOT NULL,
FOREIGN KEY (`chess_board_id`) REFERENCES `chess_board` (`id`),
PRIMARY KEY (file, `rank`, chess_board_id)
);
15 changes: 15 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3.9"
services:
db:
image: mysql:8.0.28
platform: linux/x86_64
restart: always
ports:
- "13306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: Asia/Seoul
volumes:
- ./db/mysql/init:/docker-entrypoint-initdb.d
6 changes: 1 addition & 5 deletions src/main/java/chess/Application.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package chess;

import chess.controller.ChessGame;
import chess.view.input.InputView;
import chess.view.output.OutputView;

public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
ChessGame chessGame = new ChessGame(inputView, outputView);
ChessGame chessGame = new ChessGame();
chessGame.start();
}
}
16 changes: 7 additions & 9 deletions src/main/java/chess/controller/ChessGame.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
package chess.controller;

import chess.dao.util.ProductMySqlConnector;
import chess.service.ChessGameService;
import chess.view.input.InputView;
import chess.view.output.OutputView;

import java.util.Objects;
import java.util.function.Supplier;

public class ChessGame {
private final InputView inputView;
private final OutputView outputView;

public ChessGame(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}
private final InputView inputView = new InputView();
private final OutputView outputView = new OutputView();
private final ChessGameService chessGameService = new ChessGameService(new ProductMySqlConnector());

public void start() {
GameState gameState = retryOnException(this::prepare);
while (gameState.canContinue()) {
GameState currentGameState = gameState;
gameState = retryOnException(() -> currentGameState.run(inputView, outputView));
gameState = retryOnException(() -> currentGameState.run(inputView, outputView, chessGameService));
}
}

private GameState prepare() {
GameState prepare = new Prepare();
return prepare.run(inputView, outputView);
return prepare.run(inputView, outputView, chessGameService);
}

private <T> T retryOnException(Supplier<T> retryOperation) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/chess/controller/End.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package chess.controller;

import chess.service.ChessGameService;
import chess.view.input.InputView;
import chess.view.output.OutputView;

public class End implements GameState {

@Override
public GameState run(InputView inputView, OutputView outputView) {
public GameState run(InputView inputView, OutputView outputView, ChessGameService chessGameService) {
throw new UnsupportedOperationException("게임이 종료되어 게임을 실행할 수 없습니다.");
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/chess/controller/GameState.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package chess.controller;

import chess.service.ChessGameService;
import chess.view.input.InputView;
import chess.view.output.OutputView;

public interface GameState {
GameState run(InputView inputView, OutputView outputView);
GameState run(InputView inputView, OutputView outputView, ChessGameService chessGameService);
boolean canContinue();
}
33 changes: 33 additions & 0 deletions src/main/java/chess/controller/Move.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package chess.controller;

import chess.model.board.ChessBoard;
import chess.model.board.Turn;
import chess.service.ChessGameService;
import chess.view.input.InputView;
import chess.view.input.MoveArguments;
import chess.view.output.OutputView;

public class Move implements GameState {
private final ChessBoard chessBoard;
private final Turn turn;
private final MoveArguments moveArguments;

public Move(ChessBoard chessBoard, Turn turn, MoveArguments moveArguments) {
this.chessBoard = chessBoard;
this.turn = turn;
this.moveArguments = moveArguments;
}

@Override
public GameState run(InputView inputView, OutputView outputView, ChessGameService chessGameService) {
chessGameService.move(chessBoard, turn, moveArguments);
outputView.printChessBoard(chessBoard);
Turn nextTurn = chessGameService.saveNextTurn(chessBoard, turn);
return new Run(chessBoard, nextTurn);
}

@Override
public boolean canContinue() {
return true;
}
}
28 changes: 18 additions & 10 deletions src/main/java/chess/controller/Prepare.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
package chess.controller;

import chess.model.board.ChessBoard;
import chess.model.board.ChessBoardGenerator;
import chess.model.board.ChessBoardInitializer;
import chess.model.board.Turn;
import chess.model.piece.Side;
import chess.dao.dto.GameResultDto;
import chess.dao.dto.LatestChessBoardDto;
import chess.service.ChessGameService;
import chess.view.input.GameCommand;
import chess.view.input.InputView;
import chess.view.output.OutputView;

import java.util.List;

public class Prepare implements GameState {

@Override
public GameState run(InputView inputView, OutputView outputView) {
public GameState run(InputView inputView, OutputView outputView, ChessGameService chessGameService) {
GameCommand gameCommand = getFirstGameCommand(inputView);
if (gameCommand.isEnd()) {
return new End();
}
ChessBoardGenerator chessBoardInitializer = new ChessBoardInitializer();
ChessBoard chessBoard = new ChessBoard(chessBoardInitializer.create());
outputView.printChessBoard(chessBoard);
return Run.initializeWithFirstTurn(chessBoard);
if (gameCommand.isLogs()) {
showGameResults(outputView, chessGameService);
return new End();
}
LatestChessBoardDto latestChessBoardDto = chessGameService.createOrGetInitialChessBoard();
outputView.printChessBoard(latestChessBoardDto.chessBoard());
return new Run(latestChessBoardDto.chessBoard(), latestChessBoardDto.turn());
}

private GameCommand getFirstGameCommand(InputView inputView) {
String gameCommandInput = inputView.readGameCommand();
return GameCommand.createInPreparation(gameCommandInput);
}

private void showGameResults(OutputView outputView, ChessGameService chessGameService) {
List<GameResultDto> gameResults = chessGameService.findAllGameResults();
outputView.printGameResults(gameResults);
}

@Override
public boolean canContinue() {
return true;
Expand Down
34 changes: 15 additions & 19 deletions src/main/java/chess/controller/Run.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package chess.controller;

import chess.model.board.ChessBoard;
import chess.model.evaluation.PositionEvaluation;
import chess.model.board.Turn;
import chess.model.piece.Side;
import chess.model.position.Position;
import chess.model.evaluation.PositionEvaluation;
import chess.service.ChessGameService;
import chess.view.input.GameArguments;
import chess.view.input.GameCommand;
import chess.view.input.InputView;
import chess.view.input.MoveArguments;
import chess.view.output.OutputView;

public class Run implements GameState {
Expand All @@ -20,39 +18,37 @@ public Run(ChessBoard chessBoard, Turn turn) {
this.turn = turn;
}

public static Run initializeWithFirstTurn(ChessBoard chessBoard) {
return new Run(chessBoard, Turn.from(Side.WHITE));
}

@Override
public GameState run(InputView inputView, OutputView outputView) {
public GameState run(InputView inputView, OutputView outputView, ChessGameService chessGameService) {
if (chessBoard.canNotProgress()) {
return new Summarize(chessBoard);
}
GameArguments gameArguments = inputView.readMoveArguments();
return playByCommand(gameArguments, outputView);
}

private GameState playByCommand(GameArguments gameArguments, OutputView outputView) {
GameCommand gameCommand = gameArguments.gameCommand();
if (gameCommand.isEnd()) {
return new End();
}
if (gameCommand.isTie()) {
return new Summarize(chessBoard);
}
if (gameCommand.isMove()) {
move(gameArguments.moveArguments(), outputView);
return new Run(chessBoard, turn.getNextTurn());
return new Move(chessBoard, turn, gameArguments.moveArguments());
}
evaluateCurrentBoard(outputView);
return this;
}

private void move(MoveArguments moveArguments, OutputView outputView) {
Position source = moveArguments.createSourcePosition();
Position target = moveArguments.createTargetPosition();
chessBoard.move(source, target, turn);
outputView.printChessBoard(chessBoard);
}

private void evaluateCurrentBoard(OutputView outputView) {
PositionEvaluation positionEvaluation = chessBoard.evaluateCurrentBoard();
outputView.printPositionEvaluation(positionEvaluation);
}

@Override
public boolean canContinue() {
return chessBoard.canContinue();
return true;
}
}
Loading