diff --git a/.gitignore b/.gitignore index 6c018781387..6716fbc98f9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ out/ ### VS Code ### .vscode/ +.env diff --git a/build.gradle b/build.gradle index a0a012f5798..38cc59b87d7 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 28ff446a215..ffed3a254e9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/chess/Application.java b/src/main/java/chess/Application.java index 5a139c27750..f67050609d2 100644 --- a/src/main/java/chess/Application.java +++ b/src/main/java/chess/Application.java @@ -1,5 +1,7 @@ package chess; +import chess.game.ChessGameManager; + public class Application { public static void main(String[] args) { ChessGameManager chessGameManager = new ChessGameManager(); diff --git a/src/main/java/chess/board/Board.java b/src/main/java/chess/board/Board.java index 9b50264b625..826ea2ff3c6 100644 --- a/src/main/java/chess/board/Board.java +++ b/src/main/java/chess/board/Board.java @@ -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; @@ -74,6 +75,33 @@ public void verifyPath(Position start, Position target) { } } + public String convertBoardStateToString() { + StringBuilder stringBuilder = new StringBuilder(); + 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) { + 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); + } + } + } + public void checkPositionIsEmpty(Position position, Position target) { if (!this.findPiece(position).isEmpty() && !position.equals(target)) { throw new IllegalArgumentException("다른 기물이 존재해서 지나갈 수 없습니다."); diff --git a/src/main/java/chess/dao/ChessGameDao.java b/src/main/java/chess/dao/ChessGameDao.java new file mode 100644 index 00000000000..3aa3e162225 --- /dev/null +++ b/src/main/java/chess/dao/ChessGameDao.java @@ -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(); + + 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 = ?"; + + 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)"; + + 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(); + } 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(); + } + } +} diff --git a/src/main/java/chess/ChessGameManager.java b/src/main/java/chess/game/ChessGameManager.java similarity index 53% rename from src/main/java/chess/ChessGameManager.java rename to src/main/java/chess/game/ChessGameManager.java index ecc72cd75e7..b06eaad6a23 100644 --- a/src/main/java/chess/ChessGameManager.java +++ b/src/main/java/chess/game/ChessGameManager.java @@ -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; @@ -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); } } @@ -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'; @@ -69,4 +121,8 @@ public void verifyTurn(Piece piece) { throw new IllegalStateException("상대방 기물을 이동시킬 수 없습니다."); } } + + private boolean getTurnState() { + return turn.isSameTeam(Team.WHITE); + } } diff --git a/src/main/java/chess/piece/Team.java b/src/main/java/chess/piece/Team.java index 3472e5dd4e5..8837902f374 100644 --- a/src/main/java/chess/piece/Team.java +++ b/src/main/java/chess/piece/Team.java @@ -15,4 +15,9 @@ public Team changeTurn() { } return BLACK; } + + public Team getTurnByBinary(int n) { + if (n == 0) return Team.BLACK; + return Team.WHITE; + } } diff --git a/src/main/java/chess/ui/OutputView.java b/src/main/java/chess/ui/OutputView.java index 75f43ef8cc3..4ef5ca4065a 100644 --- a/src/main/java/chess/ui/OutputView.java +++ b/src/main/java/chess/ui/OutputView.java @@ -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"); } @@ -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 팀의 승리입니다."); diff --git a/src/main/java/chess/ui/PieceSymbol.java b/src/main/java/chess/ui/PieceSymbol.java index fd1f03e882e..7b018eff3b5 100644 --- a/src/main/java/chess/ui/PieceSymbol.java +++ b/src/main/java/chess/ui/PieceSymbol.java @@ -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, String> symbolMap = new HashMap<>(); + private static final Map, String> typeToSymbolMap = new HashMap<>(); + private static final Map 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); + } } diff --git a/src/main/java/chess/utils/DatabaseUtil.java b/src/main/java/chess/utils/DatabaseUtil.java new file mode 100644 index 00000000000..73e5314afbd --- /dev/null +++ b/src/main/java/chess/utils/DatabaseUtil.java @@ -0,0 +1,17 @@ +package chess.utils; + +import io.github.cdimascio.dotenv.Dotenv; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class DatabaseUtil { + private static final Dotenv dotenv = Dotenv.load(); + private static final String URL = dotenv.get("DB_URL"); + private static final String USER = dotenv.get("DB_USER"); + private static final String PASSWORD = dotenv.get("DB_PASSWORD"); + + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection(URL, USER, PASSWORD); + } +} diff --git a/src/test/java/chess/ChessGameManagerTest.java b/src/test/java/chess/ChessGameManagerTest.java index 2aa0356d73b..683f5ce6d42 100644 --- a/src/test/java/chess/ChessGameManagerTest.java +++ b/src/test/java/chess/ChessGameManagerTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import chess.board.Board; +import chess.game.ChessGameManager; import chess.piece.Pawn; import chess.piece.Piece; import chess.piece.Team; diff --git a/src/test/java/chess/database/DatabaseUtilTest.java b/src/test/java/chess/database/DatabaseUtilTest.java new file mode 100644 index 00000000000..7c0bb7aa01a --- /dev/null +++ b/src/test/java/chess/database/DatabaseUtilTest.java @@ -0,0 +1,22 @@ +package chess.database; + +import chess.utils.DatabaseUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import java.sql.Connection; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.*; + +public class DatabaseUtilTest { + @DisplayName("데이터베이스에 성공적으로 연결되는지 테스트") + @Test + void testDatabaseConnection() { + try (Connection connection = DatabaseUtil.getConnection()) { + assertNotNull(connection, "연결이 null일 수 없습니다"); + assertFalse(connection.isClosed(), "연결이 닫혀 있습니다"); + } catch (SQLException e) { + fail("연결 실패 : " + e.getMessage()); + } + } +}