Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[체스 - 3, 4단계] 아토(이혜린) 미션 제출합니다. #24

Open
wants to merge 36 commits into
base: hyxrxn
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
977d9e4
[체스 - 1, 2단계] 아토(이혜린) 미션 제출합니다. (#706)
hyxrxn Mar 25, 2024
c3bb337
feat(PieceType): 패키지 변경 및 타입별 스코어 확인
hyxrxn Mar 27, 2024
643c556
feat(Piece): 기물이 왕인지 폰인지 여부 판단
hyxrxn Mar 27, 2024
5a15ab0
feat(Board): 팀별 점수 계산
hyxrxn Mar 27, 2024
023804a
feat(Position): 같은 파일, 랭크 여부 판단
hyxrxn Mar 27, 2024
44a9ffc
feat(GameCommand): 커멘드에 status 추가
hyxrxn Mar 27, 2024
dff28c7
feat(ChessGame): 점수 출력 및 킹 잡힐시 게임 종료
hyxrxn Mar 27, 2024
9eabc7b
refactor(Board): 메서드 분리
hyxrxn Mar 27, 2024
2ba0d88
refactor(PieceTypeTest): import 변경
hyxrxn Mar 27, 2024
255cbb5
style(GameStatus): 개행 추가
hyxrxn Mar 27, 2024
05a3547
test(Team): 다음 팀 확인
hyxrxn Mar 28, 2024
724c9ef
feat(GameCommand): 시작할 때 불가능한 커맨드 입력 확인
hyxrxn Mar 30, 2024
46f55fc
refactor(Position): 메서드명 변경
hyxrxn Mar 30, 2024
69aca9d
refactor(GameStatus): 열거형 상수명 변경
hyxrxn Mar 30, 2024
6edbedd
feat(GameStatus): 플레이 중인 상태인지 확인
hyxrxn Mar 30, 2024
0b14727
test(GameCommand): 해당 커맨드 확인
hyxrxn Mar 30, 2024
b9b36cb
refactor(ChessGame): 변수명 변경
hyxrxn Mar 30, 2024
c198908
refactor(GameStatus): 메서드명 변경
hyxrxn Mar 30, 2024
6017958
test(GameStatus): 이긴 팀을 이용해 게임 상태 확인
hyxrxn Mar 30, 2024
53e2cd6
refactor(Board): 필요한 값만 사용할 수 있도록 메서드 리턴 형식 변경
hyxrxn Mar 30, 2024
b748637
feat(ScoreCalculator): 스코어 계산 로직 분리
hyxrxn Mar 30, 2024
16a3341
test(Board): 특정 팀의 말 및 폰의 위치 확인
hyxrxn Mar 30, 2024
dfeb489
refactor(ScoreCalculator): 가독성을 위해 상수 추출
hyxrxn Mar 30, 2024
38e8cad
refactor(ScoreCalculator): 불필요한 중복 필터 삭제
hyxrxn Mar 30, 2024
e493812
feat(docker-compose): 도커 실행을 위한 파일
hyxrxn Apr 1, 2024
499e57b
docs: 실행 방법 추가
hyxrxn Apr 1, 2024
43dbfde
feat(init): 초기 테이블 설정 정보
hyxrxn Apr 1, 2024
69a4af0
feat(build.gradle): 의존관계 추가
hyxrxn Apr 1, 2024
da26f30
feat(BoardDao): DB 삽입, 삭제, 조회 및 존재 확인
hyxrxn Apr 1, 2024
2940490
feat(ChessGame): Dao 이용해 정보 저장, 삭제 및 불러오기
hyxrxn Apr 1, 2024
426d51d
refactor(BoardDao): 메서드 분리
hyxrxn Apr 1, 2024
85ad50b
docs: 게임 진행 방법 추가
hyxrxn Apr 1, 2024
f70c091
refactor(GameStatus): 불필요한 static 제거
hyxrxn Apr 3, 2024
ec85ee9
refactor(ChessGame): 이미 존재하는 메서드 이용
hyxrxn Apr 3, 2024
ed1a5ba
refactor(ScoreCalculator): predicate을 분리하여 가독성 개선
hyxrxn Apr 3, 2024
2944796
refactor(BoardDao): 실패시 예외 발생
hyxrxn Apr 3, 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ out/

### VS Code ###
.vscode/

db/mysql/data/
75 changes: 72 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,76 @@
# java-chess

체스 미션 저장소
## 실행 방법
`java-chess`에서 `docker-compose -p chess up -d` 명령어 입력

## 우아한테크코스 코드리뷰
## 페어와 지킬 컨벤션
1. 클래스 정의 다음 줄은 공백으로 한다.
2. test code에 사용하는 메서드는 `static import`한다.
3. `this`는 같은 클래스의 객체가 파라미터로 넘어왔을 때, 파라미터 변수 명이 필드의 변수 명과 겹칠 때 사용한다.

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 기능 요구 사항

### 체스 말
- [x] 무슨 팀인지 알려준다.
- [x] 킹인지 확인한다.
- [x] 폰인지 확인한다.

### 체스 말 타입
- [x] 무슨 타입인지 알려준다.
- [x] 타입별 점수를 알려준다.

### 움직임
- [x] 이동 가능한지 판단한다.
- [x] 해당 경로를 구한다.

### 팀
- [x] 흰색, 검은색을 구분한다.
- [x] 다음 팀을 알려준다.

### 체스 보드
- [x] 체스 말 위치 초기화를 한다.
- [x] 해당 위치에 어떤 말이 있는지 알려준다.
- [x] 시작 위치의 말을 도착 위치로 옮긴다.
- [x] 시작 위치에 말이 없을 경우 예외
- [x] 말의 이동 범위를 넘어갈 경우 예외
- [x] 이동 경로에 다른 말이 있을 경우 예외
- [x] 마지막 위치에 적 말이 있을 경우 잡는다.
- [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다.
- [x] 흰색부터 번갈아가며 플레이한다.
- [x] 각 팀의 점수를 계산한다.
- [x] 왕이 잡히면 게임이 끝난다.

### 위치
- [x] 가로 위치(왼쪽부터 a~h)를 저장한다.
- [x] 세로 위치(아래부터 1~8)를 저장한다.
- [x] 서로 같은 위치인지 판단한다.
- [x] 다음 동, 서, 남, 북쪽 위치를 알려준다.
- [x] 서로 같은 랭크인지 판단한다.
- [x] 서로 같은 파일인지 판단한다.

### 출력
- [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다.

### 게임 진행
- [x] start 커맨드를 입력하면 게임에 사용할 보드를 생성한다.
- [x] 이미 보드 정보가 존재할 경우, 그 보드 정보를 불러온다.
- [x] 보드가 존재하지 않을 경우 새 보드를 생성한다.
- [x] end 커맨드를 입력하면 보드의 정보를 저장한다.
- [x] 왕이 잡혀 게임이 종료되면 저장된 보드의 정보를 삭제한다.

-----------
## 왕이 잡혀 게임이 종료되는 예시
### 흑 승리
move f2 f3
move e7 e5
move g2 g4
move d8 h4
move h2 h3
move h4 e1

### 백 승리
move e2 e3
move f7 f6
move d1 h5
move g8 h6
move h5 e8
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")
}

java {
Expand Down
12 changes: 12 additions & 0 deletions db/mysql/init/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS game (
gameId INT PRIMARY KEY,
turn VARCHAR(5) NOT NULL
);

CREATE TABLE IF NOT EXISTS board (
gameId INT,
file CHAR(5) NOT NULL,
`rank` CHAR(5) NOT NULL,
type VARCHAR(10) NOT NULL,
team VARCHAR(5) NOT NULL
);
18 changes: 18 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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_DATABASE: chess
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: Asia/Seoul
volumes:
- ./db/mysql/data:/var/lib/mysql
- ./db/mysql/config:/etc/mysql/conf.d
- ./db/mysql/init:/docker-entrypoint-initdb.d
Empty file removed src/main/java/.gitkeep
Empty file.
201 changes: 201 additions & 0 deletions src/main/java/chess/BoardDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package chess;

import chess.domain.Board;
import chess.domain.piece.Piece;
import chess.domain.piece.PieceType;
import chess.domain.piece.Team;
import chess.domain.position.File;
import chess.domain.position.Position;
import chess.domain.position.Rank;
import chess.dto.BoardDto;
import chess.dto.PieceDto;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class BoardDao {

private static final String SERVER = "localhost:13306"; // MySQL 서버 주소
private static final String DATABASE = "chess"; // MySQL DATABASE 이름
private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
private static final String USERNAME = "root"; // MySQL 서버 아이디
private static final String PASSWORD = "root"; // MySQL 서버 비밀번호

public Connection getConnection() {
try {
return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD);
} catch (final SQLException e) {
System.err.println("DB 연결 오류:" + e.getMessage());
e.printStackTrace();
return null;
}
}

public void add(final int gameId, final BoardDto boardDto) {
addGame(gameId, boardDto);
addBoard(gameId, boardDto);
}

private void addGame(final int gameId, final BoardDto boardDto) {
final var query = "INSERT INTO game VALUES(?, ?)";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
preparedStatement.setString(2, boardDto.getTurn());
preparedStatement.executeUpdate();
} catch (final SQLException e) {
throw new RuntimeException(e);
}
}

private void addBoard(final int gameId, final BoardDto boardDto) {
Map<Position, PieceDto> board = boardDto.getBoardDto();

final var query = "INSERT INTO board VALUES(?, ?, ?, ?, ?)";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
addPiece(gameId, board, preparedStatement);
} catch (final SQLException e) {
throw new RuntimeException(e);
}
}

private void addPiece(int gameId, Map<Position, PieceDto> board, PreparedStatement preparedStatement)
throws SQLException {
for (Entry<Position, PieceDto> pieceEntry : board.entrySet()) {
preparedStatement.setInt(1, gameId);
preparedStatement.setString(2, pieceEntry.getKey().getFile());
preparedStatement.setString(3, pieceEntry.getKey().getRank());
preparedStatement.setString(4, pieceEntry.getValue().getType());
preparedStatement.setString(5, pieceEntry.getValue().getTeam());
preparedStatement.executeUpdate();
}
}

public void delete(int gameId) {
deleteGame(gameId);
deleteBoard(gameId);
}

private void deleteGame(int gameId) {
final var query = "DELETE FROM game WHERE gameId = ?";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

private void deleteBoard(int gameId) {
final var query = "DELETE FROM board WHERE gameId = ?";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public Board loadBoard(int gameId) {
Team turn = getGameTurn(gameId);
Map<Position, Piece> pieces = getBoardPieces(gameId);
return new Board(pieces, turn);
}

Comment on lines +105 to +110
Copy link
Member

Choose a reason for hiding this comment

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

우리가 DAO 계층을 이용하는 이유는 계층 외부에서 봤을 때는 다음의 메시지 때문이라고 생각해

  • Board 넣어줘
  • Board 꺼내줘

이러한 측면에서 봤을 때 해당 메서드에서 도메인인 Board를 반환하도록 하는 것이 나는 위화감 없다고 생각해!

다만 다른 크루들은 데이터 베이스에서 꺼내온 값을 변환하는 과정을 이를 이용하는 서비스 레이어에 두거나 entity계층을 운용하던데 아토는 어떻게 생각해!?

private Team getGameTurn(int gameId) {
final String query = "SELECT turn FROM game WHERE gameId = ?";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
Team turn = getTeam(preparedStatement);
validateExistTeam(turn);
return turn;
} catch (SQLException e) {
throw new RuntimeException("게임 턴 정보 조회 실패", e);
}
}

private Team getTeam(PreparedStatement preparedStatement) throws SQLException {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
return getTeam(resultSet);
}
}

private Team getTeam(ResultSet resultSet) throws SQLException {
if (resultSet.next()) {
String turn = resultSet.getString("turn");
return Team.valueOf(turn.toUpperCase());
}
return null;
}

private void validateExistTeam(Team turn) {
if (turn == null) {
throw new IllegalArgumentException("해당 게임 ID를 찾을 수 없습니다.");
}
}

private Map<Position, Piece> getBoardPieces(int gameId) {
Map<Position, Piece> pieces = new HashMap<>();
final String query = "SELECT file, `rank`, type, team FROM board WHERE gameId = ?";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
getOnePiece(preparedStatement, pieces);
} catch (SQLException e) {
throw new RuntimeException("보드 정보 조회 실패", e);
}
return pieces;
}

