Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ out/

### VS Code ###
.vscode/
.env
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ repositories {
dependencies {
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
implementation 'mysql:mysql-connector-java:8.0.26'
implementation 'io.github.cdimascio:dotenv-java:2.2.0'
}

java {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
Copy link
Owner

Choose a reason for hiding this comment

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

gradle 버전을 업데이트 한 이유는 뭔가요?

zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 2 additions & 0 deletions src/main/java/chess/Application.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package chess;

import chess.game.ChessGameManager;

public class Application {
public static void main(String[] args) {
ChessGameManager chessGameManager = new ChessGameManager();
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/chess/board/Board.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import chess.piece.*;
import chess.position.Position;
import chess.position.StartPiecePosition;
import chess.ui.PieceSymbol;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -74,6 +75,33 @@ public void verifyPath(Position start, Position target) {
}
}

public String convertBoardStateToString() {
StringBuilder stringBuilder = new StringBuilder();
Copy link
Owner

Choose a reason for hiding this comment

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

StringBuilder 사용 👍👍
String과 어떤 차이가 있나요?

for (int rank = Position.LAST_RANK; rank >= Position.FIRST_RANK; rank--) {
for (int file = Position.FIRST_FILE; file <= Position.LAST_FILE; file++) {
Position position = new Position(file, rank);
Piece piece = board.get(position);
stringBuilder.append(PieceSymbol.convertTypeToSymbol(piece));
}
}
return stringBuilder.toString();
}


public void loadBoardState(String boardState) {
Copy link
Owner

Choose a reason for hiding this comment

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

Board는 현재 실직적인 체스의 비즈니스 로직(기물들의 움직임 규칙, 게임 턴 관리...)을 담고 있는 객체로 보이는데, 보드의 상태를 문자열로 받아서 초기화 하는 기능이 여기에 있어도 괜찮을까요?

지금 형태라면 비즈니스 로직과 상관없이 DB에 저장하는 형태가 변경되는데도 (ex. 각 기물들이 기물 테이블에 개별 행으로 저장됨) 비즈니스 로직을 담당하고 있는 Board 객체도 같이 변경이 일어나야할 것 같아요.

board.clear();
Piece piece;
int index = 0;
for (int rank = Position.LAST_RANK; rank >= Position.FIRST_RANK; rank--) {
for (int file = Position.FIRST_FILE; file <= Position.LAST_FILE; file++) {
char symbol = boardState.charAt(index++);
piece = PieceSymbol.convertSymbolToPiece(symbol);
Position position = new Position(file, rank);
board.put(position, piece);
}
}
Comment on lines +93 to +102
Copy link
Owner

Choose a reason for hiding this comment

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

  1. Piece를 for문 안에서 사용하는데 미리 선언한 이유가 있나요?
  2. index는 무엇을 의미하나요? file과 rank를 조합한 값 같은데 굳이 하나의 변수가 더 필요한가요?
  3. DB에서 불러온 값을 온전히 믿을 수 있나요? 혹시 모든 DB의 값이 QQQQQQQQQQQ.. 처럼 되어있으면 서버에서는 어떤 행동을 취하는 게 옳을까요?

}

public void checkPositionIsEmpty(Position position, Position target) {
if (!this.findPiece(position).isEmpty() && !position.equals(target)) {
throw new IllegalArgumentException("다른 기물이 존재해서 지나갈 수 없습니다.");
Expand Down
116 changes: 116 additions & 0 deletions src/main/java/chess/dao/ChessGameDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package chess.dao;

import chess.board.Board;
import chess.utils.DatabaseUtil;
import chess.piece.Team;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class ChessGameDao {
public void createNewRoom() {
String query = "INSERT INTO game (boardState, turn, status) VALUES (?, ?, ?)";
Board board = new Board();
Comment on lines +12 to +14
Copy link
Owner

Choose a reason for hiding this comment

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

이렇게 데이터베이스와 직접 소통하는 기능에 대한 테스트는 왜 없나요?
어떻게 테스트할 수 있을까요?


try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setString(1, board.convertBoardStateToString());
pstmt.setBoolean(2, true);
pstmt.setString(3, "ready");
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}

public int selectRoom(int gameId) {
String query = "SELECT status FROM game WHERE gameId = ?";
Copy link
Owner

Choose a reason for hiding this comment

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

현재 보드 상태를 하나의 문자열로 쭉 이어서 저장하는 방법이 너무 참신하네요ㅋㅋㅋ
그런데 하나의 컬럼이 체스판 전체 데이터를 저장하고 있는 건 데이터베이스의 제 1 정규화 규칙을 어긴 것 같기도 한데 어떻게 생각하나요?

이 방식에는 다른 방식과 비교했을 때 어떤 장단점이 있을까요?


try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setInt(1, gameId); // gameId를 쿼리에 설정
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return gameId;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
throw new IllegalStateException("존재하지 않는 방 번호입니다.");
}

public void saveGame(int gameId, String boardState, boolean turn, String status) {
String query = "INSERT INTO game (gameId, boardState, turn, status) VALUES (?, ?, ?, ?) " + "ON DUPLICATE KEY UPDATE boardState = VALUES(boardState), turn = VALUES(turn), status = VALUES(status)";

Comment on lines +44 to +45
Copy link
Owner

Choose a reason for hiding this comment

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

ON DUPLICATE KEY UPDATE는 몇몇 DB에서만 한정적으로 사용 가능한 SQL 입니다. (ANSI 표준이 아닙니다)
이런 경우 DB의 종류가 바뀌면 서버가 정상작동하지 않을 수 있는데, 이를 어떻게 수정해줄 수 있을까요?

try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setInt(1, gameId);
pstmt.setString(2, boardState);
pstmt.setBoolean(3, turn);
pstmt.setString(4, status);
pstmt.executeUpdate();
Comment on lines +46 to +51
Copy link
Owner

Choose a reason for hiding this comment

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

preparedStatement 사용 👍👍
그냥 statement와 어떤 차이가 있나요?

} catch (SQLException e) {
e.printStackTrace();
}
}

public void updateCurrentBoard(int gameId, Board board) {
String query = "SELECT boardState, status FROM game WHERE gameId = ?";
try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setInt(1, gameId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
String boardState = rs.getString("boardState");
board.loadBoardState(boardState);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}

public Team getCurrentTurn(int gameId, Team turn) {
String query = "SELECT turn FROM game WHERE gameID = ?";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setInt(1, gameId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return turn.getTurnByBinary(rs.getInt("turn"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return turn;
}

public String getCurrentStatus(int gameId) {
String query = "SELECT status FROM game WHERE gameId = ?";

try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setInt(1, gameId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getString("status");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
throw new IllegalStateException("존재하지 않는 방입니다.");
}

public void changeStatus(int gameId) {
String query = "UPDATE game SET status = ? WHERE gameId = ?";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(query)) {
pstmt.setString(1, "end");
pstmt.setInt(2, gameId);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package chess;
package chess.game;

import chess.board.Board;
import chess.dao.ChessGameDao;
import chess.piece.Piece;
import chess.ui.InputView;
import chess.ui.OutputView;
Expand All @@ -10,29 +11,56 @@
public class ChessGameManager {
private static final String START_COMMAND = "start";
private static final String STATUS_COMMAND = "status";
private static final String END_COMMAND = "end";
private static final String CREATE_NEW_ROOM_COMMAND = "create";
private Team turn = Team.WHITE;
private boolean isGameOver = false;
private ChessGameDao chessGameDao;
private int gameId;

public ChessGameManager() {
this.chessGameDao = new ChessGameDao();
}

public void startNewGame() {
OutputView.printStartMessage();
if (InputView.inputCommand().equals(START_COMMAND)) {
String command = InputView.inputCommand();
if (command.equals(START_COMMAND)) {
Board board = new Board();
selectRoom();
loadGame(board);
OutputView.printBoard(board);
proceedGame(board);
}
if (command.equals(CREATE_NEW_ROOM_COMMAND)) {
createNewRoom();
startNewGame();
}
}

public void createNewRoom() {
OutputView.printCreateRoomMessage();
chessGameDao.createNewRoom();
}

public void proceedGame(Board board) {
if (isGameOver) {
changeStatus(gameId);
return;
}
String command = InputView.inputCommand();
if (command.equals(END_COMMAND)) {
saveGame(gameId, board);
return;
}
if (!command.equals(STATUS_COMMAND)) {
moveProcess(board, command);
saveGame(gameId, board);
proceedGame(board);
}
if (command.equals(STATUS_COMMAND)) {
OutputView.printEndGameByStatusMessage(board);
changeStatus(gameId);
}
}

Expand All @@ -57,6 +85,30 @@ public void moveProcess(Board board, String command) {
turn = turn.changeTurn();
}

public void selectRoom() {
OutputView.printSelectRoomMessage();
int roomNumber = Integer.parseInt(InputView.inputCommand());
if (chessGameDao.getCurrentStatus(roomNumber).equals(END_COMMAND)) {
throw new IllegalStateException("종료된 게임입니다.");
}
OutputView.printEnterRoomMessage(chessGameDao.selectRoom(roomNumber));
gameId = roomNumber;
}

public void saveGame(int gameId, Board board) {
String status = "playing";
chessGameDao.saveGame(gameId, board.convertBoardStateToString(), getTurnState(), status);
}

public void loadGame(Board board) {
chessGameDao.updateCurrentBoard(gameId, board);
turn = chessGameDao.getCurrentTurn(gameId, turn);
}

public void changeStatus(int gameId) {
chessGameDao.changeStatus(gameId);
}

public Position searchPosition(String location) {
int file = location.charAt(0) - 'a' + 1;
int rank = location.charAt(1) - '0';
Expand All @@ -69,4 +121,8 @@ public void verifyTurn(Piece piece) {
throw new IllegalStateException("상대방 기물을 이동시킬 수 없습니다.");
}
}

private boolean getTurnState() {
return turn.isSameTeam(Team.WHITE);
}
}
5 changes: 5 additions & 0 deletions src/main/java/chess/piece/Team.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ public Team changeTurn() {
}
return BLACK;
}

public Team getTurnByBinary(int n) {
if (n == 0) return Team.BLACK;
return Team.WHITE;
}
Comment on lines +19 to +22
Copy link
Owner

Choose a reason for hiding this comment

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

  1. google style guide에서는 한줄의 문법이어도 중괄호를 사용하기를 권합니다.
  2. n값이 -7, 6 처럼 이상한 값일때 검증이 필요하지 않을까요?

}
15 changes: 15 additions & 0 deletions src/main/java/chess/ui/OutputView.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
public class OutputView {
public static void printStartMessage() {
System.out.println("> 체스 게임을 시작합니다.");
System.out.println("> 방 생성 : create");
System.out.println("> 게임 시작 : start");
System.out.println("> 게임 종료 : end");
System.out.println("> 점수 출력 : status");
System.out.println("> 게임 이동 : move source위치 target위치 - 예. move b2 b3");
}

Expand All @@ -22,6 +24,19 @@ public static void printBoard(Board board) {
System.out.println();
}

public static void printCreateRoomMessage() {
System.out.println("새로운 방을 만들었습니다.");
System.out.println();
}

public static void printSelectRoomMessage() {
System.out.print("입장할 방 번호를 입력하세요 : ");
}

public static void printEnterRoomMessage(int roomNumber) {
System.out.println(roomNumber + "번 방에 입장했습니다.");
}

public static void printEndGameMessage(Team team) {
if (team.isSameTeam(Team.WHITE)) {
System.out.println("WHITE 팀의 승리입니다.");
Expand Down
38 changes: 29 additions & 9 deletions src/main/java/chess/ui/PieceSymbol.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
package chess.ui;

import chess.piece.*;

import java.util.HashMap;
import java.util.Map;

public class PieceSymbol {
private static final Map<Class<?>, String> symbolMap = new HashMap<>();
private static final Map<Class<?>, String> typeToSymbolMap = new HashMap<>();
Copy link
Owner

Choose a reason for hiding this comment

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

Class<?> 와 Type 은 뭐가 다를까요?

private static final Map<Character, Piece> pieceMap = new HashMap<>();

static {
symbolMap.put(Pawn.class, "P");
symbolMap.put(Rook.class, "R");
symbolMap.put(Bishop.class, "B");
symbolMap.put(Knight.class, "N");
symbolMap.put(Queen.class, "Q");
symbolMap.put(King.class, "K");
symbolMap.put(Empty.class, ".");
typeToSymbolMap.put(Pawn.class, "P");
typeToSymbolMap.put(Rook.class, "R");
typeToSymbolMap.put(Bishop.class, "B");
typeToSymbolMap.put(Knight.class, "N");
typeToSymbolMap.put(Queen.class, "Q");
typeToSymbolMap.put(King.class, "K");
typeToSymbolMap.put(Empty.class, ".");

pieceMap.put('p', new Pawn(Team.WHITE));
pieceMap.put('r', new Rook(Team.WHITE));
pieceMap.put('b', new Bishop(Team.WHITE));
pieceMap.put('n', new Knight(Team.WHITE));
pieceMap.put('q', new Queen(Team.WHITE));
pieceMap.put('k', new King(Team.WHITE));
pieceMap.put('P', new Pawn(Team.BLACK));
pieceMap.put('R', new Rook(Team.BLACK));
pieceMap.put('B', new Bishop(Team.BLACK));
pieceMap.put('N', new Knight(Team.BLACK));
pieceMap.put('Q', new Queen(Team.BLACK));
pieceMap.put('K', new King(Team.BLACK));
pieceMap.put('.', new Empty(Team.NONE));
}

private PieceSymbol() {
}

public static String convertTypeToSymbol(Piece piece) {
String symbol = symbolMap.get(piece.getClass());
String symbol = typeToSymbolMap.get(piece.getClass());

if (piece.isSameTeam(Team.WHITE)) {
return symbol.toLowerCase();
}
return symbol;
}

public static Piece convertSymbolToPiece(char symbol) {
return pieceMap.get(symbol);
}
}
Loading