private void getOnePiece(PreparedStatement preparedStatement, Map<Position, Piece> pieces) throws SQLException {
try (ResultSet resultSet = preparedStatement.executeQuery()) {
getOnePiece(pieces, resultSet);
}
}

private void getOnePiece(Map<Position, Piece> pieces, ResultSet resultSet) throws SQLException {
while (resultSet.next()) {
File file = File.valueOf(resultSet.getString("file").toUpperCase());
Rank rank = Rank.valueOf(resultSet.getString("rank").toUpperCase());
PieceType type = PieceType.valueOf(resultSet.getString("type").toUpperCase());
Team team = Team.valueOf(resultSet.getString("team").toUpperCase());
Position position = new Position(file, rank);
Piece piece = type.createPiece(team);
pieces.put(position, piece);
}
}

public boolean isExistBoard(int gameId) {
final String query = "SELECT EXISTS(SELECT 1 FROM game WHERE gameId = ?) AS Exist";
try (final var connection = getConnection();
final var preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, gameId);
return getExist(preparedStatement);
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}

private boolean getExist(PreparedStatement preparedStatement) throws SQLException {
try (ResultSet rs = preparedStatement.executeQuery()) {
return getExist(rs);
}
}

private boolean getExist(ResultSet rs) throws SQLException {
if (rs.next()) {
return rs.getBoolean("Exist");
}
return false;
}
}
15 changes: 15 additions & 0 deletions src/main/java/chess/ChessApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package chess;

import chess.view.InputView;
import chess.view.OutputView;

public class ChessApplication {

public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
ChessGame chessGame = new ChessGame(inputView, outputView);

chessGame.tryStart();
}
}
Loading