From 42d381002c4c9f5bc27cfa9a7f551ba0716a94e2 Mon Sep 17 00:00:00 2001 From: Junyoung Lee Date: Mon, 27 Apr 2020 10:38:28 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[=EC=8A=A4=ED=8B=B0=EC=B9=98]=20=EC=B2=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=8B=A4=EC=8A=B5=20?= =?UTF-8?q?1=EB=8B=A8=EA=B3=84=20=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C?= =?UTF-8?q?=EC=9E=85=EB=8B=88=EB=8B=A4=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat : 기존 Chess 코드 추가 * refactor : 기존 Chess 코드의 Application과 Controller 명명 수정 * refactor : JdbcTemplate 명명 수정 및 Spring Bean 등록 - 기존 JdbcTemplate 라이브러리와 충돌로 인한 명명 수정 - Dao, Service, ConnectionManager를 Spring Bean 등록 * feat : SpringChessController 추가 * feat : SpringChessController 구현 * refactor : controller 피드백 리팩토링 * refactor : database package 리팩토링 - ConnectionManager를 DataSource로 명명 수정. - CustomJdbcTemplate을 JdbcTemplate으로 명명 수정, 별칭 지정. - MySqlConnectionManager를 MySqlDataSource로 명명 수정, 별칭 지정. * refactor : 접근 지정자 수정 및 상수 추출 * refactor : BlackPawnStrategy와 WhitePawnStrategy를 PawnStrategy로 추상화 --- build.gradle | 33 +-- .../chess/ConsoleChessApplication.java | 37 +++ .../wooteco/chess/SparkChessApplication.java | 46 ++-- .../chess/controller/ChessController.java | 69 +++++- .../controller/SparkChessController.java | 86 +++++++ .../controller/SpringChessController.java | 78 ++++++ .../java/wooteco/chess/dao/ChessGameDao.java | 15 ++ .../wooteco/chess/dao/ChessHistoryDao.java | 15 ++ .../wooteco/chess/dao/MySqlChessGameDao.java | 62 +++++ .../chess/dao/MySqlChessHistoryDao.java | 66 +++++ .../wooteco/chess/database/DataSource.java | 10 + .../wooteco/chess/database/JdbcTemplate.java | 66 +++++ .../chess/database/MySqlDataSource.java | 46 ++++ .../database/PreparedStatementSetter.java | 10 + .../wooteco/chess/database/RowMapper.java | 10 + .../chess/domain/chessBoard/ChessBoard.java | 139 +++++++++++ .../chessBoard/ChessBoardInitializer.java | 81 ++++++ .../chess/domain/chessGame/ChessCommand.java | 101 ++++++++ .../chess/domain/chessGame/ChessGame.java | 114 +++++++++ .../chess/domain/chessGame/ChessStatus.java | 103 ++++++++ .../chess/domain/chessGame/CommandType.java | 50 ++++ .../chessGame/gameState/BlackTurnState.java | 17 ++ .../chessGame/gameState/ChessEndState.java | 26 ++ .../chessGame/gameState/ChessTurnState.java | 28 +++ .../domain/chessGame/gameState/EndState.java | 16 ++ .../domain/chessGame/gameState/GameState.java | 21 ++ .../chessGame/gameState/KingCaughtState.java | 16 ++ .../chessGame/gameState/WhiteTurnState.java | 17 ++ .../chess/domain/chessPiece/Catchable.java | 9 + .../chess/domain/chessPiece/ChessPiece.java | 102 ++++++++ .../chess/domain/chessPiece/Movable.java | 11 + .../chessPiece/pieceState/InitialState.java | 12 + .../chessPiece/pieceState/MovedState.java | 12 + .../chessPiece/pieceState/PieceState.java | 11 + .../pieceStrategy/BishopStrategy.java | 34 +++ .../pieceStrategy/BlackPawnStrategy.java | 21 ++ .../pieceStrategy/KingStrategy.java | 32 +++ .../pieceStrategy/KnightStrategy.java | 39 +++ .../pieceStrategy/PawnStrategy.java | 45 ++++ .../pieceStrategy/PieceStrategy.java | 13 + .../pieceStrategy/QueenStrategy.java | 34 +++ .../pieceStrategy/RookStrategy.java | 34 +++ .../pieceStrategy/WhitePawnStrategy.java | 21 ++ .../chessPiece/pieceType/PieceColor.java | 45 ++++ .../chessPiece/pieceType/PieceDirection.java | 38 +++ .../chessPiece/pieceType/PieceType.java | 80 ++++++ .../chess/domain/position/ChessFile.java | 60 +++++ .../chess/domain/position/ChessRank.java | 53 ++++ .../chess/domain/position/MoveDirection.java | 50 ++++ .../chess/domain/position/Position.java | 87 +++++++ .../java/wooteco/chess/entity/BaseEntity.java | 24 ++ .../wooteco/chess/entity/ChessGameEntity.java | 34 +++ .../chess/entity/ChessHistoryEntity.java | 76 ++++++ .../wooteco/chess/service/ChessService.java | 93 +++++++ .../chess/service/dto/ChessBoardDto.java | 52 ++++ .../chess/service/dto/ChessGameDto.java | 77 ++++++ .../chess/service/dto/ChessStatusDto.java | 50 ++++ .../chess/service/dto/ChessStatusDtos.java | 48 ++++ .../chess/service/dto/PieceColorDto.java | 42 ++++ .../chess/util/ChessBoardRenderer.java | 61 +++++ .../java/wooteco/chess/util/StringUtil.java | 21 ++ .../wooteco/chess/view/ConsoleInputView.java | 14 ++ .../wooteco/chess/view/ConsoleOutputView.java | 40 +++ .../wooteco/chess/web/PieceNameConverter.java | 41 ++++ src/main/resources/application.properties | 7 +- .../resources/public/images/black-bishop.png | Bin 0 -> 3178 bytes .../resources/public/images/black-king.png | Bin 0 -> 4191 bytes .../resources/public/images/black-knight.png | Bin 0 -> 4281 bytes .../resources/public/images/black-pawn.png | Bin 0 -> 2813 bytes .../resources/public/images/black-queen.png | Bin 0 -> 3631 bytes .../resources/public/images/black-rook.png | Bin 0 -> 2784 bytes .../resources/public/images/white-bishop.png | Bin 0 -> 4219 bytes .../resources/public/images/white-king.png | Bin 0 -> 6097 bytes .../resources/public/images/white-knight.png | Bin 0 -> 5355 bytes .../resources/public/images/white-pawn.png | Bin 0 -> 3877 bytes .../resources/public/images/white-queen.png | Bin 0 -> 5345 bytes .../resources/public/images/white-rook.png | Bin 0 -> 3554 bytes src/main/resources/public/style.css | 194 +++++++++++++++ src/main/resources/templates/chess.hbs | 129 ++++++++++ src/main/resources/templates/index.hbs | 20 +- src/main/resources/templates/result.hbs | 114 +++++++++ .../chess/dao/InMemoryChessGameDao.java | 45 ++++ .../chess/dao/InMemoryChessHistoryDao.java | 43 ++++ .../chess/dao/MySqlChessGameDaoTest.java | 133 ++++++++++ .../chess/dao/MySqlChessHistoryDaoTest.java | 131 ++++++++++ .../domain/chessBoard/ChessBoardTest.java | 232 ++++++++++++++++++ .../domain/chessGame/ChessCommandTest.java | 128 ++++++++++ .../chess/domain/chessGame/ChessGameTest.java | 199 +++++++++++++++ .../domain/chessGame/ChessStatusTest.java | 57 +++++ .../domain/chessGame/CommandTypeTest.java | 76 ++++++ .../gameState/BlackTurnStateTest.java | 26 ++ .../gameState/ChessEndStateTest.java | 42 ++++ .../gameState/ChessTurnStateTest.java | 40 +++ .../chessGame/gameState/EndStateTest.java | 32 +++ .../gameState/KingCaughtStateTest.java | 31 +++ .../gameState/WhiteTurnStateTest.java | 26 ++ .../domain/chessPiece/ChessPieceTest.java | 168 +++++++++++++ .../pieceState/InitialStateTest.java | 19 ++ .../chessPiece/pieceState/MovedStateTest.java | 19 ++ .../pieceStrategy/BishopStrategyTest.java | 50 ++++ .../pieceStrategy/BlackPawnStrategyTest.java | 72 ++++++ .../pieceStrategy/KingStrategyTest.java | 55 +++++ .../pieceStrategy/KnightStrategyTest.java | 55 +++++ .../pieceStrategy/QueenStrategyTest.java | 50 ++++ .../pieceStrategy/RookStrategyTest.java | 50 ++++ .../pieceStrategy/WhitePawnStrategyTest.java | 72 ++++++ .../chessPiece/pieceType/PieceColorTest.java | 45 ++++ .../chessPiece/pieceType/PieceTypeTest.java | 55 +++++ .../chess/domain/position/ChessFileTest.java | 79 ++++++ .../chess/domain/position/ChessRankTest.java | 63 +++++ .../domain/position/MoveDirectionTest.java | 32 +++ .../chess/domain/position/PositionTest.java | 125 ++++++++++ .../wooteco/chess/entity/BaseEntityTest.java | 36 +++ .../chess/entity/ChessGameEntityTest.java | 41 ++++ .../chess/entity/ChessHistoryEntityTest.java | 80 ++++++ .../chess/service/ChessServiceTest.java | 142 +++++++++++ .../chess/service/dto/ChessBoardDtoTest.java | 45 ++++ .../chess/service/dto/ChessGameDtoTest.java | 31 +++ .../chess/service/dto/ChessStatusDtoTest.java | 34 +++ .../service/dto/ChessStatusDtosTest.java | 39 +++ .../chess/service/dto/PieceColorDtoTest.java | 27 ++ .../chess/util/ChessBoardRendererTest.java | 43 ++++ .../wooteco/chess/util/StringUtilTest.java | 30 +++ .../chess/web/PieceNameConverterTest.java | 21 ++ 124 files changed, 6127 insertions(+), 50 deletions(-) create mode 100644 src/main/java/wooteco/chess/ConsoleChessApplication.java create mode 100644 src/main/java/wooteco/chess/controller/SparkChessController.java create mode 100644 src/main/java/wooteco/chess/controller/SpringChessController.java create mode 100644 src/main/java/wooteco/chess/dao/ChessGameDao.java create mode 100644 src/main/java/wooteco/chess/dao/ChessHistoryDao.java create mode 100644 src/main/java/wooteco/chess/dao/MySqlChessGameDao.java create mode 100644 src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java create mode 100644 src/main/java/wooteco/chess/database/DataSource.java create mode 100644 src/main/java/wooteco/chess/database/JdbcTemplate.java create mode 100644 src/main/java/wooteco/chess/database/MySqlDataSource.java create mode 100644 src/main/java/wooteco/chess/database/PreparedStatementSetter.java create mode 100644 src/main/java/wooteco/chess/database/RowMapper.java create mode 100644 src/main/java/wooteco/chess/domain/chessBoard/ChessBoard.java create mode 100644 src/main/java/wooteco/chess/domain/chessBoard/ChessBoardInitializer.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/ChessCommand.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/ChessGame.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/ChessStatus.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/CommandType.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/BlackTurnState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/ChessEndState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/ChessTurnState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/EndState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/GameState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/KingCaughtState.java create mode 100644 src/main/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnState.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/Catchable.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/ChessPiece.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/Movable.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceState/InitialState.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceState/MovedState.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceState/PieceState.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PawnStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PieceStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategy.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceColor.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceDirection.java create mode 100644 src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceType.java create mode 100644 src/main/java/wooteco/chess/domain/position/ChessFile.java create mode 100644 src/main/java/wooteco/chess/domain/position/ChessRank.java create mode 100644 src/main/java/wooteco/chess/domain/position/MoveDirection.java create mode 100644 src/main/java/wooteco/chess/domain/position/Position.java create mode 100644 src/main/java/wooteco/chess/entity/BaseEntity.java create mode 100644 src/main/java/wooteco/chess/entity/ChessGameEntity.java create mode 100644 src/main/java/wooteco/chess/entity/ChessHistoryEntity.java create mode 100644 src/main/java/wooteco/chess/service/ChessService.java create mode 100644 src/main/java/wooteco/chess/service/dto/ChessBoardDto.java create mode 100644 src/main/java/wooteco/chess/service/dto/ChessGameDto.java create mode 100644 src/main/java/wooteco/chess/service/dto/ChessStatusDto.java create mode 100644 src/main/java/wooteco/chess/service/dto/ChessStatusDtos.java create mode 100644 src/main/java/wooteco/chess/service/dto/PieceColorDto.java create mode 100644 src/main/java/wooteco/chess/util/ChessBoardRenderer.java create mode 100644 src/main/java/wooteco/chess/util/StringUtil.java create mode 100644 src/main/java/wooteco/chess/view/ConsoleInputView.java create mode 100644 src/main/java/wooteco/chess/view/ConsoleOutputView.java create mode 100644 src/main/java/wooteco/chess/web/PieceNameConverter.java create mode 100644 src/main/resources/public/images/black-bishop.png create mode 100644 src/main/resources/public/images/black-king.png create mode 100644 src/main/resources/public/images/black-knight.png create mode 100644 src/main/resources/public/images/black-pawn.png create mode 100644 src/main/resources/public/images/black-queen.png create mode 100644 src/main/resources/public/images/black-rook.png create mode 100644 src/main/resources/public/images/white-bishop.png create mode 100644 src/main/resources/public/images/white-king.png create mode 100644 src/main/resources/public/images/white-knight.png create mode 100644 src/main/resources/public/images/white-pawn.png create mode 100644 src/main/resources/public/images/white-queen.png create mode 100644 src/main/resources/public/images/white-rook.png create mode 100644 src/main/resources/public/style.css create mode 100644 src/main/resources/templates/chess.hbs create mode 100644 src/main/resources/templates/result.hbs create mode 100644 src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java create mode 100644 src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java create mode 100644 src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java create mode 100644 src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessBoard/ChessBoardTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/ChessCommandTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/ChessGameTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/ChessStatusTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/CommandTypeTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/BlackTurnStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/ChessEndStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/ChessTurnStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/EndStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/KingCaughtStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/ChessPieceTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceState/InitialStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceState/MovedStateTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategyTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceColorTest.java create mode 100644 src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceTypeTest.java create mode 100644 src/test/java/wooteco/chess/domain/position/ChessFileTest.java create mode 100644 src/test/java/wooteco/chess/domain/position/ChessRankTest.java create mode 100644 src/test/java/wooteco/chess/domain/position/MoveDirectionTest.java create mode 100644 src/test/java/wooteco/chess/domain/position/PositionTest.java create mode 100644 src/test/java/wooteco/chess/entity/BaseEntityTest.java create mode 100644 src/test/java/wooteco/chess/entity/ChessGameEntityTest.java create mode 100644 src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java create mode 100644 src/test/java/wooteco/chess/service/ChessServiceTest.java create mode 100644 src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java create mode 100644 src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java create mode 100644 src/test/java/wooteco/chess/service/dto/ChessStatusDtoTest.java create mode 100644 src/test/java/wooteco/chess/service/dto/ChessStatusDtosTest.java create mode 100644 src/test/java/wooteco/chess/service/dto/PieceColorDtoTest.java create mode 100644 src/test/java/wooteco/chess/util/ChessBoardRendererTest.java create mode 100644 src/test/java/wooteco/chess/util/StringUtilTest.java create mode 100644 src/test/java/wooteco/chess/web/PieceNameConverterTest.java diff --git a/build.gradle b/build.gradle index 5ba642c94c..2a1c4a8517 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { - id 'org.springframework.boot' version '2.2.5.RELEASE' - id 'io.spring.dependency-management' version '1.0.9.RELEASE' - id 'java' + id 'org.springframework.boot' version '2.2.5.RELEASE' + id 'io.spring.dependency-management' version '1.0.9.RELEASE' + id 'java' } group = 'com.example' @@ -9,23 +9,24 @@ version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'com.sparkjava:spark-core:2.9.0' - implementation 'com.sparkjava:spark-template-handlebars:2.7.1' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' - implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1' - implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.1' - testImplementation 'io.rest-assured:rest-assured:3.3.0' - testImplementation('org.springframework.boot:spring-boot-starter-test') { - exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' - } - runtimeOnly 'com.h2database:h2' + implementation 'com.sparkjava:spark-core:2.9.0' + implementation 'com.sparkjava:spark-template-handlebars:2.7.1' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' + implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1' + implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.1' + testImplementation 'io.rest-assured:rest-assured:3.3.0' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + runtimeOnly 'mysql:mysql-connector-java:8.0.16' + runtimeOnly 'com.h2database:h2' } test { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/wooteco/chess/ConsoleChessApplication.java b/src/main/java/wooteco/chess/ConsoleChessApplication.java new file mode 100644 index 0000000000..a7b4b09124 --- /dev/null +++ b/src/main/java/wooteco/chess/ConsoleChessApplication.java @@ -0,0 +1,37 @@ +package wooteco.chess; + +import java.util.List; + +import wooteco.chess.controller.ChessController; +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.ChessCommand; +import wooteco.chess.domain.chessGame.ChessGame; +import wooteco.chess.util.StringUtil; +import wooteco.chess.view.ConsoleInputView; +import wooteco.chess.view.ConsoleOutputView; + +public class ConsoleChessApplication { + + public static void main(String[] args) { + ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + ChessGame chessGame = ChessGame.from(chessBoard); + ChessController chessController = new ChessController(chessGame); + + ConsoleOutputView.printChessStart(); + if (isStartChessCommand()) { + chessController.run(); + } + ConsoleOutputView.printChessEnd(); + } + + private static boolean isStartChessCommand() { + List commandArguments = StringUtil.splitChessCommand(ConsoleInputView.inputChessCommand()); + + if (!ChessCommand.of(commandArguments).isStartChessCommand()) { + throw new IllegalArgumentException("게임을 시작해야 입력 가능한 명령어입니다."); + } + return true; + } + +} diff --git a/src/main/java/wooteco/chess/SparkChessApplication.java b/src/main/java/wooteco/chess/SparkChessApplication.java index 86a8e29510..ca1dd2c26b 100644 --- a/src/main/java/wooteco/chess/SparkChessApplication.java +++ b/src/main/java/wooteco/chess/SparkChessApplication.java @@ -1,22 +1,36 @@ package wooteco.chess; -import spark.ModelAndView; -import spark.template.handlebars.HandlebarsTemplateEngine; +import static spark.Spark.*; -import java.util.HashMap; -import java.util.Map; - -import static spark.Spark.get; +import wooteco.chess.controller.SparkChessController; +import wooteco.chess.dao.ChessGameDao; +import wooteco.chess.dao.ChessHistoryDao; +import wooteco.chess.dao.MySqlChessGameDao; +import wooteco.chess.dao.MySqlChessHistoryDao; +import wooteco.chess.database.DataSource; +import wooteco.chess.database.JdbcTemplate; +import wooteco.chess.database.MySqlDataSource; +import wooteco.chess.service.ChessService; public class SparkChessApplication { - public static void main(String[] args) { - get("/", (req, res) -> { - Map model = new HashMap<>(); - return render(model, "index.hbs"); - }); - } - - private static String render(Map model, String templatePath) { - return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath)); - } + + public static void main(String[] args) { + port(8080); + staticFileLocation("/public"); + + SparkChessController sparkChessController = initWebController(); + sparkChessController.run(); + } + + private static SparkChessController initWebController() { + DataSource dataSource = MySqlDataSource.getInstance(); + JdbcTemplate template = new JdbcTemplate(dataSource); + + ChessGameDao chessGameDao = new MySqlChessGameDao(template); + ChessHistoryDao chessHistoryDao = new MySqlChessHistoryDao(template); + ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + + return new SparkChessController(chessService); + } + } diff --git a/src/main/java/wooteco/chess/controller/ChessController.java b/src/main/java/wooteco/chess/controller/ChessController.java index 1a18c64438..d8d243518a 100644 --- a/src/main/java/wooteco/chess/controller/ChessController.java +++ b/src/main/java/wooteco/chess/controller/ChessController.java @@ -1,12 +1,67 @@ package wooteco.chess.controller; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; +import static wooteco.chess.util.StringUtil.*; +import static wooteco.chess.view.ConsoleInputView.*; +import static wooteco.chess.view.ConsoleOutputView.*; + +import java.util.Objects; + +import wooteco.chess.domain.chessGame.ChessCommand; +import wooteco.chess.domain.chessGame.ChessGame; +import wooteco.chess.util.ChessBoardRenderer; -@Controller public class ChessController { - @GetMapping("/") - public String index() { - return "index"; - } + + private final ChessGame chessGame; + + public ChessController(ChessGame chessGame) { + Objects.requireNonNull(chessGame, "체스 게임이 null입니다."); + this.chessGame = chessGame; + } + + public void run() { + do { + printChessBoard(ChessBoardRenderer.render(chessGame.getChessBoard())); + + ChessCommand chessCommand = receiveChessCommand(); + playChessGameBy(chessCommand); + } while (!isEndState()); + } + + private ChessCommand receiveChessCommand() { + ChessCommand chessCommand = ChessCommand.of(splitChessCommand(inputChessCommand())); + + if (chessCommand.isStartChessCommand()) { + throw new IllegalArgumentException("start 명령어은 최초 시작 때만 사용 가능합니다."); + } + return chessCommand; + } + + private void playChessGameBy(ChessCommand chessCommand) { + if (chessCommand.isMoveChessCommand()) { + chessGame.move(chessCommand); + } + if (chessCommand.isStatusChessCommand()) { + double score = chessGame.status(chessCommand); + printStatus(chessCommand.getStatusPieceColor(), score); + } + if (chessCommand.isEndChessCommand()) { + chessGame.end(); + } + } + + private boolean isEndState() { + if (!chessGame.isEndState()) { + return false; + } + return checkKingCaught(); + } + + private boolean checkKingCaught() { + if (chessGame.isKingCaught()) { + printKingCaught(chessGame.getCurrentPieceColor()); + } + return true; + } + } diff --git a/src/main/java/wooteco/chess/controller/SparkChessController.java b/src/main/java/wooteco/chess/controller/SparkChessController.java new file mode 100644 index 0000000000..4923a1b31d --- /dev/null +++ b/src/main/java/wooteco/chess/controller/SparkChessController.java @@ -0,0 +1,86 @@ +package wooteco.chess.controller; + +import static spark.Spark.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import spark.ModelAndView; +import spark.Request; +import spark.Response; +import spark.template.handlebars.HandlebarsTemplateEngine; +import wooteco.chess.service.ChessService; +import wooteco.chess.service.dto.ChessBoardDto; +import wooteco.chess.service.dto.ChessGameDto; +import wooteco.chess.service.dto.ChessStatusDtos; + +public class SparkChessController { + + private static final HandlebarsTemplateEngine HANDLEBARS_TEMPLATE_ENGINE = new HandlebarsTemplateEngine(); + + private final ChessService chessService; + + public SparkChessController(final ChessService chessService) { + Objects.requireNonNull(chessService, "체스 서비스가 null입니다."); + this.chessService = chessService; + } + + public void run() { + get("/", this::renderStartPage); + get("/chess", (request, response) -> renderGame(chessService.loadChessGame())); + + post("/chess_play", this::playChessGame); + post("/chess_new", this::newChessGame); + post("/chess_end", this::endChessGame); + } + + private String renderStartPage(final Request request, final Response response) { + return render(new HashMap<>(), "index.hbs"); + } + + private String playChessGame(final Request request, final Response response) { + final String sourcePosition = request.queryParams("sourcePosition").trim(); + final String targetPosition = request.queryParams("targetPosition").trim(); + final ChessGameDto chessGameDto = chessService.playChessGame(sourcePosition, targetPosition); + + if (chessGameDto.isEndState()) { + return renderResult(chessGameDto); + } + return renderGame(chessGameDto); + } + + private String newChessGame(final Request request, final Response response) { + return renderGame(chessService.createChessGame()); + } + + private String endChessGame(final Request request, final Response response) { + return renderResult(chessService.endChessGame()); + } + + private String renderGame(final ChessGameDto chessGameDto) { + final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); + final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); + + final Map model = new HashMap<>(chessBoardDto.getChessBoard()); + model.put("piece_color", chessGameDto.getPieceColorDto()); + model.put("status", chessStatusDtos.getChessStatusDtos()); + return render(model, "chess.hbs"); + } + + private String renderResult(final ChessGameDto chessGameDto) { + final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); + final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); + + final Map model = new HashMap<>(chessBoardDto.getChessBoard()); + model.put("is_king_caught", chessGameDto.isKingCaught()); + model.put("piece_color", chessGameDto.getPieceColorDto()); + model.put("status", chessStatusDtos.getChessStatusDtos()); + return render(model, "result.hbs"); + } + + private static String render(Map model, String templatePath) { + return HANDLEBARS_TEMPLATE_ENGINE.render(new ModelAndView(model, templatePath)); + } + +} diff --git a/src/main/java/wooteco/chess/controller/SpringChessController.java b/src/main/java/wooteco/chess/controller/SpringChessController.java new file mode 100644 index 0000000000..5aa1c91747 --- /dev/null +++ b/src/main/java/wooteco/chess/controller/SpringChessController.java @@ -0,0 +1,78 @@ +package wooteco.chess.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import wooteco.chess.service.ChessService; +import wooteco.chess.service.dto.ChessBoardDto; +import wooteco.chess.service.dto.ChessGameDto; +import wooteco.chess.service.dto.ChessStatusDtos; + +@Controller +public class SpringChessController { + + private final ChessService chessService; + + public SpringChessController(final ChessService chessService) { + this.chessService = chessService; + } + + @GetMapping("/") + public String start() { + return "index"; + } + + @GetMapping("/chess") + public String loadChessGame(final Model model) { + final ChessGameDto chessGameDto = chessService.loadChessGame(); + + return renderGame(chessGameDto, model); + } + + @PostMapping("/chess_play") + public String playChessGame(@RequestParam final String sourcePosition, @RequestParam final String targetPosition, + final Model model) { + final ChessGameDto chessGameDto = chessService.playChessGame(sourcePosition.trim(), targetPosition.trim()); + + if (chessGameDto.isEndState()) { + return renderResult(chessGameDto, model); + } + return renderGame(chessGameDto, model); + } + + @PostMapping("/chess_new") + public String newChessGame(final Model model) { + return renderGame(chessService.createChessGame(), model); + } + + @PostMapping("/chess_end") + public String endChessGame(final Model model) { + final ChessGameDto chessGameDto = chessService.endChessGame(); + return renderResult(chessGameDto, model); + } + + private String renderGame(final ChessGameDto chessGameDto, final Model model) { + final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); + final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); + + model.addAllAttributes(chessBoardDto.getChessBoard()); + model.addAttribute("piece_color", chessGameDto.getPieceColorDto()); + model.addAttribute("status", chessStatusDtos.getChessStatusDtos()); + return "chess"; + } + + private String renderResult(final ChessGameDto chessGameDto, final Model model) { + final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); + final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); + + model.addAllAttributes(chessBoardDto.getChessBoard()); + model.addAttribute("is_king_caught", chessGameDto.isKingCaught()); + model.addAttribute("piece_color", chessGameDto.getPieceColorDto()); + model.addAttribute("status", chessStatusDtos.getChessStatusDtos()); + return "result"; + } + +} diff --git a/src/main/java/wooteco/chess/dao/ChessGameDao.java b/src/main/java/wooteco/chess/dao/ChessGameDao.java new file mode 100644 index 0000000000..402b425a76 --- /dev/null +++ b/src/main/java/wooteco/chess/dao/ChessGameDao.java @@ -0,0 +1,15 @@ +package wooteco.chess.dao; + +import wooteco.chess.entity.ChessGameEntity; + +public interface ChessGameDao { + + long add(final ChessGameEntity entity); + + long findMaxGameId(); + + boolean isEmpty(); + + void deleteAll(); + +} diff --git a/src/main/java/wooteco/chess/dao/ChessHistoryDao.java b/src/main/java/wooteco/chess/dao/ChessHistoryDao.java new file mode 100644 index 0000000000..9fe17d0689 --- /dev/null +++ b/src/main/java/wooteco/chess/dao/ChessHistoryDao.java @@ -0,0 +1,15 @@ +package wooteco.chess.dao; + +import java.util.List; + +import wooteco.chess.entity.ChessHistoryEntity; + +public interface ChessHistoryDao { + + List findAllByGameId(final long gameId); + + void add(final ChessHistoryEntity entity); + + void deleteAll(); + +} diff --git a/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java b/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java new file mode 100644 index 0000000000..ecdb88ee4b --- /dev/null +++ b/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java @@ -0,0 +1,62 @@ +package wooteco.chess.dao; + +import java.sql.ResultSet; +import java.sql.Timestamp; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import wooteco.chess.database.JdbcTemplate; +import wooteco.chess.entity.ChessGameEntity; + +@Repository +public class MySqlChessGameDao implements ChessGameDao { + + public static final long EMPTY_CHESS_GAME = 0L; + private static final String CHESS_GAME_TABLE = "chess_games"; + + private final JdbcTemplate jdbcTemplate; + + public MySqlChessGameDao(@Qualifier("CustomJdbcTemplate") final JdbcTemplate jdbcTemplate) { + Objects.requireNonNull(jdbcTemplate, "JdbcTemplate이 null입니다."); + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public long add(final ChessGameEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + final String query = "INSERT INTO " + CHESS_GAME_TABLE + " (created_time) VALUES (?)"; + + return jdbcTemplate.executeUpdate(query, preparedStatement -> { + preparedStatement.setTimestamp(1, Timestamp.valueOf(entity.getCreatedTime())); + }); + } + + @Override + public long findMaxGameId() { + final String query = "SELECT MAX(game_id) AS max_id FROM " + CHESS_GAME_TABLE; + + return jdbcTemplate.executeQuery(query, resultSet -> { + if (resultSet.next()) { + return resultSet.getLong("max_id"); + } + return EMPTY_CHESS_GAME; + }); + } + + @Override + public boolean isEmpty() { + final String query = "SELECT * FROM " + CHESS_GAME_TABLE; + + return !jdbcTemplate.executeQuery(query, ResultSet::next); + } + + @Override + public void deleteAll() { + final String query = "DELETE FROM " + CHESS_GAME_TABLE; + + jdbcTemplate.executeUpdate(query); + } + +} diff --git a/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java b/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java new file mode 100644 index 0000000000..356edf85f3 --- /dev/null +++ b/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java @@ -0,0 +1,66 @@ +package wooteco.chess.dao; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Repository; + +import wooteco.chess.database.JdbcTemplate; +import wooteco.chess.entity.ChessHistoryEntity; + +@Repository +public class MySqlChessHistoryDao implements ChessHistoryDao { + + private static final String CHESS_HISTORY_TABLE = "chess_histories"; + + private final JdbcTemplate jdbcTemplate; + + public MySqlChessHistoryDao(@Qualifier("CustomJdbcTemplate") final JdbcTemplate jdbcTemplate) { + Objects.requireNonNull(jdbcTemplate, "JdbcTemplate이 null입니다."); + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public List findAllByGameId(final long gameId) { + final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE + " WHERE game_id = ?"; + + return jdbcTemplate.executeQuery(query, resultSet -> { + final List entities = new ArrayList<>(); + + while (resultSet.next()) { + entities.add(ChessHistoryEntity.of( + resultSet.getLong("history_id"), + resultSet.getLong("game_id"), + resultSet.getString("start"), + resultSet.getString("end"), + resultSet.getTimestamp("created_time").toLocalDateTime())); + } + return entities; + }, preparedStatement -> preparedStatement.setLong(1, gameId)); + } + + @Override + public void add(final ChessHistoryEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + final String query = + "INSERT INTO " + CHESS_HISTORY_TABLE + " (game_id, start, end, created_time) VALUES (?, ?, ?, ?)"; + + jdbcTemplate.executeUpdate(query, preparedStatement -> { + preparedStatement.setLong(1, entity.getGameId()); + preparedStatement.setString(2, entity.getStart()); + preparedStatement.setString(3, entity.getEnd()); + preparedStatement.setTimestamp(4, Timestamp.valueOf(entity.getCreatedTime())); + }); + } + + @Override + public void deleteAll() { + final String query = "DELETE FROM " + CHESS_HISTORY_TABLE; + + jdbcTemplate.executeUpdate(query); + } + +} diff --git a/src/main/java/wooteco/chess/database/DataSource.java b/src/main/java/wooteco/chess/database/DataSource.java new file mode 100644 index 0000000000..a2779f2a1a --- /dev/null +++ b/src/main/java/wooteco/chess/database/DataSource.java @@ -0,0 +1,10 @@ +package wooteco.chess.database; + +import java.sql.Connection; + +// TODO: 2020/04/25 명명 수정하기 -> DataSource +public interface DataSource { + + Connection getConnection(); + +} diff --git a/src/main/java/wooteco/chess/database/JdbcTemplate.java b/src/main/java/wooteco/chess/database/JdbcTemplate.java new file mode 100644 index 0000000000..9abfb0c2aa --- /dev/null +++ b/src/main/java/wooteco/chess/database/JdbcTemplate.java @@ -0,0 +1,66 @@ +package wooteco.chess.database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component("CustomJdbcTemplate") +public class JdbcTemplate { + + private final DataSource dataSource; + + public JdbcTemplate(@Qualifier("CustomDataSource") final DataSource dataSource) { + this.dataSource = dataSource; + } + + public T executeQuery(String query, RowMapper rowMapper, PreparedStatementSetter preparedStatementSetter) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(query) + ) { + preparedStatementSetter.setArgument(preparedStatement); + return rowMapper.mapRow(preparedStatement.executeQuery()); + } catch (SQLException exception) { + System.err.println(exception.getMessage()); + } + return null; + } + + public T executeQuery(String query, RowMapper rowMapper) { + return executeQuery(query, rowMapper, preparedStatement -> { + }); + } + + public Long executeUpdate(String query, PreparedStatementSetter preparedStatementSetter) { + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS) + ) { + preparedStatementSetter.setArgument(preparedStatement); + preparedStatement.executeUpdate(); + return generatedKey(preparedStatement); + } catch (SQLException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + private Long generatedKey(final PreparedStatement preparedStatement) { + try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) { + if (resultSet.next()) { + return resultSet.getLong(1); + } + return null; + } catch (SQLException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + public Long executeUpdate(String query) { + return executeUpdate(query, preparedStatement -> { + }); + } + +} diff --git a/src/main/java/wooteco/chess/database/MySqlDataSource.java b/src/main/java/wooteco/chess/database/MySqlDataSource.java new file mode 100644 index 0000000000..5199bccde1 --- /dev/null +++ b/src/main/java/wooteco/chess/database/MySqlDataSource.java @@ -0,0 +1,46 @@ +package wooteco.chess.database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.springframework.stereotype.Component; + +@Component("CustomDataSource") +public class MySqlDataSource implements DataSource { + + private static final String server = "127.0.0.1:13306"; // MySQL 서버 주소 + private static final String database = "woowa_level_01_chess"; // MySQL DATABASE 이름 + private static final String option = "?useSSL=false&serverTimezone=UTC"; + private static final String userName = "root"; // MySQL 서버 아이디 + private static final String password = "root"; // MySQL 서버 비밀번호 + private static final String CONNECTION_FORMAT = "jdbc:mysql://%s/%s%s"; + + private MySqlDataSource() { + } + + public static MySqlDataSource getInstance() { + return LazyHolder.INSTANCE; + } + + @Override + public Connection getConnection() { + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + return DriverManager.getConnection(String.format(CONNECTION_FORMAT, server, database, option), userName, + password); + } catch (ClassNotFoundException e) { + System.err.println("JDBC Driver load 오류: " + e.getMessage()); + e.printStackTrace(); + } catch (SQLException e) { + System.err.println("연결 오류:" + e.getMessage()); + e.printStackTrace(); + } + return null; + } + + private static class LazyHolder { + private static final MySqlDataSource INSTANCE = new MySqlDataSource(); + } + +} diff --git a/src/main/java/wooteco/chess/database/PreparedStatementSetter.java b/src/main/java/wooteco/chess/database/PreparedStatementSetter.java new file mode 100644 index 0000000000..86042a15bb --- /dev/null +++ b/src/main/java/wooteco/chess/database/PreparedStatementSetter.java @@ -0,0 +1,10 @@ +package wooteco.chess.database; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public interface PreparedStatementSetter { + + void setArgument(PreparedStatement preparedStatement) throws SQLException; + +} diff --git a/src/main/java/wooteco/chess/database/RowMapper.java b/src/main/java/wooteco/chess/database/RowMapper.java new file mode 100644 index 0000000000..94c39cdf9d --- /dev/null +++ b/src/main/java/wooteco/chess/database/RowMapper.java @@ -0,0 +1,10 @@ +package wooteco.chess.database; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public interface RowMapper { + + T mapRow(ResultSet resultSet) throws SQLException; + +} diff --git a/src/main/java/wooteco/chess/domain/chessBoard/ChessBoard.java b/src/main/java/wooteco/chess/domain/chessBoard/ChessBoard.java new file mode 100644 index 0000000000..4de287c611 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessBoard/ChessBoard.java @@ -0,0 +1,139 @@ +package wooteco.chess.domain.chessBoard; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import wooteco.chess.domain.chessGame.ChessStatus; +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.position.MoveDirection; +import wooteco.chess.domain.position.Position; + +public class ChessBoard { + + private final Map chessBoard; + + public ChessBoard(final Map chessBoard) { + Objects.requireNonNull(chessBoard, "체스 보드가 null입니다."); + this.chessBoard = chessBoard; + } + + private void validate(final Position position) { + Objects.requireNonNull(position, "체스 위치가 null입니다."); + } + + private void validate(final Position sourcePosition, final Position targetPosition) { + Objects.requireNonNull(sourcePosition, "소스 위치가 null입니다."); + Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); + } + + public boolean isKingOn(final Position position) { + validate(position); + final ChessPiece chessPiece = chessBoard.get(position); + + if (Objects.isNull(chessPiece)) { + return false; + } + return chessPiece.isKing(); + } + + public boolean isChessPieceOn(final Position position) { + validate(position); + return chessBoard.containsKey(position); + } + + public boolean isSamePieceColorOn(final Position position, final PieceColor pieceColor) { + validate(position); + Objects.requireNonNull(pieceColor, "피스 색상이 null입니다."); + return chessBoard.get(position).isSame(pieceColor); + } + + public boolean canLeapChessPieceOn(final Position position) { + validate(position); + return chessBoard.get(position).canLeap(); + } + + public void checkChessPieceExistInRoute(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + final MoveDirection checkingDirection = findDirectionFrom(sourcePosition, targetPosition); + Position checkingPosition = checkingDirection.move(sourcePosition); + + while (!checkingPosition.equals(targetPosition) && isVacantOn(checkingPosition)) { + checkingPosition = checkingDirection.move(checkingPosition); + } + } + + private MoveDirection findDirectionFrom(final Position sourcePosition, final Position targetPosition) { + return Arrays.stream(MoveDirection.values()) + .filter(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("체스 피스가 이동할 수 없는 위치를 입력하였습니다.")); + } + + // NOTE: 2020/04/26 메서드명 고민해보기 + private boolean isVacantOn(final Position checkingPosition) { + if (Objects.nonNull(chessBoard.get(checkingPosition))) { + throw new IllegalArgumentException("체스 피스의 이동 경로에 다른 체스 피스가 존재합니다."); + } + return true; + } + + public void checkCanMove(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + final ChessPiece sourceChessPiece = chessBoard.get(sourcePosition); + + if (!sourceChessPiece.canMove(sourcePosition, targetPosition)) { + throw new IllegalArgumentException("체스 피스가 이동할 수 없습니다."); + } + } + + public void checkCanCatch(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + final ChessPiece sourceChessPiece = chessBoard.get(sourcePosition); + final ChessPiece targetChessPiece = chessBoard.get(targetPosition); + + if (sourceChessPiece.isSamePieceColor(targetChessPiece) + || !sourceChessPiece.canCatch(sourcePosition, targetPosition)) { + throw new IllegalArgumentException("체스 피스를 잡을 수 없습니다."); + } + } + + public void moveChessPiece(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + final ChessPiece sourceChessPiece = chessBoard.get(sourcePosition); + chessBoard.put(targetPosition, sourceChessPiece); + chessBoard.remove(sourcePosition); + } + + public ChessStatus calculateStatus() { + return ChessStatus.of(chessBoard); + } + + public String getChessPieceNameOn(final Position position) { + validate(position); + return chessBoard.get(position).getName(); + } + + public Map getChessBoard() { + return chessBoard; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessBoard that = (ChessBoard)object; + return Objects.equals(chessBoard, that.chessBoard); + } + + @Override + public int hashCode() { + return Objects.hash(chessBoard); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessBoard/ChessBoardInitializer.java b/src/main/java/wooteco/chess/domain/chessBoard/ChessBoardInitializer.java new file mode 100644 index 0000000000..b27e136b9a --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessBoard/ChessBoardInitializer.java @@ -0,0 +1,81 @@ +package wooteco.chess.domain.chessBoard; + +import java.util.HashMap; +import java.util.Map; + +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.position.ChessFile; +import wooteco.chess.domain.position.ChessRank; +import wooteco.chess.domain.position.Position; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; + +public class ChessBoardInitializer { + + private static final int WHITE_OTHERS_INIT_RANK = 1; + private static final int WHITE_PAWN_INIT_RANK = 2; + private static final int BLACK_PAWN_INIT_RANK = 7; + private static final int BLACK_OTHERS_INIT_RANK = 8; + + public static Map create() { + final Map chessBoard = new HashMap<>(); + + setWhitePawn(chessBoard); + setWhiteOthersBy(chessBoard); + setBlackPawn(chessBoard); + setBlackOthersBy(chessBoard); + return chessBoard; + } + + private static void setWhitePawn(final Map chessBoard) { + for (ChessFile chessFile : ChessFile.values()) { + chessBoard.put(Position.of(chessFile, ChessRank.from(WHITE_PAWN_INIT_RANK)), new ChessPiece( + PieceType.WHITE_PAWN)); + } + } + + private static void setBlackPawn(final Map chessBoard) { + for (ChessFile chessFile : ChessFile.values()) { + chessBoard.put(Position.of(chessFile, ChessRank.from(BLACK_PAWN_INIT_RANK)), new ChessPiece( + PieceType.BLACK_PAWN)); + } + } + + private static void setWhiteOthersBy(final Map chessBoard) { + chessBoard.put(Position.of(ChessFile.A, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_ROOK)); + chessBoard.put(Position.of(ChessFile.B, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_KNIGHT)); + chessBoard.put(Position.of(ChessFile.C, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_BISHOP)); + chessBoard.put(Position.of(ChessFile.D, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_QUEEN)); + chessBoard.put(Position.of(ChessFile.E, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_KING)); + chessBoard.put(Position.of(ChessFile.F, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_BISHOP)); + chessBoard.put(Position.of(ChessFile.G, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_KNIGHT)); + chessBoard.put(Position.of(ChessFile.H, ChessRank.from(WHITE_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.WHITE_ROOK)); + } + + private static void setBlackOthersBy(final Map chessBoard) { + chessBoard.put(Position.of(ChessFile.A, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_ROOK)); + chessBoard.put(Position.of(ChessFile.B, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_KNIGHT)); + chessBoard.put(Position.of(ChessFile.C, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_BISHOP)); + chessBoard.put(Position.of(ChessFile.D, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_QUEEN)); + chessBoard.put(Position.of(ChessFile.E, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_KING)); + chessBoard.put(Position.of(ChessFile.F, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_BISHOP)); + chessBoard.put(Position.of(ChessFile.G, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_KNIGHT)); + chessBoard.put(Position.of(ChessFile.H, ChessRank.from(BLACK_OTHERS_INIT_RANK)), new ChessPiece( + PieceType.BLACK_ROOK)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/ChessCommand.java b/src/main/java/wooteco/chess/domain/chessGame/ChessCommand.java new file mode 100644 index 0000000000..d6c5594d5c --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/ChessCommand.java @@ -0,0 +1,101 @@ +package wooteco.chess.domain.chessGame; + +import java.util.List; +import java.util.Objects; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.position.Position; + +public class ChessCommand { + + private static final int COMMAND_STATUS_INDEX = 0; + private static final int SOURCE_POSITION_INDEX = 1; + private static final int TARGET_POSITION_INDEX = 2; + private static final int STATUS_PIECE_COLOR_INDEX = 1; + + private final CommandType commandType; + private final List commandArguments; + + private ChessCommand(final CommandType commandType, final List commandArguments) { + this.commandType = commandType; + this.commandArguments = commandArguments; + } + + public static ChessCommand of(final List commandArguments) { + validate(commandArguments); + final CommandType commandType = CommandType.of(commandArguments.get(COMMAND_STATUS_INDEX)); + + if (!commandType.isCorrectArgumentsSize(commandArguments.size())) { + throw new IllegalArgumentException("유효한 명령어 인자가 아닙니다."); + } + return new ChessCommand(commandType, commandArguments); + } + + private static void validate(final List commandArguments) { + if (Objects.isNull(commandArguments) || commandArguments.isEmpty()) { + throw new IllegalArgumentException("유효한 체스 명령어가 아닙니다."); + } + } + + public boolean isStartChessCommand() { + return this.commandType.isStartCommandType(); + } + + public boolean isMoveChessCommand() { + return this.commandType.isMoveCommandType(); + } + + public boolean isStatusChessCommand() { + return this.commandType.isStatusCommandType(); + } + + public boolean isEndChessCommand() { + return this.commandType.isEndCommandType(); + } + + public Position getSourcePosition() { + checkIsMoveCommandType(); + return Position.of(commandArguments.get(SOURCE_POSITION_INDEX)); + } + + public Position getTargetPosition() { + checkIsMoveCommandType(); + return Position.of(commandArguments.get(TARGET_POSITION_INDEX)); + } + + private void checkIsMoveCommandType() { + if (!this.commandType.isMoveCommandType()) { + throw new UnsupportedOperationException("move 명령어만 지원하는 기능입니다."); + } + } + + public PieceColor getStatusPieceColor() { + checkIsStatusCommandType(); + return PieceColor.of(commandArguments.get(STATUS_PIECE_COLOR_INDEX)); + } + + private void checkIsStatusCommandType() { + if (!this.commandType.isStatusCommandType()) { + throw new UnsupportedOperationException("status 명령어만 지원하는 기능입니다."); + } + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessCommand that = (ChessCommand)object; + return commandType == that.commandType && + Objects.equals(commandArguments, that.commandArguments); + } + + @Override + public int hashCode() { + return Objects.hash(commandType, commandArguments); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/ChessGame.java b/src/main/java/wooteco/chess/domain/chessGame/ChessGame.java new file mode 100644 index 0000000000..49965b0177 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/ChessGame.java @@ -0,0 +1,114 @@ +package wooteco.chess.domain.chessGame; + +import java.util.Objects; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessGame.gameState.GameState; +import wooteco.chess.domain.chessGame.gameState.WhiteTurnState; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.position.Position; + +public class ChessGame { + + private static final boolean KING_CAUGHT_STATE = true; + private static final boolean NOT_KING_CAUGHT_STATE = false; + + private final ChessBoard chessBoard; + private GameState gameState; + + private ChessGame(final ChessBoard chessBoard, final GameState gameState) { + this.chessBoard = chessBoard; + this.gameState = gameState; + } + + public static ChessGame from(final ChessBoard chessBoard) { + Objects.requireNonNull(chessBoard, "체스 보드가 null입니다."); + + return new ChessGame(chessBoard, new WhiteTurnState()); + } + + public void move(final ChessCommand chessCommand) { + Objects.requireNonNull(chessCommand, "체스 명령이 null입니다."); + + final Position sourcePosition = chessCommand.getSourcePosition(); + final Position targetPosition = chessCommand.getTargetPosition(); + + final boolean isKingOnTargetPosition = chessBoard.isKingOn(targetPosition); + + moveChessPieceFrom(sourcePosition, targetPosition); + shiftGameStatusBy(isKingOnTargetPosition); + } + + private void moveChessPieceFrom(final Position sourcePosition, final Position targetPosition) { + checkChessPieceExistOn(sourcePosition); + checkCorrectChessTurn(sourcePosition); + checkLeapable(sourcePosition, targetPosition); + checkMovableOrCatchable(sourcePosition, targetPosition); + chessBoard.moveChessPiece(sourcePosition, targetPosition); + } + + private void checkChessPieceExistOn(final Position sourcePosition) { + if (!chessBoard.isChessPieceOn(sourcePosition)) { + throw new IllegalArgumentException("이동할 체스 피스가 존재하지 않습니다."); + } + } + + private void checkCorrectChessTurn(final Position sourcePosition) { + if (!chessBoard.isSamePieceColorOn(sourcePosition, gameState.getPieceColor())) { + throw new IllegalArgumentException("순서에 맞지 않은 말을 이동하였습니다."); + } + } + + private void checkLeapable(final Position sourcePosition, final Position targetPosition) { + if (!chessBoard.canLeapChessPieceOn(sourcePosition)) { + chessBoard.checkChessPieceExistInRoute(sourcePosition, targetPosition); + } + } + + private void checkMovableOrCatchable(final Position sourcePosition, final Position targetPosition) { + if (!chessBoard.isChessPieceOn(targetPosition)) { + chessBoard.checkCanMove(sourcePosition, targetPosition); + return; + } + chessBoard.checkCanCatch(sourcePosition, targetPosition); + } + + private void shiftGameStatusBy(final boolean isKingOnTargetPosition) { + if (isKingOnTargetPosition) { + gameState = gameState.shiftEndState(KING_CAUGHT_STATE); + return; + } + gameState = gameState.shiftNextTurnState(); + } + + public double status(final ChessCommand chessCommand) { + Objects.requireNonNull(chessCommand, "체스 명령이 null입니다."); + final ChessStatus chessStatus = chessBoard.calculateStatus(); + return chessStatus.getStatusOf(chessCommand.getStatusPieceColor()); + } + + public void end() { + gameState = gameState.shiftEndState(NOT_KING_CAUGHT_STATE); + } + + public boolean isEndState() { + return gameState.isEndState(); + } + + public boolean isKingCaught() { + return gameState.isKingCaughtState(); + } + + public ChessStatus getChessGameStatus() { + return chessBoard.calculateStatus(); + } + + public PieceColor getCurrentPieceColor() { + return this.gameState.getPieceColor(); + } + + public ChessBoard getChessBoard() { + return chessBoard; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/ChessStatus.java b/src/main/java/wooteco/chess/domain/chessGame/ChessStatus.java new file mode 100644 index 0000000000..925b6a866f --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/ChessStatus.java @@ -0,0 +1,103 @@ +package wooteco.chess.domain.chessGame; + +import static java.util.stream.Collectors.*; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.position.ChessFile; +import wooteco.chess.domain.position.Position; + +public class ChessStatus { + + private static final double PAWN_ALONE_ON_FILE_SCORE = 1.; + private static final double PAWN_EXIST_SAME_FILE_CONSTANT = 0.5; + + private final Map chessStatus; + + private ChessStatus(final Map chessStatus) { + this.chessStatus = chessStatus; + } + + public static ChessStatus of(final Map chessBoard) { + validate(chessBoard); + + return Arrays.stream(PieceColor.values()) + .collect(collectingAndThen( + toMap( + Function.identity(), + pieceColor -> calculateStatusOf(chessBoard, pieceColor)), + ChessStatus::new)); + } + + private static void validate(final Map chessBoard) { + if (Objects.isNull(chessBoard) || chessBoard.isEmpty()) { + throw new IllegalArgumentException("체스 보드가 존재하지 않습니다."); + } + } + + private static double calculateStatusOf(final Map chessBoard, PieceColor pieceColor) { + return Arrays.stream(ChessFile.values()) + .map(chessFile -> findChessPieceOn(chessFile, pieceColor, chessBoard)) + .mapToDouble(ChessStatus::calculateScoreOf) + .sum(); + } + + private static Stream findChessPieceOn(final ChessFile chessFile, final PieceColor pieceColor, + final Map chessBoard) { + return chessBoard.entrySet().stream() + .filter(entry -> entry.getKey().isSame(chessFile)) + .map(Map.Entry::getValue) + .filter(chessPiece -> chessPiece.isSame(pieceColor)); + } + + private static double calculateScoreOf(final Stream chessPieces) { + final boolean pawnKey = true; + final boolean notPawnKey = false; + + final Map partitioningByPawn = chessPieces.collect( + partitioningBy( + ChessPiece::isPawn, + summingDouble(ChessPiece::getScore))); + + return calculatePawnScore(partitioningByPawn.get(pawnKey)) + partitioningByPawn.get(notPawnKey); + } + + private static double calculatePawnScore(final double pawnTotalScore) { + if (pawnTotalScore > PAWN_ALONE_ON_FILE_SCORE) { + return pawnTotalScore * PAWN_EXIST_SAME_FILE_CONSTANT; + } + return pawnTotalScore; + } + + public double getStatusOf(final PieceColor pieceColor) { + return chessStatus.get(pieceColor); + } + + public Map getChessStatus() { + return chessStatus; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessStatus that = (ChessStatus)object; + return Objects.equals(chessStatus, that.chessStatus); + } + + @Override + public int hashCode() { + return Objects.hash(chessStatus); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/CommandType.java b/src/main/java/wooteco/chess/domain/chessGame/CommandType.java new file mode 100644 index 0000000000..e220e6571d --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/CommandType.java @@ -0,0 +1,50 @@ +package wooteco.chess.domain.chessGame; + +import java.util.Arrays; +import java.util.Objects; + +public enum CommandType { + + START("start", 1), + END("end", 1), + MOVE("move", 3), + STATUS("status", 2); + + private final String command; + private final int requireArgumentsSize; + + CommandType(final String command, final int requireArgumentsSize) { + this.command = command; + this.requireArgumentsSize = requireArgumentsSize; + } + + public static CommandType of(final String command) { + Objects.requireNonNull(command, "명령이 null입니다."); + + return Arrays.stream(values()) + .filter(commandType -> commandType.command.equals(command)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효한 명령어가 아닙니다.")); + } + + public boolean isStartCommandType() { + return this.equals(START); + } + + public boolean isMoveCommandType() { + return this.equals(MOVE); + } + + public boolean isStatusCommandType() { + return this.equals(STATUS); + } + + public boolean isEndCommandType() { + return this.equals(END); + } + + public boolean isCorrectArgumentsSize(final int argumentsSize) { + return this.requireArgumentsSize == argumentsSize; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/BlackTurnState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/BlackTurnState.java new file mode 100644 index 0000000000..659dc4affc --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/BlackTurnState.java @@ -0,0 +1,17 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class BlackTurnState extends ChessTurnState { + + @Override + public GameState shiftNextTurnState() { + return new WhiteTurnState(); + } + + @Override + public PieceColor getPieceColor() { + return PieceColor.BLACK; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessEndState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessEndState.java new file mode 100644 index 0000000000..93e0fe39fd --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessEndState.java @@ -0,0 +1,26 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public abstract class ChessEndState implements GameState { + + protected final PieceColor pieceColor; + + ChessEndState(final PieceColor pieceColor) { + this.pieceColor = pieceColor; + } + + @Override + public boolean isEndState() { + return true; + } + + @Override + abstract public boolean isKingCaughtState(); + + @Override + public PieceColor getPieceColor() { + return pieceColor; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessTurnState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessTurnState.java new file mode 100644 index 0000000000..79f160acd9 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/ChessTurnState.java @@ -0,0 +1,28 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public abstract class ChessTurnState implements GameState { + + @Override + public GameState shiftEndState(final boolean isKingOnTargetPosition) { + if (isKingOnTargetPosition) { + return new KingCaughtState(getPieceColor()); + } + return new EndState(getPieceColor()); + } + + @Override + public boolean isEndState() { + return false; + } + + @Override + public boolean isKingCaughtState() { + return false; + } + + @Override + public abstract PieceColor getPieceColor(); + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/EndState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/EndState.java new file mode 100644 index 0000000000..145803640e --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/EndState.java @@ -0,0 +1,16 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class EndState extends ChessEndState { + + EndState(final PieceColor pieceColor) { + super(pieceColor); + } + + @Override + public boolean isKingCaughtState() { + return false; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/GameState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/GameState.java new file mode 100644 index 0000000000..dfac5e292e --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/GameState.java @@ -0,0 +1,21 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public interface GameState { + + default GameState shiftNextTurnState() { + throw new UnsupportedOperationException("지원하지 않는 기능입니다."); + } + + default GameState shiftEndState(final boolean isKingOnTargetPosition) { + throw new UnsupportedOperationException("지원하지 않는 기능입니다."); + } + + boolean isEndState(); + + boolean isKingCaughtState(); + + PieceColor getPieceColor(); + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/KingCaughtState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/KingCaughtState.java new file mode 100644 index 0000000000..355c23b187 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/KingCaughtState.java @@ -0,0 +1,16 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class KingCaughtState extends ChessEndState { + + KingCaughtState(final PieceColor pieceColor) { + super(pieceColor); + } + + @Override + public boolean isKingCaughtState() { + return true; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnState.java b/src/main/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnState.java new file mode 100644 index 0000000000..c1e855a88d --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnState.java @@ -0,0 +1,17 @@ +package wooteco.chess.domain.chessGame.gameState; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class WhiteTurnState extends ChessTurnState { + + @Override + public GameState shiftNextTurnState() { + return new BlackTurnState(); + } + + @Override + public PieceColor getPieceColor() { + return PieceColor.WHITE; + } + +} \ No newline at end of file diff --git a/src/main/java/wooteco/chess/domain/chessPiece/Catchable.java b/src/main/java/wooteco/chess/domain/chessPiece/Catchable.java new file mode 100644 index 0000000000..0c9568a178 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/Catchable.java @@ -0,0 +1,9 @@ +package wooteco.chess.domain.chessPiece; + +import wooteco.chess.domain.position.Position; + +public interface Catchable { + + boolean canCatch(final Position sourcePosition, final Position targetPosition); + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/ChessPiece.java b/src/main/java/wooteco/chess/domain/chessPiece/ChessPiece.java new file mode 100644 index 0000000000..b119d2f6cf --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/ChessPiece.java @@ -0,0 +1,102 @@ +package wooteco.chess.domain.chessPiece; + +import java.util.Objects; + +import wooteco.chess.domain.chessPiece.pieceState.InitialState; +import wooteco.chess.domain.chessPiece.pieceState.PieceState; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +public class ChessPiece implements Movable, Catchable { + + protected final PieceType pieceType; + protected PieceState pieceState; + + protected ChessPiece(final PieceType pieceType, final PieceState pieceState) { + Objects.requireNonNull(pieceType, "피스 타입이 null입니다."); + Objects.requireNonNull(pieceState, "피스 상태가 null입니다."); + this.pieceType = pieceType; + this.pieceState = pieceState; + } + + public ChessPiece(final PieceType pieceType) { + this(pieceType, new InitialState()); + } + + @Override + public boolean canLeap() { + return this.pieceType.canLeap(); + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + + if (this.pieceType.canMove(sourcePosition, targetPosition, pieceState.getPawnMovableRange())) { + this.pieceState = pieceState.shiftNextState(); + return true; + } + return false; + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + validate(sourcePosition, targetPosition); + + if (this.pieceType.canCatch(sourcePosition, targetPosition)) { + this.pieceState = pieceState.shiftNextState(); + return true; + } + return false; + } + + private void validate(final Position sourcePosition, final Position targetPosition) { + Objects.requireNonNull(sourcePosition, "소스 위치가 null입니다."); + Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); + } + + public boolean isSamePieceColor(final ChessPiece chessPiece) { + Objects.requireNonNull(chessPiece, "체스 피스가 null입니다."); + return this.pieceType.isSamePieceColor(chessPiece.pieceType); + } + + public boolean isSame(final PieceColor pieceColor) { + Objects.requireNonNull(pieceColor, "체스 색상이 null입니다."); + return this.pieceType.isSame(pieceColor); + } + + public boolean isPawn() { + return this.pieceType.isPawn(); + } + + public boolean isKing() { + return this.pieceType.isKing(); + } + + public String getName() { + return this.pieceType.getName(); + } + + public double getScore() { + return this.pieceType.getScore(); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessPiece that = (ChessPiece)object; + return this.pieceType.equals(that.pieceType); + } + + @Override + public int hashCode() { + return Objects.hash(pieceType); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/Movable.java b/src/main/java/wooteco/chess/domain/chessPiece/Movable.java new file mode 100644 index 0000000000..58f245f220 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/Movable.java @@ -0,0 +1,11 @@ +package wooteco.chess.domain.chessPiece; + +import wooteco.chess.domain.position.Position; + +public interface Movable { + + boolean canLeap(); + + boolean canMove(final Position sourcePosition, final Position targetPosition); + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceState/InitialState.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/InitialState.java new file mode 100644 index 0000000000..d062c92f6b --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/InitialState.java @@ -0,0 +1,12 @@ +package wooteco.chess.domain.chessPiece.pieceState; + +public class InitialState implements PieceState { + + private static final int INITIAL_STATE_PAWN_MOVABLE_RANGE = 2; + + @Override + public int getPawnMovableRange() { + return INITIAL_STATE_PAWN_MOVABLE_RANGE; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceState/MovedState.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/MovedState.java new file mode 100644 index 0000000000..46634d9c2d --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/MovedState.java @@ -0,0 +1,12 @@ +package wooteco.chess.domain.chessPiece.pieceState; + +public class MovedState implements PieceState { + + public static final int MOVED_STATE_PAWN_MOVABLE_RANGE = 1; + + @Override + public int getPawnMovableRange() { + return MOVED_STATE_PAWN_MOVABLE_RANGE; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceState/PieceState.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/PieceState.java new file mode 100644 index 0000000000..2c7602661b --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceState/PieceState.java @@ -0,0 +1,11 @@ +package wooteco.chess.domain.chessPiece.pieceState; + +public interface PieceState { + + default PieceState shiftNextState() { + return new MovedState(); + } + + int getPawnMovableRange(); + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategy.java new file mode 100644 index 0000000000..0b921f4fc2 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategy.java @@ -0,0 +1,34 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static wooteco.chess.domain.chessPiece.pieceType.PieceDirection.*; + +import wooteco.chess.domain.position.Position; + +public class BishopStrategy implements PieceStrategy { + + @Override + public boolean canLeap() { + return false; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMoveDirection(sourcePosition, targetPosition) && canMoveRange(); + } + + private boolean canMoveDirection(final Position sourcePosition, final Position targetPosition) { + return DIAGONAL.getMovableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + + private boolean canMoveRange() { + return true; + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return DIAGONAL.getCatchableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategy.java new file mode 100644 index 0000000000..6994bc8451 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategy.java @@ -0,0 +1,21 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static wooteco.chess.domain.chessPiece.pieceType.PieceDirection.*; + +import wooteco.chess.domain.position.Position; + +public class BlackPawnStrategy extends PawnStrategy { + + @Override + protected boolean canMoveDirection(final Position sourcePosition, final Position targetPosition) { + return BLACK_PAWN.getMovableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + + @Override + protected boolean canCatchDirection(Position sourcePosition, Position targetPosition) { + return BLACK_PAWN.getCatchableDirections().stream() + .anyMatch(catchableDirections -> catchableDirections.isSameDirectionFrom(sourcePosition, targetPosition)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategy.java new file mode 100644 index 0000000000..cce275f0e4 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategy.java @@ -0,0 +1,32 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import wooteco.chess.domain.position.Position; + +public class KingStrategy implements PieceStrategy { + + private static final int KING_FILE_GAP = 1; + private static final int KING_RANK_GAP = 1; + + @Override + public boolean canLeap() { + return true; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMove(sourcePosition, targetPosition); + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return canMove(sourcePosition, targetPosition); + } + + private boolean canMove(final Position sourcePosition, final Position targetPosition) { + final int chessFileGap = Math.abs(sourcePosition.calculateChessFileGapTo(targetPosition)); + final int chessRankGap = Math.abs(sourcePosition.calculateChessRankGapTo(targetPosition)); + + return chessFileGap <= KING_FILE_GAP && chessRankGap <= KING_RANK_GAP; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategy.java new file mode 100644 index 0000000000..a7eec0007b --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategy.java @@ -0,0 +1,39 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import wooteco.chess.domain.position.Position; + +public class KnightStrategy implements PieceStrategy { + + public static final int KNIGHT_MOVABLE_RANGE = 3; + + @Override + public boolean canLeap() { + return true; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMove(sourcePosition, targetPosition); + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return canMove(sourcePosition, targetPosition); + } + + private boolean canMove(final Position sourcePosition, final Position targetPosition) { + final int chessFileGap = Math.abs(sourcePosition.calculateChessFileGapTo(targetPosition)); + final int chessRankGap = Math.abs(sourcePosition.calculateChessRankGapTo(targetPosition)); + + return isNotExistOnAxis(chessFileGap, chessRankGap) && isKnightCanMoveRange(chessFileGap, chessRankGap); + } + + private boolean isNotExistOnAxis(final int chessFileGap, final int chessRankGap) { + return chessFileGap != 0 && chessRankGap != 0; + } + + private boolean isKnightCanMoveRange(final int chessFileGap, final int chessRankGap) { + return (chessFileGap + chessRankGap) == KNIGHT_MOVABLE_RANGE; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PawnStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PawnStrategy.java new file mode 100644 index 0000000000..820ac2d08e --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PawnStrategy.java @@ -0,0 +1,45 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import wooteco.chess.domain.position.Position; + +public abstract class PawnStrategy implements PieceStrategy { + + private static final int PAWN_MOVABLE_FILE_GAP = 0; + private static final int PAWN_CATCHABLE_FILE_GAP = 1; + private static final int PAWN_CATCHABLE_RANK_GAP = 1; + + @Override + public boolean canLeap() { + return false; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMoveDirection(sourcePosition, targetPosition) && canMoveRange(sourcePosition, targetPosition, + pawnMovableRange); + } + + protected abstract boolean canMoveDirection(Position sourcePosition, Position targetPosition); + + private boolean canMoveRange(final Position sourcePosition, final Position targetPosition, + final int pawnMovableRange) { + final int chessFileGap = Math.abs(sourcePosition.calculateChessFileGapTo(targetPosition)); + final int chessRankGap = Math.abs(sourcePosition.calculateChessRankGapTo(targetPosition)); + + return (chessFileGap == PAWN_MOVABLE_FILE_GAP) && (chessRankGap <= pawnMovableRange); + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return canCatchDirection(sourcePosition, targetPosition) && canCatchRange(sourcePosition, targetPosition); + } + + protected abstract boolean canCatchDirection(Position sourcePosition, Position targetPosition); + + private boolean canCatchRange(Position sourcePosition, Position targetPosition) { + int chessFileGap = Math.abs(sourcePosition.calculateChessFileGapTo(targetPosition)); + int chessRankGap = Math.abs(sourcePosition.calculateChessRankGapTo(targetPosition)); + + return (chessFileGap == PAWN_CATCHABLE_FILE_GAP) && (chessRankGap == PAWN_CATCHABLE_RANK_GAP); + } +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PieceStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PieceStrategy.java new file mode 100644 index 0000000000..72b9ec950a --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/PieceStrategy.java @@ -0,0 +1,13 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import wooteco.chess.domain.position.Position; + +public interface PieceStrategy { + + boolean canLeap(); + + boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange); + + boolean canCatch(final Position sourcePosition, final Position targetPosition); + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategy.java new file mode 100644 index 0000000000..2bba661e9b --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategy.java @@ -0,0 +1,34 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static wooteco.chess.domain.chessPiece.pieceType.PieceDirection.*; + +import wooteco.chess.domain.position.Position; + +public class QueenStrategy implements PieceStrategy { + + @Override + public boolean canLeap() { + return false; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMoveDirection(sourcePosition, targetPosition) && canMoveRange(); + } + + private boolean canMoveDirection(final Position sourcePosition, final Position targetPosition) { + return ALL.getMovableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + + private boolean canMoveRange() { + return true; + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return ALL.getCatchableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategy.java new file mode 100644 index 0000000000..36edc54ddf --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategy.java @@ -0,0 +1,34 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static wooteco.chess.domain.chessPiece.pieceType.PieceDirection.*; + +import wooteco.chess.domain.position.Position; + +public class RookStrategy implements PieceStrategy { + + @Override + public boolean canLeap() { + return false; + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return canMoveDirection(sourcePosition, targetPosition) && canMoveRange(); + } + + private boolean canMoveDirection(final Position sourcePosition, final Position targetPosition) { + return AXIS.getMovableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + + private boolean canMoveRange() { + return true; + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return AXIS.getCatchableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategy.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategy.java new file mode 100644 index 0000000000..1fc6eebafd --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategy.java @@ -0,0 +1,21 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static wooteco.chess.domain.chessPiece.pieceType.PieceDirection.*; + +import wooteco.chess.domain.position.Position; + +public class WhitePawnStrategy extends PawnStrategy { + + @Override + protected boolean canMoveDirection(final Position sourcePosition, final Position targetPosition) { + return WHITE_PAWN.getMovableDirections().stream() + .anyMatch(moveDirection -> moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)); + } + + @Override + protected boolean canCatchDirection(Position sourcePosition, Position targetPosition) { + return WHITE_PAWN.getCatchableDirections().stream() + .anyMatch(catchableDirections -> catchableDirections.isSameDirectionFrom(sourcePosition, targetPosition)); + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceColor.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceColor.java new file mode 100644 index 0000000000..2d83446f10 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceColor.java @@ -0,0 +1,45 @@ +package wooteco.chess.domain.chessPiece.pieceType; + +import java.util.Arrays; +import java.util.function.Function; + +public enum PieceColor { + + WHITE("white", String::toLowerCase), + BLACK("black", String::toUpperCase); + + private final String color; + private final Function convertFrom; + + PieceColor(String color, Function convertFrom) { + this.color = color; + this.convertFrom = convertFrom; + } + + public static PieceColor of(String color) { + return Arrays.stream(values()) + .filter(pieceColor -> pieceColor.color.equals(color)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 색상이 존재하지 않습니다.")); + } + + public String convertFrom(String pieceName) { + if (pieceName == null || pieceName.isEmpty()) { + throw new IllegalArgumentException("체스 이름이 존재하지 않습니다."); + } + return convertFrom.apply(pieceName); + } + + public boolean isBlack() { + return this.equals(BLACK); + } + + public boolean isWhite() { + return this.equals(WHITE); + } + + public String getColor() { + return color; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceDirection.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceDirection.java new file mode 100644 index 0000000000..2460d24aa4 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceDirection.java @@ -0,0 +1,38 @@ +package wooteco.chess.domain.chessPiece.pieceType; + +import static wooteco.chess.domain.position.MoveDirection.*; + +import java.util.Arrays; +import java.util.List; + +import wooteco.chess.domain.position.MoveDirection; + +public enum PieceDirection { + + ALL(Arrays.asList(MoveDirection.values())), + DIAGONAL(Arrays.asList(NE, SE, SW, NW)), + AXIS(Arrays.asList(N, E, S, W)), + BLACK_PAWN(Arrays.asList(S), Arrays.asList(SW, SE)), + WHITE_PAWN(Arrays.asList(N), Arrays.asList(NW, NE)); + + private final List movableDirections; + private final List catchableDirections; + + PieceDirection(final List movableDirections, final List catchableDirections) { + this.movableDirections = movableDirections; + this.catchableDirections = catchableDirections; + } + + PieceDirection(final List movableDirections) { + this(movableDirections, movableDirections); + } + + public List getMovableDirections() { + return movableDirections; + } + + public List getCatchableDirections() { + return catchableDirections; + } + +} diff --git a/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceType.java b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceType.java new file mode 100644 index 0000000000..488eb324db --- /dev/null +++ b/src/main/java/wooteco/chess/domain/chessPiece/pieceType/PieceType.java @@ -0,0 +1,80 @@ +package wooteco.chess.domain.chessPiece.pieceType; + +import wooteco.chess.domain.chessPiece.pieceStrategy.BishopStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.BlackPawnStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.KingStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.KnightStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.PawnStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.PieceStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.QueenStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.RookStrategy; +import wooteco.chess.domain.chessPiece.pieceStrategy.WhitePawnStrategy; +import wooteco.chess.domain.position.Position; + +public enum PieceType implements PieceStrategy { + + BLACK_KING(PieceColor.BLACK, new KingStrategy(), "K", 0), + WHITE_KING(PieceColor.WHITE, new KingStrategy(), "k", 0), + BLACK_KNIGHT(PieceColor.BLACK, new KnightStrategy(), "N", 2.5), + WHITE_KNIGHT(PieceColor.WHITE, new KnightStrategy(), "n", 2.5), + BLACK_QUEEN(PieceColor.BLACK, new QueenStrategy(), "Q", 9), + WHITE_QUEEN(PieceColor.WHITE, new QueenStrategy(), "q", 9), + BLACK_ROOK(PieceColor.BLACK, new RookStrategy(), "R", 5), + WHITE_ROOK(PieceColor.WHITE, new RookStrategy(), "r", 5), + BLACK_BISHOP(PieceColor.BLACK, new BishopStrategy(), "B", 3), + WHITE_BISHOP(PieceColor.WHITE, new BishopStrategy(), "b", 3), + BLACK_PAWN(PieceColor.BLACK, new BlackPawnStrategy(), "P", 1), + WHITE_PAWN(PieceColor.WHITE, new WhitePawnStrategy(), "p", 1); + + private final PieceColor pieceColor; + private final PieceStrategy pieceStrategy; + private final String name; + private final double score; + + PieceType(final PieceColor pieceColor, final PieceStrategy pieceStrategy, final String name, final double score) { + this.pieceColor = pieceColor; + this.pieceStrategy = pieceStrategy; + this.name = name; + this.score = score; + } + + @Override + public boolean canLeap() { + return this.pieceStrategy.canLeap(); + } + + @Override + public boolean canMove(final Position sourcePosition, final Position targetPosition, final int pawnMovableRange) { + return this.pieceStrategy.canMove(sourcePosition, targetPosition, pawnMovableRange); + } + + @Override + public boolean canCatch(final Position sourcePosition, final Position targetPosition) { + return this.pieceStrategy.canCatch(sourcePosition, targetPosition); + } + + public boolean isSame(final PieceColor pieceColor) { + return this.pieceColor.equals(pieceColor); + } + + public boolean isSamePieceColor(final PieceType pieceType) { + return this.pieceColor.equals(pieceType.pieceColor); + } + + public boolean isPawn() { + return this.pieceStrategy instanceof PawnStrategy; + } + + public boolean isKing() { + return this.equals(BLACK_KING) || this.equals(WHITE_KING); + } + + public String getName() { + return name; + } + + public double getScore() { + return score; + } + +} diff --git a/src/main/java/wooteco/chess/domain/position/ChessFile.java b/src/main/java/wooteco/chess/domain/position/ChessFile.java new file mode 100644 index 0000000000..8fa1abc22a --- /dev/null +++ b/src/main/java/wooteco/chess/domain/position/ChessFile.java @@ -0,0 +1,60 @@ +package wooteco.chess.domain.position; + +import java.util.Arrays; +import java.util.Objects; + +public enum ChessFile { + + A("a", 1), + B("b", 2), + C("c", 3), + D("d", 4), + E("e", 5), + F("f", 6), + G("g", 7), + H("h", 8); + + private final String chessFile; + private final int fileValue; + + ChessFile(final String chessFile, final int fileValue) { + this.chessFile = chessFile; + this.fileValue = fileValue; + } + + public static ChessFile from(final char chessFile) { + return Arrays.stream(values()) + .filter(value -> value.chessFile.equals(String.valueOf(chessFile))) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 파일이 존재하지 않습니다.")); + } + + public static ChessFile from(final String chessFile) { + return Arrays.stream(values()) + .filter(value -> value.chessFile.equals(chessFile)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 파일이 존재하지 않습니다.")); + } + + public static ChessFile from(final int fileValue) { + return Arrays.stream(values()) + .filter(value -> value.fileValue == fileValue) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 파일이 존재하지 않습니다.")); + } + + ChessFile move(final int movingFileValue) { + return from(this.fileValue + movingFileValue); + } + + public int gapTo(final ChessFile targetChessFile) { + Objects.requireNonNull(targetChessFile, "체스 파일이 null입니다."); + return targetChessFile.fileValue - this.fileValue; + } + + @Override + public String toString() { + return String.valueOf(chessFile); + } + +} diff --git a/src/main/java/wooteco/chess/domain/position/ChessRank.java b/src/main/java/wooteco/chess/domain/position/ChessRank.java new file mode 100644 index 0000000000..98ade0b3d6 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/position/ChessRank.java @@ -0,0 +1,53 @@ +package wooteco.chess.domain.position; + +import static java.lang.Character.*; + +import java.util.Arrays; +import java.util.Objects; + +public enum ChessRank { + + ONE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8); + + private final int chessRank; + + ChessRank(final int chessRank) { + this.chessRank = chessRank; + } + + public static ChessRank from(final int chessRank) { + return Arrays.stream(values()) + .filter(value -> value.chessRank == chessRank) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 랭크가 존재하지 않습니다.")); + } + + public static ChessRank from(final char chessRank) { + return Arrays.stream(values()) + .filter(value -> value.chessRank == getNumericValue(chessRank)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 랭크가 존재하지 않습니다.")); + } + + public ChessRank move(final int movingRankValue) { + return from(this.chessRank + movingRankValue); + } + + public int gapTo(final ChessRank targetChessRank) { + Objects.requireNonNull(targetChessRank, "체스 랭크가 null입니다."); + return targetChessRank.chessRank - this.chessRank; + } + + @Override + public String toString() { + return String.valueOf(chessRank); + } + +} diff --git a/src/main/java/wooteco/chess/domain/position/MoveDirection.java b/src/main/java/wooteco/chess/domain/position/MoveDirection.java new file mode 100644 index 0000000000..7aad08db20 --- /dev/null +++ b/src/main/java/wooteco/chess/domain/position/MoveDirection.java @@ -0,0 +1,50 @@ +package wooteco.chess.domain.position; + +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.UnaryOperator; + +public enum MoveDirection { + + N((fileGap, rankGap) -> fileGap == 0 && rankGap > 0, + (sourcePosition) -> sourcePosition.move(0, 1)), + NE((fileGap, rankGap) -> isSameGap(fileGap, rankGap) && fileGap >= 0 && rankGap >= 0, + (sourcePosition) -> sourcePosition.move(1, 1)), + E((fileGap, rankGap) -> fileGap > 0 && rankGap == 0, + (sourcePosition) -> sourcePosition.move(1, 0)), + SE((fileGap, rankGap) -> isSameGap(fileGap, rankGap) && fileGap >= 0 && rankGap <= 0, + (sourcePosition) -> sourcePosition.move(1, -1)), + S((fileGap, rankGap) -> fileGap == 0 && rankGap < 0, + (sourcePosition) -> sourcePosition.move(0, -1)), + SW((fileGap, rankGap) -> isSameGap(fileGap, rankGap) && fileGap <= 0 && rankGap <= 0, + (sourcePosition) -> sourcePosition.move(-1, -1)), + W((fileGap, rankGap) -> fileGap < 0 && rankGap == 0, + (sourcePosition) -> sourcePosition.move(-1, 0)), + NW((fileGap, rankGap) -> isSameGap(fileGap, rankGap) && fileGap <= 0 && rankGap >= 0, + (sourcePosition) -> sourcePosition.move(-1, 1)); + + private final BiPredicate isSameDirection; + private final UnaryOperator moveNextPosition; + + MoveDirection(final BiPredicate isSameDirection, final UnaryOperator moveNextPosition) { + this.isSameDirection = isSameDirection; + this.moveNextPosition = moveNextPosition; + } + + private static boolean isSameGap(final int fileGap, final int rankGap) { + return (Math.abs(fileGap) - Math.abs(rankGap)) == 0; + } + + public boolean isSameDirectionFrom(final Position sourcePosition, final Position targetPosition) { + final int chessFileGap = sourcePosition.calculateChessFileGapTo(targetPosition); + final int chessRankGap = sourcePosition.calculateChessRankGapTo(targetPosition); + + return this.isSameDirection.test(chessFileGap, chessRankGap); + } + + public Position move(final Position sourcePosition) { + Objects.requireNonNull(sourcePosition, "유효한 위치가 아닙니다."); + return moveNextPosition.apply(sourcePosition); + } + +} diff --git a/src/main/java/wooteco/chess/domain/position/Position.java b/src/main/java/wooteco/chess/domain/position/Position.java new file mode 100644 index 0000000000..a4349a9c3a --- /dev/null +++ b/src/main/java/wooteco/chess/domain/position/Position.java @@ -0,0 +1,87 @@ +package wooteco.chess.domain.position; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class Position { + + private static final int FILE_INDEX = 0; + private static final int RANK_INDEX = 1; + private static final int POSITION_KEY_VALID_LENGTH = 2; + private static final Map POSITION_CACHE = new HashMap<>(); + + private final ChessFile chessFile; + private final ChessRank chessRank; + + private Position(final ChessFile chessFile, final ChessRank chessRank) { + this.chessFile = chessFile; + this.chessRank = chessRank; + } + + static { + for (ChessFile chessFile : ChessFile.values()) { + for (ChessRank chessRank : ChessRank.values()) { + POSITION_CACHE.put(key(chessFile, chessRank), new Position(chessFile, chessRank)); + } + } + } + + private static String key(final ChessFile chessFile, final ChessRank chessRank) { + return String.format("%s%s", chessFile, chessRank); + } + + public static Position of(final ChessFile chessFile, final ChessRank chessRank) { + Objects.requireNonNull(chessFile, "체스 파일이 null입니다."); + Objects.requireNonNull(chessRank, "체스 랭크가 null입니다."); + + return POSITION_CACHE.getOrDefault(key(chessFile, chessRank), new Position(chessFile, chessRank)); + } + + public static Position of(final String key) { + validate(key); + return POSITION_CACHE.getOrDefault(key, + new Position(ChessFile.from(key.charAt(FILE_INDEX)), ChessRank.from(key.charAt(RANK_INDEX)))); + } + + private static void validate(final String key) { + validateEmpty(key); + validateLength(key); + } + + private static void validateEmpty(final String key) { + if (Objects.isNull(key) || key.isEmpty()) { + throw new IllegalArgumentException("유효한 위치 입력이 아닙니다."); + } + } + + private static void validateLength(final String key) { + if (key.length() != POSITION_KEY_VALID_LENGTH) { + throw new IllegalArgumentException("유효한 위치 입력이 아닙니다."); + } + } + + Position move(final int movingFileValue, final int movingRankValue) { + return Position.of(chessFile.move(movingFileValue), chessRank.move(movingRankValue)); + } + + public int calculateChessFileGapTo(final Position targetPosition) { + Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); + return this.chessFile.gapTo(targetPosition.chessFile); + } + + public int calculateChessRankGapTo(final Position targetPosition) { + Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); + return this.chessRank.gapTo(targetPosition.chessRank); + } + + public boolean isSame(final ChessFile chessFile) { + Objects.requireNonNull(chessFile, "체스 파일이 null입니다."); + return this.chessFile.equals(chessFile); + } + + public String key() { + return key(this.chessFile, this.chessRank); + } + +} diff --git a/src/main/java/wooteco/chess/entity/BaseEntity.java b/src/main/java/wooteco/chess/entity/BaseEntity.java new file mode 100644 index 0000000000..32d8ff50b5 --- /dev/null +++ b/src/main/java/wooteco/chess/entity/BaseEntity.java @@ -0,0 +1,24 @@ +package wooteco.chess.entity; + +import java.time.LocalDateTime; +import java.util.Objects; + +public abstract class BaseEntity implements Comparable { + + protected final LocalDateTime createdTime; + + protected BaseEntity(final LocalDateTime createdTime) { + Objects.requireNonNull(createdTime, "생성 시간이 null입니다."); + this.createdTime = createdTime; + } + + public LocalDateTime getCreatedTime() { + return createdTime; + } + + @Override + public int compareTo(final BaseEntity that) { + return this.createdTime.compareTo(that.createdTime); + } + +} diff --git a/src/main/java/wooteco/chess/entity/ChessGameEntity.java b/src/main/java/wooteco/chess/entity/ChessGameEntity.java new file mode 100644 index 0000000000..a1d1a3e3f6 --- /dev/null +++ b/src/main/java/wooteco/chess/entity/ChessGameEntity.java @@ -0,0 +1,34 @@ +package wooteco.chess.entity; + +import java.time.LocalDateTime; +import java.util.Objects; + +public class ChessGameEntity extends BaseEntity { + + private static final long DEFAULT_GAME_ID = 0L; + + private final long gameId; + + private ChessGameEntity(final long gameId, final LocalDateTime createdTime) { + super(createdTime); + this.gameId = gameId; + } + + public static ChessGameEntity of(final long gameId, final LocalDateTime createdTime) { + return new ChessGameEntity(gameId, createdTime); + } + + public static ChessGameEntity of(final long gameId, final ChessGameEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + return new ChessGameEntity(gameId, entity.createdTime); + } + + public static ChessGameEntity of(final LocalDateTime createdTime) { + return new ChessGameEntity(DEFAULT_GAME_ID, createdTime); + } + + public long getGameId() { + return gameId; + } + +} diff --git a/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java b/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java new file mode 100644 index 0000000000..5bd3ac8ea0 --- /dev/null +++ b/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java @@ -0,0 +1,76 @@ +package wooteco.chess.entity; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Objects; + +import wooteco.chess.domain.chessGame.ChessCommand; + +public class ChessHistoryEntity extends BaseEntity { + + private static final long DEFAULT_HISTORY_ID = 0L; + private static final long DEFAULT_GAME_ID = 0L; + private static final String MOVE_COMMAND = "move"; + + private final long historyId; + private final long gameId; + private final String start; + private final String end; + + private ChessHistoryEntity(final long historyId, final long gameId, final String start, final String end, + final LocalDateTime createdTime) { + super(createdTime); + validate(start, end); + this.historyId = historyId; + this.gameId = gameId; + this.start = start; + this.end = end; + } + + public static ChessHistoryEntity of(final long historyId, final long gameId, final String start, final String end, + final LocalDateTime createdTime) { + return new ChessHistoryEntity(historyId, gameId, start, end, createdTime); + } + + public static ChessHistoryEntity of(final long gameId, final String start, final String end, + final LocalDateTime createdTime) { + return new ChessHistoryEntity(DEFAULT_HISTORY_ID, gameId, start, end, createdTime); + } + + public static ChessHistoryEntity of(final String start, final String end) { + return new ChessHistoryEntity(DEFAULT_HISTORY_ID, DEFAULT_GAME_ID, start, end, + LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + } + + public static ChessHistoryEntity of(final long historyId, final ChessHistoryEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + return new ChessHistoryEntity(historyId, entity.gameId, entity.start, entity.end, entity.createdTime); + } + + private void validate(final String start, final String end) { + Objects.requireNonNull(start, "출발 위치가 null입니다."); + Objects.requireNonNull(end, "도착 위치가 null입니다."); + } + + public ChessCommand generateMoveCommand() { + return ChessCommand.of(Arrays.asList(MOVE_COMMAND, start, end)); + } + + public long getHistoryId() { + return historyId; + } + + public long getGameId() { + return gameId; + } + + public String getStart() { + return start; + } + + public String getEnd() { + return end; + } + +} diff --git a/src/main/java/wooteco/chess/service/ChessService.java b/src/main/java/wooteco/chess/service/ChessService.java new file mode 100644 index 0000000000..c97fadbb04 --- /dev/null +++ b/src/main/java/wooteco/chess/service/ChessService.java @@ -0,0 +1,93 @@ +package wooteco.chess.service; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Objects; + +import org.springframework.stereotype.Service; + +import wooteco.chess.dao.ChessGameDao; +import wooteco.chess.dao.ChessHistoryDao; +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.ChessCommand; +import wooteco.chess.domain.chessGame.ChessGame; +import wooteco.chess.entity.ChessGameEntity; +import wooteco.chess.entity.ChessHistoryEntity; +import wooteco.chess.service.dto.ChessGameDto; + +@Service +public class ChessService { + + private static final String MOVE_COMMAND = "move"; + + private final ChessGameDao chessGameDao; + private final ChessHistoryDao chessHistoryDao; + + public ChessService(final ChessGameDao chessGameDao, final ChessHistoryDao chessHistoryDao) { + Objects.requireNonNull(chessGameDao, "ChessGameDao가 null입니다."); + Objects.requireNonNull(chessHistoryDao, "ChessHistoryDao가 null입니다."); + this.chessGameDao = chessGameDao; + this.chessHistoryDao = chessHistoryDao; + checkChessGameIsEmpty(chessGameDao); + } + + private void checkChessGameIsEmpty(final ChessGameDao chessGameDao) { + if (chessGameDao.isEmpty()) { + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + + chessGameDao.add(entity); + } + } + + public ChessGameDto loadChessGame() { + final long gameId = chessGameDao.findMaxGameId(); + return ChessGameDto.of(initChessGameOf(gameId)); + } + + private ChessGame initChessGameOf(final long gameId) { + final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + final ChessGame chessGame = ChessGame.from(chessBoard); + + chessHistoryDao.findAllByGameId(gameId).stream() + .sorted(ChessHistoryEntity::compareTo) + .map(ChessHistoryEntity::generateMoveCommand) + .forEach(chessGame::move); + return chessGame; + } + + public ChessGameDto playChessGame(final String sourcePosition, final String targetPosition) { + Objects.requireNonNull(sourcePosition, "소스 위치가 null입니다."); + Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); + return moveChessPiece(sourcePosition, targetPosition); + } + + private ChessGameDto moveChessPiece(final String sourcePosition, final String targetPosition) { + final long gameId = chessGameDao.findMaxGameId(); + final ChessGame chessGame = initChessGameOf(gameId); + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, sourcePosition, targetPosition)); + final ChessHistoryEntity chessHistoryEntity = + ChessHistoryEntity.of(gameId, sourcePosition, targetPosition, LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + + chessGame.move(chessCommand); + chessHistoryDao.add(chessHistoryEntity); + return ChessGameDto.of(chessGame); + } + + public ChessGameDto createChessGame() { + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + final long gameId = chessGameDao.add(entity); + + return ChessGameDto.of(initChessGameOf(gameId)); + } + + public ChessGameDto endChessGame() { + final long gameId = chessGameDao.findMaxGameId(); + final ChessGame chessGame = initChessGameOf(gameId); + + chessGame.end(); + return ChessGameDto.of(chessGame); + } + +} diff --git a/src/main/java/wooteco/chess/service/dto/ChessBoardDto.java b/src/main/java/wooteco/chess/service/dto/ChessBoardDto.java new file mode 100644 index 0000000000..541c3a1416 --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/ChessBoardDto.java @@ -0,0 +1,52 @@ +package wooteco.chess.service.dto; + +import static java.util.stream.Collectors.*; + +import java.util.Map; +import java.util.Objects; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.web.PieceNameConverter; + +public class ChessBoardDto { + + private final Map chessBoard; + + private ChessBoardDto(final Map chessBoard) { + this.chessBoard = chessBoard; + } + + public static ChessBoardDto of(final ChessBoard chessBoard) { + Objects.requireNonNull(chessBoard, "체스 보드가 null입니다."); + + return chessBoard.getChessBoard().entrySet() + .stream() + .collect(collectingAndThen( + toMap( + entry -> entry.getKey().key(), + entry -> PieceNameConverter.of(entry.getValue().getName()).getImageFileName() + ), ChessBoardDto::new)); + } + + public Map getChessBoard() { + return chessBoard; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessBoardDto that = (ChessBoardDto)object; + return Objects.equals(chessBoard, that.chessBoard); + } + + @Override + public int hashCode() { + return Objects.hash(chessBoard); + } + +} diff --git a/src/main/java/wooteco/chess/service/dto/ChessGameDto.java b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java new file mode 100644 index 0000000000..e12f3a9b46 --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java @@ -0,0 +1,77 @@ +package wooteco.chess.service.dto; + +import java.util.Objects; + +import wooteco.chess.domain.chessGame.ChessGame; + +public class ChessGameDto { + + private final ChessBoardDto chessBoardDto; + private final PieceColorDto pieceColorDto; + private final ChessStatusDtos chessStatusDtos; + private final boolean isEndState; + private final boolean isKingCaught; + + private ChessGameDto(final ChessBoardDto chessBoardDto, final PieceColorDto pieceColorDto, + final ChessStatusDtos chessStatusDtos, final boolean isEndState, final boolean isKingCaught) { + this.chessBoardDto = chessBoardDto; + this.pieceColorDto = pieceColorDto; + this.chessStatusDtos = chessStatusDtos; + this.isEndState = isEndState; + this.isKingCaught = isKingCaught; + } + + public static ChessGameDto of(final ChessGame chessGame) { + Objects.requireNonNull(chessGame, "체스 게임이 null입니다."); + + final ChessBoardDto chessBoardDto = ChessBoardDto.of(chessGame.getChessBoard()); + final PieceColorDto pieceColorDto = PieceColorDto.of(chessGame.getCurrentPieceColor()); + final ChessStatusDtos chessStatusDtos = ChessStatusDtos.of(chessGame.getChessGameStatus()); + final boolean isEndStatus = chessGame.isEndState(); + final boolean isKingCaught = chessGame.isKingCaught(); + + return new ChessGameDto(chessBoardDto, pieceColorDto, chessStatusDtos, isEndStatus, isKingCaught); + } + + public ChessBoardDto getChessBoardDto() { + return chessBoardDto; + } + + public PieceColorDto getPieceColorDto() { + return pieceColorDto; + } + + public ChessStatusDtos getChessStatusDtos() { + return chessStatusDtos; + } + + public boolean isEndState() { + return isEndState; + } + + public boolean isKingCaught() { + return isKingCaught; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessGameDto that = (ChessGameDto)object; + return isEndState == that.isEndState && + isKingCaught == that.isKingCaught && + Objects.equals(chessBoardDto, that.chessBoardDto) && + Objects.equals(pieceColorDto, that.pieceColorDto) && + Objects.equals(chessStatusDtos, that.chessStatusDtos); + } + + @Override + public int hashCode() { + return Objects.hash(chessBoardDto, pieceColorDto, chessStatusDtos, isEndState, isKingCaught); + } + +} diff --git a/src/main/java/wooteco/chess/service/dto/ChessStatusDto.java b/src/main/java/wooteco/chess/service/dto/ChessStatusDto.java new file mode 100644 index 0000000000..e3936163e2 --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/ChessStatusDto.java @@ -0,0 +1,50 @@ +package wooteco.chess.service.dto; + +import java.util.Objects; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class ChessStatusDto { + + private final String pieceColor; + private final String score; + + private ChessStatusDto(final String pieceColor, final String score) { + this.pieceColor = pieceColor; + this.score = score; + } + + public static ChessStatusDto of(PieceColor pieceColor, Double score) { + Objects.requireNonNull(pieceColor, "피스 색상이 null입니다."); + Objects.requireNonNull(score, "점수가 null입니다."); + + return new ChessStatusDto(pieceColor.getColor(), String.valueOf(score)); + } + + public String getPieceColor() { + return pieceColor; + } + + public String getScore() { + return score; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessStatusDto that = (ChessStatusDto)object; + return Objects.equals(pieceColor, that.pieceColor) && + Objects.equals(score, that.score); + } + + @Override + public int hashCode() { + return Objects.hash(pieceColor, score); + } + +} diff --git a/src/main/java/wooteco/chess/service/dto/ChessStatusDtos.java b/src/main/java/wooteco/chess/service/dto/ChessStatusDtos.java new file mode 100644 index 0000000000..353e3d9c80 --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/ChessStatusDtos.java @@ -0,0 +1,48 @@ +package wooteco.chess.service.dto; + +import static java.util.stream.Collectors.*; + +import java.util.List; +import java.util.Objects; + +import wooteco.chess.domain.chessGame.ChessStatus; + +public class ChessStatusDtos { + + private final List chessStatusDtos; + + private ChessStatusDtos(final List chessStatusDtos) { + this.chessStatusDtos = chessStatusDtos; + } + + public static ChessStatusDtos of(final ChessStatus chessStatus) { + Objects.requireNonNull(chessStatus, "체스 상태가 null입니다."); + + return chessStatus.getChessStatus().entrySet() + .stream() + .map(entry -> ChessStatusDto.of(entry.getKey(), entry.getValue())) + .collect(collectingAndThen(toList(), ChessStatusDtos::new)); + } + + public List getChessStatusDtos() { + return chessStatusDtos; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final ChessStatusDtos that = (ChessStatusDtos)object; + return Objects.equals(chessStatusDtos, that.chessStatusDtos); + } + + @Override + public int hashCode() { + return Objects.hash(chessStatusDtos); + } + +} diff --git a/src/main/java/wooteco/chess/service/dto/PieceColorDto.java b/src/main/java/wooteco/chess/service/dto/PieceColorDto.java new file mode 100644 index 0000000000..4a9a3b2867 --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/PieceColorDto.java @@ -0,0 +1,42 @@ +package wooteco.chess.service.dto; + +import java.util.Objects; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class PieceColorDto { + + private final String pieceColor; + + private PieceColorDto(final String pieceColor) { + this.pieceColor = pieceColor; + } + + public static PieceColorDto of(final PieceColor pieceColor) { + Objects.requireNonNull(pieceColor, "피스 색상이 null입니다."); + + return new PieceColorDto(pieceColor.getColor()); + } + + public String getPieceColor() { + return pieceColor; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + final PieceColorDto that = (PieceColorDto)object; + return Objects.equals(pieceColor, that.pieceColor); + } + + @Override + public int hashCode() { + return Objects.hash(pieceColor); + } + +} diff --git a/src/main/java/wooteco/chess/util/ChessBoardRenderer.java b/src/main/java/wooteco/chess/util/ChessBoardRenderer.java new file mode 100644 index 0000000000..c8f5a035a7 --- /dev/null +++ b/src/main/java/wooteco/chess/util/ChessBoardRenderer.java @@ -0,0 +1,61 @@ +package wooteco.chess.util; + +import static java.util.stream.Collectors.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.position.ChessFile; +import wooteco.chess.domain.position.ChessRank; +import wooteco.chess.domain.position.Position; + +public class ChessBoardRenderer { + + private static final String EMPTY_SPACE = ""; + private static final String DELIMITER = " "; + private static final String RANK_APPEND_FORMAT = "%s %s"; + private static final String EMPTY_POSITION = "."; + + public static List render(final ChessBoard chessBoard) { + Objects.requireNonNull(chessBoard, "체스 보드가 null입니다."); + final List renderedChessBoard = renderEachChessRankFrom(chessBoard); + + Collections.reverse(renderedChessBoard); + appendChessFileName(renderedChessBoard); + return renderedChessBoard; + } + + private static List renderEachChessRankFrom(final ChessBoard chessBoard) { + return Arrays.stream(ChessRank.values()) + .map(chessRank -> renderChessRankFrom(chessBoard, chessRank)) + .collect(toList()); + } + + private static String renderChessRankFrom(final ChessBoard chessBoard, final ChessRank chessRank) { + return Arrays.stream(ChessFile.values()) + .map(chessFile -> renderChessPieceFrom(chessBoard, chessRank, chessFile)) + .collect(collectingAndThen(joining(DELIMITER), + renderedRank -> String.format(RANK_APPEND_FORMAT, renderedRank, chessRank))); + } + + private static String renderChessPieceFrom(final ChessBoard chessBoard, final ChessRank chessRank, + final ChessFile chessFile) { + final Position renderingPosition = Position.of(chessFile, chessRank); + + if (chessBoard.isChessPieceOn(renderingPosition)) { + return chessBoard.getChessPieceNameOn(renderingPosition); + } + return EMPTY_POSITION; + } + + private static void appendChessFileName(final List renderedChessBoard) { + renderedChessBoard.add(EMPTY_SPACE); + renderedChessBoard.add(Arrays.stream(ChessFile.values()) + .map(ChessFile::toString) + .collect(joining(DELIMITER))); + } + +} diff --git a/src/main/java/wooteco/chess/util/StringUtil.java b/src/main/java/wooteco/chess/util/StringUtil.java new file mode 100644 index 0000000000..242c74c80c --- /dev/null +++ b/src/main/java/wooteco/chess/util/StringUtil.java @@ -0,0 +1,21 @@ +package wooteco.chess.util; + +import static java.util.stream.Collectors.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class StringUtil { + + private static final String DELIMITER = " "; + + public static List splitChessCommand(final String chessCommand) { + Objects.requireNonNull(chessCommand, "분리할 명령어가 null입니다."); + + return Arrays.stream(chessCommand.split(DELIMITER)) + .map(String::trim) + .collect(toList()); + } + +} diff --git a/src/main/java/wooteco/chess/view/ConsoleInputView.java b/src/main/java/wooteco/chess/view/ConsoleInputView.java new file mode 100644 index 0000000000..84b9a39d11 --- /dev/null +++ b/src/main/java/wooteco/chess/view/ConsoleInputView.java @@ -0,0 +1,14 @@ +package wooteco.chess.view; + +import java.util.Scanner; + +public class ConsoleInputView { + + private static final Scanner SCANNER = new Scanner(System.in); + + public static String inputChessCommand() { + System.out.print(ConsoleOutputView.PROMPT); + return SCANNER.nextLine(); + } + +} diff --git a/src/main/java/wooteco/chess/view/ConsoleOutputView.java b/src/main/java/wooteco/chess/view/ConsoleOutputView.java new file mode 100644 index 0000000000..9626f354f0 --- /dev/null +++ b/src/main/java/wooteco/chess/view/ConsoleOutputView.java @@ -0,0 +1,40 @@ +package wooteco.chess.view; + +import java.util.List; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +public class ConsoleOutputView { + + static final String PROMPT = "> "; + + private static void printNewLine() { + System.out.println(); + } + + public static void printChessStart() { + System.out.println(PROMPT + "체스 게임을 시작합니다."); + System.out.println(PROMPT + "게임 시작 : start"); + System.out.println(PROMPT + "게임 이동 : move source위치 target위치 - 예. move b2 b3"); + System.out.println(PROMPT + "게임 점수 : status 체스 색상 - 예. status white, status black"); + System.out.println(PROMPT + "게임 종료 : end"); + } + + public static void printChessBoard(List renderedChessBoard) { + printNewLine(); + renderedChessBoard.forEach(System.out::println); + } + + public static void printStatus(PieceColor statusPieceColor, double score) { + System.out.println(String.format("%s 점수 : %.1f", statusPieceColor.getColor(), score)); + } + + public static void printKingCaught(PieceColor catchingPieceColor) { + System.out.println(String.format("%s가 킹을 잡았습니다.", catchingPieceColor.getColor())); + } + + public static void printChessEnd() { + System.out.println(PROMPT + "게임 종료!"); + } + +} diff --git a/src/main/java/wooteco/chess/web/PieceNameConverter.java b/src/main/java/wooteco/chess/web/PieceNameConverter.java new file mode 100644 index 0000000000..2a0f2008c3 --- /dev/null +++ b/src/main/java/wooteco/chess/web/PieceNameConverter.java @@ -0,0 +1,41 @@ +package wooteco.chess.web; + +import java.util.Arrays; + +public enum PieceNameConverter { + + BLACK_KING("K", "black-king"), + WHITE_KING("k", "white-king"), + BLACK_KNIGHT("N", "black-knight"), + WHITE_KNIGHT("n", "white-knight"), + BLACK_QUEEN("Q", "black-queen"), + WHITE_QUEEN("q", "white-queen"), + BLACK_ROOK("R", "black-rook"), + WHITE_ROOK("r", "white-rook"), + BLACK_BISHOP("B", "black-bishop"), + WHITE_BISHOP("b", "white-bishop"), + BLACK_PAWN("P", "black-pawn"), + WHITE_PAWN("p", "white-pawn"); + + private static final String IMAGE_SOURCE_FORMAT = ""; + + private final String chessPieceName; + private final String imageFileName; + + PieceNameConverter(final String chessPieceName, final String imageFileName) { + this.chessPieceName = chessPieceName; + this.imageFileName = imageFileName; + } + + public static PieceNameConverter of(String chessPieceName) { + return Arrays.stream(values()) + .filter(value -> value.chessPieceName.equals(chessPieceName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("체스 피스가 유효하지 않습니다.")); + } + + public String getImageFileName() { + return String.format(IMAGE_SOURCE_FORMAT, imageFileName); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f67c3336f6..69b89983cb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1 @@ -spring.h2.console.enabled=true - -spring.datasource.driver-class-name=com.mysql.jdbc.Driver -spring.datasource.url= -spring.datasource.username= -spring.datasource.password= +spring.h2.console.enabled=true \ No newline at end of file diff --git a/src/main/resources/public/images/black-bishop.png b/src/main/resources/public/images/black-bishop.png new file mode 100644 index 0000000000000000000000000000000000000000..34a767f697fccbc04e870b84358032dcf4cd8f89 GIT binary patch literal 3178 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 j6#l1k|M;dN6}FxIAwA7 literal 0 HcmV?d00001 diff --git a/src/main/resources/public/images/black-knight.png b/src/main/resources/public/images/black-knight.png new file mode 100644 index 0000000000000000000000000000000000000000..36d5e86cc268cda5e05d2ca58520a68be0cc90f1 GIT binary patch literal 4281 zcmZ`-2UJtr(hfz5^rnyyS|Wn9gibK@5+V?~0t(WC1R)T*ARxU;2d^MaK$;Zk0-|6j z(k@lX1*8emq>5B|;C=7j_tt;^bJjU~_I&fr%$`|mud`329u`eU4W&0f5(WG{;sK&gN=36O5gX4nXis1_H>*m;n@L64}`e2*(2` zf5`yAwX+cbAkQZMOB0?=@wa>qGHQzjotaSLjZEB4bhPDcoSj6iY@MxfqTWs}=LP_j zx7?ZNgmbsz@pf_~xXF3TLw{Mwoyq5FIF#p?iTeY2sELjqkE*jPjz>yVQWODIpyuJ> zLAl!6$r-3={F{C@lZV>7ySvE2;a*-|qFxfB&aU_2Vn`$sju3~7i;J9Dh`2o@xLbLP z5ZqvYI{A+uHJqD`E8fK&?@Zu1_iJVC?BOmCg`Nlc>-sZKcf8%-kqB=8Ue?(H;pYfk zOcVkCCmPNh|Nqd=kw0m_*7avN)VVV`T~|ErEaCIC6vR-!0{oBe@AOdTUgY%f-Z)1S zHM|p!;C9ZDMIcb{e?b@Hsd9^vPG&N{VOdNU zNBb6UcdS6ZyfV*#EjuC}s%xg7QmJOijdzLx<2s1*?NYYmEcw+Xz};Gou*`1@4noD^ zp1?_UZ~)d3B(>`<*f=c^YOZcsOaNlr6TUcT6^q{j(w34CT`_zxSnl-_uIo5+WlL@2 zu$-)b@sv+SeA}?>cY(qK&P_wPr0eCu$MS~YPppAQMdbVN=d{_CO*HO0DmomuN4q1H zgbY?UNGTvC$t8;%xwP(a^?YM(U_UHR!F&@+l$23O6{jl1Hhq~yPlmCVe~4n|?T>Sp z*MfBGlw-!dibQG>h28{`68r=91_ahF*2GQ_phyi5gSQ)ovCb8AHzXXw)m+yRj;Pw)2hgysZOF@(A*9=(crD(Y9)TA4Cp z*z30J&UwR{8x{%Rd{GB{*o0K;54prrjUTe;Pm`>{lKJeZ{AT|h-xif(7JWse`Z)Js zqUnuW&5IW|RTZZTQq34`xJY4}+#MNtk;WABGH@=zVv>g2xLeK1Ki#$)Ypx;R3NrBS>@#nJXmxkbo(9dUUHhKHm%M`s=AT3XSaO9 zYN{Z2&ZGg+qG6n20h9PXV=V;lbPQy1=WBg z>`YKuS>Gmb3cdPR^D4VuPXkCvS0GElCA^KwY6D2DDtER03CBcm@Wp4kZmFj_UAYo6 zL2Hx+2^4`hf)W!Gg}M4)t=BmCVggnK6ez4bwxXtwU@qeH-NF*`CrjixgEzdYpGk=5 zLWr@iADepe5<9XF-?zzb?)$dT8q^V{)R<}THOVveNS#Ufh@V6;`W_iZl8}Icf<Xbke{0gF#q{QFW-9IqZ9w$FV+w%I9pB#fJ?&Z zb8YYQSEa7^eR|u{5}iqHvNOlb!)2VeRzHL&rOT-doviV*%~znD|EPU?$I{5Bz^Kzh zsZczzQ-`*m9qqlhY`1YR;xV9`VhTp%@fl!|p1q2;=bL7W%zi)RB^cE|UyjU^hv+>A zYP@d%-u-+#QcSm%KNrl@sJ`*WSYPtHix@rS51?RkP_M03km{@KY@X+{S(K+Agy$U1 z2#WU^#&S?y#iOBpu(_s}3Fv!g=*wJ_y}X_;P$B+0pEQhmz^pSFgdUuLCb?An?m&3nHivrh}cOLw&`#btva`XXN= zdNHpL*G{!Y-TBJ;a%agJ`hMgjL=!{!K_SeA3%4+DlmZ&vTRPW~R@I%?(8C$vYRdd9 z7}w25d{I802B4-Q&X^akR@Lj-sjg%RyC2;21}DbHTOybA=Zt%J?-m~!f&=wIAg!l(T>mQaS zDLhnv%R4GmZWnr2Bi4ECL`WC~t4x!kbH*?}n+9^cH9H>UFCS#TI8(Ag7gjbJW!k`( zR^X8?Ju%Y~-2L8y2&tz+Z*P-IzYf+!$5Sx+gOt)Ur9Sq`&RjQlM%(OM zLv42#J={_HEWDI(rKa7jt5?>NOsKn;=lDmGuJnUC!Zb68h5scD62RNvKD7o%&jTCP zbu^#fV9WHPeRT+X{Zh!BO`=F+UvZ2DJukZ&a`?1^5om!mZ}gNR@x&rx_=D`n)}@hP zsyPHqx`QxPX>7^zS=yqL?2=ZIdF+_k%-lpEgYesIQU|v)>(AJ;jfd(Bt;BH9XPd_f zGd>Frka6r|Sn6>0Ynu+RwO&V?veeqG#=B4E3t0dKzlQ`5Cnfns)4Ca4cxd9ROo_Z> zjfb!5DCR<^)_^Mh^A zs;wgV`h-(HIh&2G;u@m&K_bCE-&b%E%3SFKVfj=+M%fKcZql(k*oDf+XbXYHPD*2sN0n z4u@}t((3~y-X1#QSe?+uAy0^3l|;Q(Mqa1~41ik?@`u`=oJy)aizC{uDJGgKFY~Kk z+w|Aib}%%H2&ezz&`vFUcQKes@oFDN^!UlHufZ)&(-?@PgJsJC1wUh*<2aO-V(E67 z*|L%LrzWvH6C2Z%d2!d{G{A8ZC+Y0-Yogz377olr`k85AwoY=1o;B7wiYjbt5#OPT zU86s86Tr2Bh;64mx%RjKGvCGW`)bnG=x_$^YlAT%wRt7UA2`E1x_q%RX^hW8g-&KI~ynONxPRKGQ_JDbn$vQ=8x6PFU%oWuA?^_1EoRFlqDI(9P; zYOpbpERik;!_7pl@z|7i?2t@J`^N3|VfLc9s3JvJL_VJ=76Aa7&pvH|Qa447c>%&4 z^PafLww6$99ETr7jA|@^(4C$dH|pE{NmA@iL`hrb?(Lh)_K4ak-pAkNws6+RyUQ}9 zP|^ibN96C!xlPdjdqVinfnicvNm`L=7mtmp@N-OV-y&F(P}Ns89I5)|Y5ewQIVxtC z5RX7%4CKu;`&WCkF}<|7d>pn=(Vq14rP+%z7m{Nu_8b*o3PCSVogw^ z3Za`5K{N3_zHJnngoe9&(iYOU-n0cFH}6%(uyc>tt3Ow|`}8Z+W6y66Lu#~Lu6M*D zDA>M+9>mY~S8`hoU(AKrLR#8uWWn0jAu&xRmtYqR7ch70aN10c>83#sB~S literal 0 HcmV?d00001 diff --git a/src/main/resources/public/images/black-pawn.png b/src/main/resources/public/images/black-pawn.png new file mode 100644 index 0000000000000000000000000000000000000000..0c78bfa22319cc1c8d07a28def6c5018c4e533cb GIT binary patch literal 2813 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/public/images/black-rook.png b/src/main/resources/public/images/black-rook.png new file mode 100644 index 0000000000000000000000000000000000000000..c0868b068057336d59b39dbd3f9131f2b518c10b GIT binary patch literal 2784 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/public/images/white-pawn.png b/src/main/resources/public/images/white-pawn.png new file mode 100644 index 0000000000000000000000000000000000000000..0bcb589842feb7bf83ef7018d3ae83e67180e6c4 GIT binary patch literal 3877 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 X&L>H+) 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/public/images/white-rook.png b/src/main/resources/public/images/white-rook.png new file mode 100644 index 0000000000000000000000000000000000000000..c46d82284e10af7a430fbeed3d6623495a837eda GIT binary patch literal 3554 zcma)9c{r478y`mYCD}D6L&-ifb~BA>>>1g~T6SY(ol({)lu%?uMV5@6gK`jAijc8p z8CyzWvV<%VrKxY!`A+A%uJe7@_rBNrKKJ(f-S_?6|2$8kg_#i#r#L470N^n`rEkSp zmK>fZ2DjV0BD&0D$TguCHzf7`ncfy|K5cDL|efvjdo!4gr7+iHY$7;&A|$ zA2I-NoUsA`%z4azS>p46zvO-A!PCmIj1U%_jeUr{>1l*Vpg+XTGtk`&66PPY9{@mx zAsC{+SBM)p%-=77fC$r;`VoO($opof6!=F-$a!rkds7RrULf8JtPW9wz@$)|U@#bo z_w+_s=^OqDXK30|J|Q7N2q-i(G!zo53JJtxp~`SL912r`s;DS2B9sW>0U>T-N&$qU zKS}=0qwhuVz~h2KaDf5feO@>BzzZSTQd0Yg{(64a8G`fvl_`MmXIqR0q5B@FG6V+w zCzw|l?!UnHJwIVT`udp;xle{L$K$*h1@D)IQbztr@E^8cIq&xc;Y977mdxq3fu^*+rcm#J z?8t%8r+ccQA9Cz$XqqX}My*HR@~^Xpu^^d`F#VHiQOYh@qjhOxwzILiy86t-$AJQ) zlt&^WBIDrcE9=;mH?JEnEYEMubzx0cwZk_(!-+(?g0!@B6M4sW@nu5LfoJkp^8*iU zxLxH7MkcJgfcP+=8Nf3(t?~GW^|iHIsM!`E5O~EXT+JF4}rIm4_!f42#F(_h#DTrLWjbWvXH| zbk{CKpQ~(aL$iN!R!~rg4E+9$u31-KS6^QrQ;7YeTW?J;q2^I#Hp%GP;=LWOyJX+pw>%uUYborTZVpkB&3>&CT~`jbnwlAyRYa^lI_SE#hZ0 zJ((k~m;e5|?I-l5l0}EUT=be5sZ7$k(Ik8T0e`!4Q7=9eN7E^+SI6^H{Dv3Nl3{0$ ziu9l_5k%{S9A(&4f(sHq$~v4-+Z@c2FB6}P@+6tn%wJLyGK>R6)cbBdm-EO0QC6^~ zAQqpOhekc~HSHWG9$?t;hq*^4++`&mH(bEC7L;(eIF_WcC7$u6Ns)$L*7U%XgqFB3 zh$pI&+Dqr+4mIe`Nz#1xOHh-)Ao`gj=X`pEhd_-PZ+cLHkttqDNfE#M>E_jM-OLRE@tg=OTckx-cD(s56KBucQI7= zZPc;s6r;4c>Y>7(yPy1Wy13s0h4O6H6z)$7Ut zgnR7>NVS%ChJ@<1%{Zxm-$YpPr%&Wg6$MYEcy}ToW8E?D)7;%nKw{Y?1J%g27kd|% zX=n~HMY(X(WE~`|M{K zE2(lf&S+M!;bl z^N;)wijEkWQoW`i%QOJs@=MkjWg=w^5~Aix%%V>mS1q+)(oR&uosS2KIU?giH{x}9j!a>>+5?M z(RuI-@HUO4b@{QEAU)Nl%|Wu>Q{~-?!vVFPasUMs z@k@xbFmGj>b-yv_saJP8<(7flVn^efN6uq(L+`6r{Oa&wVyi4IOY0koo^2A0Q?XDG z9;H&78$#Envmg@zEe=DmEJ0$1gt~d}biIC=^L~{nQQm5hc4I7sX{xTKS+$`%7~`8V zG&GcrdhOZ{yi3_AR<#U4ux&l9E^(6Ynd<6}W~RnCcCU%pFaiuQUPq-WX-cI8ny z@LQigAx7F=6jNYVzrAu-Q5??!vrQY7q82F3z8WXiJBHI&5hLm{lo-3o0lu90%$wdt z(n&S(W+b3(wW@ZM!L>kZibaB!4lfl{z7}l>ZcO0!IpbUB;&x8ocI7UE%)%#qsI$;d zNlD4<^vR%DkWWeS6WboMe2m|U#&u#9si6A+OkD%Rvxs4v62hy=lsUBR-Ih_)|3hhx z1&&S<@dFCke$mB~=9WsgI?Lj7L2Z#jt9G$-9r7x#!3hEc>&5QW!X0_ZCiL#MTQGCzrf|UXssUim+nu9(o(gDQd9YVn9%*HrDOuRV2kW~{`~nB zOY6HT+VJ&8l$^i~iC%tc!WL030`EFg{fY1U5#W6~VA*c5L~<|<*x%E;B9?Q5SjxY& zXGNs}ln0R_4+aiIv3(%Bv>BP+JjR<-l6vO})WDRNI~uMmcU-C6)-v3&?U8}BpJl>5 zkSJG>_gNfoxX;$kJNV*@Jz-xwrQ%~=`ny%xH0G_Mj%>pvYvoDxH^LY3$7Zi6ebnJg zncTBsBuYrUM-#Xu#T^+DQMzwH*Ua`c4#V2($ZB_OZEdM~i;SSbt&~Vd6-BU7%dM0g z5JTbeY<#aa{lJ2cY@G&d`CMrnE=hy!6^8*Q;QWJKZiHrzHtL%s7Y_-(91A`CUjoFG6n)#iY)O$y%fKK94g_m{#^2?J3H;U<2Qc7@( z@@)IWT&VG2)bJA*vNYR0q4!MrQO5k!FPimZ?tlOfez><*nxo|bi`W7&{`xWL(+;huxn_WzkaKM3TtOvOe9uQa5YpX709k^WV`FD}qVm)@3GmXp9EPSnA3g;1JVP7vHYeRnMgTLm zXZBokK`*(>tEzGbdyeZ=H8;OX)5}WDIEhF~)vIjyh%YE8Nbi^Rs==?nZ4+y*MBdq| z=H1|n%VSgTI(W2Q?srutPCi8$*!T>Ai4=E?La}jS|J2voVFOwy4p!WdTN!DqR=bgw zkTUyHR=4}BWQPQJrSqHS?CbTlovNy;sjzgVQk3x5ovij_p3)RU=Bf$HVq4R_7ru+nndBB#4i#{h-8iR6>iyCUJcmGONuV zIA=YrZFCbP)}`EUsH%ItEJkAHsqOW3!#khvgWJb>+%kGjd2TshEADDf%N*!d-_Ydt z;xvhj+_PK%t`|-uHM)LYP?qtZ#4imOVF0_C(Fs=Gz1+)7v7!>Lr|5@W*H2C-(44HkX`v?&h>K3N4(DM(8bEeWq lQHgXay4{KUgB1ygP8Y-zZke&P zG4=PFFD>|W%2AkLPihYUtQ*}%t{a>^37as`;oTZ|K<=PO&~pIj9j?km4*G3J;9g1O zrxkL>apNI{To;VzZ$>62H;~?Tl$(=_w--x`W;4{K?;Dr5PN3n@>7C|64QGxAM`v?h z`M8ZgH2QQ>@nDLeqB%LzY0PP;#LRexh?4266Lo yk5 literal 0 HcmV?d00001 diff --git a/src/main/resources/public/style.css b/src/main/resources/public/style.css new file mode 100644 index 0000000000..63f78d3783 --- /dev/null +++ b/src/main/resources/public/style.css @@ -0,0 +1,194 @@ +@import url(//fonts.googleapis.com/css?family=Yanone+Kaffeesatz); +@import url(//fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); +@import url(//fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic); +@import url(//fonts.googleapis.com/earlyaccess/hanna.css); + +@font-face { + font-family: "BMHANNAAir"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_four@1.0/BMHANNAAir.woff") format("woff"); + font-weight: bold; + font-style: normal; +} + +* { + box-sizing: border-box; + font-family: "BMHANNAAir", -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; +} + +article > form { + text-align: center; +} + +#start__button { + display: inline-block; + padding: 15px 0; + margin: auto; + width: 120px; + text-align: center; + font-size: 1.2rem; + border: none; + background-color: #333; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +#start__button:hover { + background-color: #f8585b; +} + +header > h1 { + margin: 2rem auto; + font-size: 3rem; + text-align: center; +} + +table.chessboard { + margin: 2rem auto 2rem auto; + border: 30px solid #333; + background-color: #777; + border-collapse: collapse; +} + +td.chessboard { + display: inline-block; + width: 80px; + height: 80px; + border: 2px solid #333; +} + +tr.chessboard:nth-child(odd) td.chessboard:nth-child(even), +tr.chessboard:nth-child(even) td.chessboard:nth-child(odd) { + background: rgb(235, 235, 235); +} + +img.chessboard { + width: 100%; + height: 100%; +} + +article.chessgame__turn { + text-align: center; + line-height: 1.6; + font-size: 2rem; + margin: 0; +} + +#chessgame__turn__area { + width: 40rem; + display: inline-block; + text-align: center; + border: 3px solid #333; + background-color: #777; + color: #fff; +} + +article.chess__status { + text-align: center; + line-height: 1.6; + font-size: 2rem; + margin: 1rem; +} + +#chess__status__area { + width: 40rem; + display: inline-block; + text-align: center; + border: 3px solid #333; + background-color: #777; + color: #fff; +} + +article.move__command { + text-align: center; + line-height: 1.6; + font-size: 2rem; +} + +.move__command__area { + display: inline-block; + margin: 0.5rem auto; + padding: 0.8rem; + width: 5rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +.move__command__area::placeholder { + color: #999; +} + +.move__command__submit { + display: inline-block; + padding: 0.8rem; + margin: 0.5rem auto; + width: 6rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +.move__command__submit:hover { + background-color: #f8585b; +} + +article.chess_command { + display: flex; + flex-direction: row; + justify-content: center; +} + +.end__command__submit { + padding: 0.8rem; + margin: 2rem 2rem; + width: 10rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +.end__command__submit:hover { + background-color: #f8585b; +} + +.restart__command__submit { + padding: 0.8rem; + margin: 2rem 2rem; + width: 10rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +.restart__command__submit:hover { + background-color: #f8585b; +} + +article.king__caught { + text-align: center; + line-height: 1.6; + font-size: 2.5rem; +} diff --git a/src/main/resources/templates/chess.hbs b/src/main/resources/templates/chess.hbs new file mode 100644 index 0000000000..9352cc2923 --- /dev/null +++ b/src/main/resources/templates/chess.hbs @@ -0,0 +1,129 @@ + + + + + + + 스티치의 체스 게임 + + +
+

Chess Game

+
+
+
+ {{#piece_color}} {{pieceColor}}턴입니다. {{/piece_color}} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{{a8}}}{{{b8}}}{{{c8}}}{{{d8}}}{{{e8}}}{{{f8}}}{{{g8}}}{{{h8}}}
{{{a7}}}{{{b7}}}{{{c7}}}{{{d7}}}{{{e7}}}{{{f7}}}{{{g7}}}{{{h7}}}
{{{a6}}}{{{b6}}}{{{c6}}}{{{d6}}}{{{e6}}}{{{f6}}}{{{g6}}}{{{h6}}}
{{{a5}}}{{{b5}}}{{{c5}}}{{{d5}}}{{{e5}}}{{{f5}}}{{{g5}}}{{{h5}}}
{{{a4}}}{{{b4}}}{{{c4}}}{{{d4}}}{{{e4}}}{{{f4}}}{{{g4}}}{{{h4}}}
{{{a3}}}{{{b3}}}{{{c3}}}{{{d3}}}{{{e3}}}{{{f3}}}{{{g3}}}{{{h3}}}
{{{a2}}}{{{b2}}}{{{c2}}}{{{d2}}}{{{e2}}}{{{f2}}}{{{g2}}}{{{h2}}}
{{{a1}}}{{{b1}}}{{{c1}}}{{{d1}}}{{{e1}}}{{{f1}}}{{{g1}}}{{{h1}}}
+
+
+
+ {{#status}} {{pieceColor}}의 점수 : {{score}} +
+ {{/status}} +
+
+
+
+ 이동시킬 피스 :  + +
+ 이동시킬 위치 :  + +
+ +
+
+
+
+ +
+
+ +
+
+ + diff --git a/src/main/resources/templates/index.hbs b/src/main/resources/templates/index.hbs index 011c8cb828..2eee3b2f7e 100644 --- a/src/main/resources/templates/index.hbs +++ b/src/main/resources/templates/index.hbs @@ -1,9 +1,19 @@ - - + + - 모두의 체스 + + + + 스티치의 체스 게임 -

Hello World

+
+

Chess Game

+
+
+
+ +
+
- \ No newline at end of file + diff --git a/src/main/resources/templates/result.hbs b/src/main/resources/templates/result.hbs new file mode 100644 index 0000000000..2e15e115aa --- /dev/null +++ b/src/main/resources/templates/result.hbs @@ -0,0 +1,114 @@ + + + + + + + 스티치의 체스 게임 + + +
+

Game End!!!

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{{a8}}}{{{b8}}}{{{c8}}}{{{d8}}}{{{e8}}}{{{f8}}}{{{g8}}}{{{h8}}}
{{{a7}}}{{{b7}}}{{{c7}}}{{{d7}}}{{{e7}}}{{{f7}}}{{{g7}}}{{{h7}}}
{{{a6}}}{{{b6}}}{{{c6}}}{{{d6}}}{{{e6}}}{{{f6}}}{{{g6}}}{{{h6}}}
{{{a5}}}{{{b5}}}{{{c5}}}{{{d5}}}{{{e5}}}{{{f5}}}{{{g5}}}{{{h5}}}
{{{a4}}}{{{b4}}}{{{c4}}}{{{d4}}}{{{e4}}}{{{f4}}}{{{g4}}}{{{h4}}}
{{{a3}}}{{{b3}}}{{{c3}}}{{{d3}}}{{{e3}}}{{{f3}}}{{{g3}}}{{{h3}}}
{{{a2}}}{{{b2}}}{{{c2}}}{{{d2}}}{{{e2}}}{{{f2}}}{{{g2}}}{{{h2}}}
{{{a1}}}{{{b1}}}{{{c1}}}{{{d1}}}{{{e1}}}{{{f1}}}{{{g1}}}{{{h1}}}
+
+
+ {{#is_king_caught}} {{piece_color.pieceColor}}가 왕을 잡았습니다!!! {{/is_king_caught}} +
+
+
+ {{#status}} + {{pieceColor}}의 점수 : {{score}} +
+ {{/status}} +
+
+
+
+ +
+
+ + diff --git a/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java b/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java new file mode 100644 index 0000000000..8eed5866d2 --- /dev/null +++ b/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java @@ -0,0 +1,45 @@ +package wooteco.chess.dao; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import wooteco.chess.entity.ChessGameEntity; + +public class InMemoryChessGameDao implements ChessGameDao { + + private final Map chessGameRepository = new HashMap<>(); + private long autoIncrement; + + public InMemoryChessGameDao() { + this.autoIncrement = 0L; + } + + @Override + public long add(final ChessGameEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + autoIncrement++; + + final ChessGameEntity chessGameEntity = ChessGameEntity.of(autoIncrement, entity); + + chessGameRepository.put(autoIncrement, chessGameEntity); + return autoIncrement; + } + + @Override + public long findMaxGameId() { + return autoIncrement; + } + + @Override + public boolean isEmpty() { + return chessGameRepository.isEmpty(); + } + + @Override + public void deleteAll() { + autoIncrement = 0L; + chessGameRepository.clear(); + } + +} diff --git a/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java b/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java new file mode 100644 index 0000000000..d67c80a634 --- /dev/null +++ b/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java @@ -0,0 +1,43 @@ +package wooteco.chess.dao; + +import static java.util.stream.Collectors.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import wooteco.chess.entity.ChessHistoryEntity; + +public class InMemoryChessHistoryDao implements ChessHistoryDao { + + private final Map chessHistoryRepository = new HashMap<>(); + private long autoIncrement; + + public InMemoryChessHistoryDao() { + this.autoIncrement = 0L; + } + + @Override + public List findAllByGameId(final long gameId) { + return chessHistoryRepository.values().stream() + .filter(entity -> entity.getGameId() == gameId) + .collect(toList()); + } + + @Override + public void add(final ChessHistoryEntity entity) { + Objects.requireNonNull(entity, "엔티티가 null입니다."); + autoIncrement++; + + final ChessHistoryEntity chessHistoryEntity = ChessHistoryEntity.of(autoIncrement, entity); + + chessHistoryRepository.put(autoIncrement, chessHistoryEntity); + } + + @Override + public void deleteAll() { + chessHistoryRepository.clear(); + } + +} diff --git a/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java b/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java new file mode 100644 index 0000000000..b0eb5f40ad --- /dev/null +++ b/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java @@ -0,0 +1,133 @@ +package wooteco.chess.dao; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.database.JdbcTemplate; +import wooteco.chess.database.MySqlDataSource; +import wooteco.chess.entity.ChessGameEntity; + +class MySqlChessGameDaoTest { + + private static final String CHESS_GAME_TABLE = "chess_games"; + private static final long INIT_AUTO_INCREMENT = 1L; + + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void setUp() { + jdbcTemplate = new JdbcTemplate(MySqlDataSource.getInstance()); + + final String query1 = "DELETE FROM " + CHESS_GAME_TABLE; + jdbcTemplate.executeUpdate(query1); + + final String query2 = "ALTER TABLE " + CHESS_GAME_TABLE + " AUTO_INCREMENT = ?"; + jdbcTemplate.executeUpdate(query2, + preparedStatement -> preparedStatement.setLong(1, INIT_AUTO_INCREMENT)); + } + + @ParameterizedTest + @NullSource + void MySqlChessGameDao_NullJdbcTemplate_ExceptionThrown(final JdbcTemplate jdbcTemplate) { + assertThatThrownBy(() -> new MySqlChessGameDao(jdbcTemplate)) + .isInstanceOf(NullPointerException.class) + .hasMessage("JdbcTemplate이 null입니다."); + } + + @Test + void MySqlChessGameDao_JdbcTemplate_GenerateInstance() { + assertThat(new MySqlChessGameDao(jdbcTemplate)).isInstanceOf(MySqlChessGameDao.class); + } + + @ParameterizedTest + @NullSource + void add_NullChessGameEntity_ExceptionThrown(final ChessGameEntity entity) { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + + assertThatThrownBy(() -> mySqlChessGameDao.add(entity)) + .isInstanceOf(NullPointerException.class) + .hasMessage("엔티티가 null입니다."); + } + + @Test + void add_ChessGameEntity_InsertChessGame() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); + + assertThat(mySqlChessGameDao.add(entity)).isEqualTo(INIT_AUTO_INCREMENT); + } + + @Test + void findMaxGameId_EmptyChessGame_ExceptionThrown() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + + assertThat(mySqlChessGameDao.findMaxGameId()).isEqualTo(MySqlChessGameDao.EMPTY_CHESS_GAME); + } + + @Test + void findMaxGameId_ReturnMaxGameId() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); + mySqlChessGameDao.add(entity); + + assertThat(mySqlChessGameDao.findMaxGameId()).isEqualTo(INIT_AUTO_INCREMENT); + } + + @Test + void isEmpty_EmptyChessGame_ReturnTrue() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + + assertThat(mySqlChessGameDao.isEmpty()).isTrue(); + } + + @Test + void isEmpty_NotEmptyChessGame_ReturnFalse() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); + mySqlChessGameDao.add(entity); + + assertThat(mySqlChessGameDao.isEmpty()).isFalse(); + } + + @Test + void deleteAll_ExistChessGame_DeleteAllFromChessGameTable() { + final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); + final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); + mySqlChessGameDao.add(entity); + mySqlChessGameDao.deleteAll(); + + assertThat(findAll().isEmpty()).isTrue(); + } + + private List findAll() { + final String query = "SELECT * FROM " + CHESS_GAME_TABLE; + + return jdbcTemplate.executeQuery(query, resultSet -> { + final List entities = new ArrayList<>(); + + while (resultSet.next()) { + entities.add(ChessGameEntity.of( + resultSet.getLong("game_id"), + resultSet.getTimestamp("created_time").toLocalDateTime())); + } + return entities; + }); + } + + @AfterEach + void tearDown() { + final String query = "DELETE FROM " + CHESS_GAME_TABLE; + + jdbcTemplate.executeUpdate(query); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java b/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java new file mode 100644 index 0000000000..e779623f96 --- /dev/null +++ b/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java @@ -0,0 +1,131 @@ +package wooteco.chess.dao; + +import static org.assertj.core.api.Assertions.*; + +import java.sql.ResultSet; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.database.JdbcTemplate; +import wooteco.chess.database.MySqlDataSource; +import wooteco.chess.entity.ChessHistoryEntity; + +class MySqlChessHistoryDaoTest { + + private static final String CHESS_HISTORY_TABLE = "chess_histories"; + private static final long INIT_AUTO_INCREMENT = 1L; + + private JdbcTemplate jdbcTemplate; + + @BeforeEach + void setUp() { + jdbcTemplate = new JdbcTemplate(MySqlDataSource.getInstance()); + + final String query1 = "DELETE FROM " + CHESS_HISTORY_TABLE; + jdbcTemplate.executeUpdate(query1); + + final String query2 = "ALTER TABLE " + CHESS_HISTORY_TABLE + " AUTO_INCREMENT = ?"; + jdbcTemplate.executeUpdate(query2, + preparedStatement -> preparedStatement.setLong(1, INIT_AUTO_INCREMENT)); + } + + @ParameterizedTest + @NullSource + void MySqlChessHistoryDao_NullJdbcTemplate_ExceptionThrown(final JdbcTemplate jdbcTemplate) { + assertThatThrownBy(() -> new MySqlChessHistoryDao(jdbcTemplate)) + .isInstanceOf(NullPointerException.class) + .hasMessage("JdbcTemplate이 null입니다."); + } + + @Test + void MySqlChessHistoryDao_JdbcTemplate_GenerateInstance() { + assertThat(new MySqlChessHistoryDao(jdbcTemplate)).isInstanceOf(MySqlChessHistoryDao.class); + } + + @Test + void findAllByGameId_GameId_ReturnChessHistoryEntities() { + final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); + final long gameId = 1; + final ChessHistoryEntity value = ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now()); + mySqlChessHistoryDao.add(value); + + assertThat(isSame(mySqlChessHistoryDao.findAllByGameId(gameId), value)).isTrue(); + } + + private boolean isSame(final List actual, final ChessHistoryEntity expected) { + return actual.size() == 1 + && actual.get(0).getGameId() == expected.getGameId() + && actual.get(0).getStart().equals(expected.getStart()) + && actual.get(0).getEnd().equals(expected.getEnd()); + } + + @ParameterizedTest + @NullSource + void add_NullChessHistoryEntity_ExceptionThrown(final ChessHistoryEntity entity) { + final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); + + assertThatThrownBy(() -> mySqlChessHistoryDao.add(entity)) + .isInstanceOf(NullPointerException.class) + .hasMessage("엔티티가 null입니다."); + } + + @Test + void add_ChessHistoryEntity_InsertChessHistory() { + final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); + final long gameId = 1; + mySqlChessHistoryDao.add(ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now())); + + assertThat(isChessHistoryExist("b2", "b4")).isTrue(); + } + + private boolean isChessHistoryExist(final String start, final String end) { + final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE + " WHERE start = ? AND end = ?"; + + return jdbcTemplate.executeQuery(query, ResultSet::next, preparedStatement -> { + preparedStatement.setString(1, start); + preparedStatement.setString(2, end); + }); + } + + @Test + void deleteAll_DeleteAllChessHistoryFromChessHistoryTable() { + final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); + mySqlChessHistoryDao.add(ChessHistoryEntity.of("b2", "b4")); + mySqlChessHistoryDao.deleteAll(); + + assertThat(findAll().isEmpty()).isTrue(); + } + + private List findAll() { + final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE; + + return jdbcTemplate.executeQuery(query, resultSet -> { + final List entities = new ArrayList<>(); + + while (resultSet.next()) { + entities.add(ChessHistoryEntity.of( + resultSet.getLong("history_id"), + resultSet.getLong("game_id"), + resultSet.getString("start"), + resultSet.getString("end"), + resultSet.getTimestamp("created_time").toLocalDateTime())); + } + return entities; + }); + } + + @AfterEach + void tearDown() { + final String query = "DELETE FROM " + CHESS_HISTORY_TABLE; + + jdbcTemplate.executeUpdate(query); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessBoard/ChessBoardTest.java b/src/test/java/wooteco/chess/domain/chessBoard/ChessBoardTest.java new file mode 100644 index 0000000000..8236c34f28 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessBoard/ChessBoardTest.java @@ -0,0 +1,232 @@ +package wooteco.chess.domain.chessBoard; + +import static org.assertj.core.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessGame.ChessStatus; +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +public class ChessBoardTest { + + @ParameterizedTest + @NullSource + void ChessBoard_NullMapOfPositionAndChessPiece_ExceptionThrown(final Map chessBoard) { + assertThatThrownBy(() -> new ChessBoard(chessBoard)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 보드가 null입니다."); + } + + @Test + void ChessBoard_MapOfPositionAndChessPiece_GenerateInstance() { + assertThat(new ChessBoard(ChessBoardInitializer.create())).isInstanceOf(ChessBoard.class); + } + + @ParameterizedTest + @NullSource + void validate_NullPosition_ExceptionThrown(final Position position) { + assertThatThrownBy(() -> new ChessBoard(ChessBoardInitializer.create()).isKingOn(position)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 위치가 null입니다."); + } + + @ParameterizedTest + @NullSource + void validate_NullSourcePosition_ExceptionThrown(final Position sourcePosition) { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkChessPieceExistInRoute(sourcePosition, Position.of("d5"))) + .isInstanceOf(NullPointerException.class) + .hasMessage("소스 위치가 null입니다."); + } + + @ParameterizedTest + @NullSource + void validate_NullTargetPosition_ExceptionThrown(final Position targetPosition) { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkChessPieceExistInRoute(Position.of("d5"), targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("타겟 위치가 null입니다."); + } + + @Test + void isKingOn_KingExistPosition_ReturnTrue() { + final Map value = new HashMap<>(); + value.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.isKingOn(Position.of("b1"))).isTrue(); + } + + @Test + void isKingOn_KingNotExistPosition_ReturnFalse() { + final Map value = new HashMap<>(); + value.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.isKingOn(Position.of("b1"))).isFalse(); + } + + @Test + void isChessPieceOn_ChessPieceExistPosition_ReturnTrue() { + final Map value = new HashMap<>(); + value.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.isChessPieceOn(Position.of("b1"))).isTrue(); + } + + @Test + void isChessPieceOn_ChessPieceNotExistPosition_ReturnFalse() { + final Map value = new HashMap<>(); + value.put(Position.of("b2"), new ChessPiece(PieceType.WHITE_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.isChessPieceOn(Position.of("b1"))).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"BLACK,true", "WHITE,false"}) + void isSamePieceColorOn_SameSourcePositionAndPieceColor_ReturnCompareResult(final PieceColor pieceColor, + boolean expected) { + final Map value = new HashMap<>(); + value.put(Position.of("c3"), new ChessPiece(PieceType.BLACK_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.isSamePieceColorOn(Position.of("c3"), pieceColor)).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void isSamePieceColorOn_NullPieceColor_ExceptionThrown(final PieceColor pieceColor) { + final Map value = new HashMap<>(); + value.put(Position.of("c3"), new ChessPiece(PieceType.BLACK_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.isSamePieceColorOn(Position.of("b1"), pieceColor)) + .isInstanceOf(NullPointerException.class) + .hasMessage("피스 색상이 null입니다."); + } + + @Test + void canLeapChessPieceOn_LeapableChessPiece_ReturnTrue() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.canLeapChessPieceOn(Position.of("c4"))).isTrue(); + } + + @Test + void canLeapChessPieceOn_NonLeapableChessPiece_ReturnFalse() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_ROOK)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThat(chessBoard.canLeapChessPieceOn(Position.of("c4"))).isFalse(); + } + + @Test + void checkChessPieceExistInRoute_InvalidMoveDirection_ExceptionThrown() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkChessPieceExistInRoute(Position.of("b3"), Position.of("c8"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스가 이동할 수 없는 위치를 입력하였습니다."); + } + + @Test + void checkChessPieceExistInRoute_ChessPieceExistInRoute_ExceptionThrown() { + final Map value = new HashMap<>(); + value.put(Position.of("d5"), new ChessPiece(PieceType.WHITE_KING)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkChessPieceExistInRoute(Position.of("b3"), Position.of("e6"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스의 이동 경로에 다른 체스 피스가 존재합니다."); + } + + @Test + void checkCanMove_CanNotMovableChessPiece_ExceptionThrown() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_ROOK)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkCanMove(Position.of("c4"), Position.of("b3"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스가 이동할 수 없습니다."); + } + + @Test + void checkCanCatch_SamePieceColor_ExceptionThrown() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_ROOK)); + value.put(Position.of("c6"), new ChessPiece(PieceType.WHITE_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkCanCatch(Position.of("c4"), Position.of("c6"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스를 잡을 수 없습니다."); + } + + @Test + void checkCanCatch_CanNotCatchTargetPosition_ExceptionThrown() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_ROOK)); + value.put(Position.of("d5"), new ChessPiece(PieceType.BLACK_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + + assertThatThrownBy(() -> chessBoard.checkCanCatch(Position.of("c4"), Position.of("d5"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스를 잡을 수 없습니다."); + } + + @Test + void moveChessPiece_SourcePositionAndTargetPosition_MoveChessPiece() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_ROOK); + final Map value = new HashMap<>(); + value.put(Position.of("c4"), chessPiece); + final ChessBoard chessBoard = new ChessBoard(value); + chessBoard.moveChessPiece(Position.of("c4"), Position.of("b2")); + + final Map expected = new HashMap<>(); + expected.put(Position.of("b2"), chessPiece); + assertThat(chessBoard).extracting("chessBoard").isEqualTo(expected); + } + + @Test + void calculateStatus_ChessBoard_CalculateStatusOfEachPieceColor() { + final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + + final ChessStatus expected = ChessStatus.of(ChessBoardInitializer.create()); + Assertions.assertThat(chessBoard.calculateStatus()).isEqualTo(expected); + } + + @Test + void getChessPieceNameOn_Position_ReturnChessNameOnPosition() { + final Map value = new HashMap<>(); + value.put(Position.of("c4"), new ChessPiece(PieceType.WHITE_ROOK)); + final ChessBoard chessBoard = new ChessBoard(value); + + final String expected = "r"; + assertThat(chessBoard.getChessPieceNameOn(Position.of("c4"))).isEqualTo(expected); + } + +} diff --git a/src/test/java/wooteco/chess/domain/chessGame/ChessCommandTest.java b/src/test/java/wooteco/chess/domain/chessGame/ChessCommandTest.java new file mode 100644 index 0000000000..68fe349f37 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/ChessCommandTest.java @@ -0,0 +1,128 @@ +package wooteco.chess.domain.chessGame; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.position.Position; + +class ChessCommandTest { + + @ParameterizedTest + @NullAndEmptySource + void validate_NullAndEmptyCommandArguments_ExceptionThrown(final List commandArguments) { + assertThatThrownBy(() -> ChessCommand.of(commandArguments)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효한 체스 명령어가 아닙니다."); + } + + @Test + void of_InvalidArgumentSize_ExceptionThrown() { + assertThatThrownBy(() -> ChessCommand.of(Arrays.asList("status"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효한 명령어 인자가 아닙니다."); + } + + @Test + void of_CommandArguments_GenerateInstance() { + assertThat(ChessCommand.of(Arrays.asList("status", "white"))).isInstanceOf(ChessCommand.class); + } + + @Test + void isStartChessCommand_StartCommandType_ReturnTrue() { + final List commandArguments = Arrays.asList("start"); + + assertThat(ChessCommand.of(commandArguments).isStartChessCommand()).isTrue(); + } + + @Test + void isStartChessCommand_NotStartCommandType_ReturnFalse() { + final List commandArguments = Arrays.asList("status", "white"); + + assertThat(ChessCommand.of(commandArguments).isStartChessCommand()).isFalse(); + } + + @Test + void isMoveChessCommand_MoveCommandType_ReturnTrue() { + final List commandArguments = Arrays.asList("move", "b1", "b2"); + + assertThat(ChessCommand.of(commandArguments).isMoveChessCommand()).isTrue(); + } + + @Test + void isMoveChessCommand_NotMoveCommandType_ReturnFalse() { + final List commandArguments = Arrays.asList("start"); + + assertThat(ChessCommand.of(commandArguments).isMoveChessCommand()).isFalse(); + } + + @Test + void isStatusChessCommand_StatusCommandType_ReturnTrue() { + final List commandArguments = Arrays.asList("status", "white"); + + assertThat(ChessCommand.of(commandArguments).isStatusChessCommand()).isTrue(); + } + + @Test + void isStatusChessCommand_NotStatusCommandType_ReturnFalse() { + final List commandArguments = Arrays.asList("end"); + + assertThat(ChessCommand.of(commandArguments).isStatusChessCommand()).isFalse(); + } + + @Test + void isEndChessCommand_EndCommandType_ReturnTrue() { + final List commandArguments = Arrays.asList("end"); + + assertThat(ChessCommand.of(commandArguments).isEndChessCommand()).isTrue(); + } + + @Test + void isEndChessCommand_NotEndCommandType_ReturnFalse() { + final List commandArguments = Arrays.asList("start"); + + assertThat(ChessCommand.of(commandArguments).isEndChessCommand()).isFalse(); + } + + @Test + void checkIsMoveCommandType_NotMoveCommandType_ExceptionThrown() { + assertThatThrownBy(() -> ChessCommand.of(Arrays.asList("status", "white")).getSourcePosition()) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("move 명령어만 지원하는 기능입니다."); + } + + @Test + void getSourcePosition_MoveCommandType_ReturnSourcePosition() { + final List commandArguments = Arrays.asList("move", "b1", "b2"); + + assertThat(ChessCommand.of(commandArguments).getSourcePosition()).isEqualTo(Position.of("b1")); + } + + @Test + void getTargetPosition_MoveCommandType_ReturnTargetPosition() { + final List commandArguments = Arrays.asList("move", "b1", "b2"); + + assertThat(ChessCommand.of(commandArguments).getTargetPosition()).isEqualTo(Position.of("b2")); + } + + @Test + void checkIsStatusCommandType_NotStatusCommandType_ExceptionThrown() { + assertThatThrownBy(() -> ChessCommand.of(Arrays.asList("start")).getStatusPieceColor()) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("status 명령어만 지원하는 기능입니다."); + } + + @Test + void getStatusPieceColor_StatusCommandType_ReturnStatusPieceColor() { + final List commandArguments = Arrays.asList("status", "white"); + + assertThat(ChessCommand.of(commandArguments).getStatusPieceColor()).isEqualTo(PieceColor.WHITE); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/ChessGameTest.java b/src/test/java/wooteco/chess/domain/chessGame/ChessGameTest.java new file mode 100644 index 0000000000..155344efb9 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/ChessGameTest.java @@ -0,0 +1,199 @@ +package wooteco.chess.domain.chessGame; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.gameState.BlackTurnState; +import wooteco.chess.domain.chessGame.gameState.EndState; +import wooteco.chess.domain.chessGame.gameState.KingCaughtState; +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +class ChessGameTest { + + private static final String MOVE_COMMAND = "move"; + + private static Stream provideChessBoardAndMoveExpectedChessBoard() { + final Map value = new HashMap<>(); + value.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_PAWN)); + + final ChessBoard chessBoard = new ChessBoard(value); + final ChessGame chessGame = ChessGame.from(chessBoard); + + final Map expectedChessBoard = new HashMap<>(); + expectedChessBoard.put(Position.of("b2"), new ChessPiece(PieceType.WHITE_PAWN)); + final ChessBoard expected = new ChessBoard(expectedChessBoard); + + return Stream.of(Arguments.of(chessGame, expected)); + } + + @Test + void from_ChessBoard_GenerateInstance() { + assertThat(ChessGame.from(new ChessBoard(ChessBoardInitializer.create()))).isInstanceOf(ChessGame.class); + } + + @ParameterizedTest + @NullSource + void from_NullChessBoard_ExceptionThrown(final ChessBoard chessBoard) { + assertThatThrownBy(() -> ChessGame.from(chessBoard)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 보드가 null입니다."); + } + + @ParameterizedTest + @NullSource + void move_NullChessBoard_ExceptionThrown(final ChessCommand chessCommand) { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThatThrownBy(() -> chessGame.move(chessCommand)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 명령이 null입니다."); + } + + @ParameterizedTest + @MethodSource("provideChessBoardAndMoveExpectedChessBoard") + void move_ChessBoard_MoveChessPieceOnSourcePosition(final ChessGame chessGame, final ChessBoard expected) { + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b1", "b2")); + chessGame.move(chessCommand); + + assertThat(chessGame).extracting("chessBoard").isEqualTo(expected); + } + + @Test + void checkChessPieceExistOn_NotOnSourcePosition_ExceptionThrown() { + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b1", "b2")); + final Map value = new HashMap<>(); + value.put(Position.of("b3"), new ChessPiece(PieceType.WHITE_QUEEN)); + final ChessBoard chessBoard = new ChessBoard(value); + final ChessGame chessGame = ChessGame.from(chessBoard); + + assertThatThrownBy(() -> chessGame.move(chessCommand)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이동할 체스 피스가 존재하지 않습니다."); + } + + @Test + void checkCorrectChessTurn_NotCorrectPieceColorSourcePosition_ExceptionThrown() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b7", "b5")); + + assertThatThrownBy(() -> chessGame.move(chessCommand)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("순서에 맞지 않은 말을 이동하였습니다."); + } + + @ParameterizedTest + @MethodSource("provideChessBoardAndMoveExpectedChessBoard") + void shiftGameStatusBy_NotKingOnTargetPosition_ShiftNextPieceColorState(final ChessGame chessGame, + final ChessBoard expected) { + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b1", "b2")); + chessGame.move(chessCommand); + + assertThat(chessGame).extracting("gameState").isInstanceOf(BlackTurnState.class); + } + + @Test + void shiftGameStatusBy_KingOnTargetPosition_ShiftKingCaughtState() { + final Map initialChessBoard = new HashMap<>(); + initialChessBoard.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_KING)); + initialChessBoard.put(Position.of("b2"), new ChessPiece(PieceType.BLACK_KING)); + final ChessBoard chessBoard = new ChessBoard(initialChessBoard); + final ChessGame chessGame = ChessGame.from(chessBoard); + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b1", "b2")); + chessGame.move(chessCommand); + + assertThat(chessGame).extracting("gameState").isInstanceOf(KingCaughtState.class); + } + + @ParameterizedTest + @NullSource + void status_NullChessCommand_ExceptionThrown(final ChessCommand chessCommand) { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThatThrownBy(() -> chessGame.status(chessCommand)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 명령이 null입니다."); + } + + @ParameterizedTest + @CsvSource(value = {"status,white", "status,black"}) + void status_ChessCommand_CalculateStatusResult(final String commandType, final String argument) { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThat(chessGame.status(ChessCommand.of(Arrays.asList(commandType, argument)))).isEqualTo(38.0); + } + + @Test + void end_ChessCommandEnd_ShiftEndState() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessGame.end(); + + assertThat(chessGame).extracting("gameState").isInstanceOf(EndState.class); + } + + @Test + void isEndState_EndState_ResultTrue() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessGame.end(); + + assertThat(chessGame.isEndState()).isTrue(); + } + + @Test + void isEndState_NotEndState_ResultFalse() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThat(chessGame.isEndState()).isFalse(); + } + + @Test + void isKingCaught_KingCaughtState_ReturnTrue() { + final Map initialChessBoard = new HashMap<>(); + initialChessBoard.put(Position.of("b1"), new ChessPiece(PieceType.WHITE_KING)); + initialChessBoard.put(Position.of("b2"), new ChessPiece(PieceType.BLACK_KING)); + final ChessBoard chessBoard = new ChessBoard(initialChessBoard); + final ChessGame chessGame = ChessGame.from(chessBoard); + final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b1", "b2")); + chessGame.move(chessCommand); + + assertThat(chessGame.isKingCaught()).isTrue(); + } + + @Test + void isKingCaught_NotKingCaughtState_ResultFalse() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThat(chessGame.isKingCaught()).isFalse(); + } + + @Test + void getCurrentPieceColor_InitGame_ReturnWhitePieceColor() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + + assertThat(chessGame.getCurrentPieceColor()).isEqualTo(PieceColor.WHITE); + } + + @Test + void getCurrentPieceColor_MoveOnce_ReturnBlackPieceColor() { + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b2", "b4"))); + + assertThat(chessGame.getCurrentPieceColor()).isEqualTo(PieceColor.BLACK); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/ChessStatusTest.java b/src/test/java/wooteco/chess/domain/chessGame/ChessStatusTest.java new file mode 100644 index 0000000000..caf696a66f --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/ChessStatusTest.java @@ -0,0 +1,57 @@ +package wooteco.chess.domain.chessGame; + +import static org.assertj.core.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +class ChessStatusTest { + + @ParameterizedTest + @NullAndEmptySource + void of_NullChessBoard_ExceptionThrown(final Map chessBoard) { + assertThatThrownBy(() -> ChessStatus.of(chessBoard)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 보드가 존재하지 않습니다."); + } + + @Test + void of_ChessBoard_GenerateInstance() { + assertThat(ChessStatus.of(ChessBoardInitializer.create())).isInstanceOf(ChessStatus.class); + } + + @Test + void of_PawnsOnSameFile_CalculateScoreHalf() { + final Map chessBoard = new HashMap<>(); + chessBoard.put(Position.of("b3"), new ChessPiece(PieceType.BLACK_PAWN)); + chessBoard.put(Position.of("b4"), new ChessPiece(PieceType.BLACK_PAWN)); + + assertThat(ChessStatus.of(chessBoard).getStatusOf(PieceColor.BLACK)).isEqualTo(1); + } + + @ParameterizedTest + @EnumSource(PieceColor.class) + void getStatusOf_ChessBoard_CalculateEachPieceColorStatus(final PieceColor pieceColor) { + final Map chessBoard = new HashMap<>(); + chessBoard.put(Position.of("b1"), new ChessPiece(PieceType.BLACK_KING)); + chessBoard.put(Position.of("b2"), new ChessPiece(PieceType.BLACK_QUEEN)); + chessBoard.put(Position.of("b3"), new ChessPiece(PieceType.BLACK_ROOK)); + chessBoard.put(Position.of("a1"), new ChessPiece(PieceType.WHITE_KING)); + chessBoard.put(Position.of("a2"), new ChessPiece(PieceType.WHITE_QUEEN)); + chessBoard.put(Position.of("a3"), new ChessPiece(PieceType.WHITE_ROOK)); + + assertThat(ChessStatus.of(chessBoard).getStatusOf(pieceColor)).isEqualTo(14); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/CommandTypeTest.java b/src/test/java/wooteco/chess/domain/chessGame/CommandTypeTest.java new file mode 100644 index 0000000000..eae0a487fc --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/CommandTypeTest.java @@ -0,0 +1,76 @@ +package wooteco.chess.domain.chessGame; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class CommandTypeTest { + + @ParameterizedTest + @NullSource + void of_NullCommandType_ExceptionThrown(final String command) { + assertThatThrownBy(() -> CommandType.of(command)) + .isInstanceOf(NullPointerException.class) + .hasMessage("명령이 null입니다."); + } + + @Test + void of_InvalidCommandType_ExceptionThrown() { + assertThatThrownBy(() -> CommandType.of("new")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효한 명령어가 아닙니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"start", "end", "move", "status"}) + void of_InputCommand_ReturnInstance(final String command) { + assertThat(CommandType.of(command)).isInstanceOf(CommandType.class); + } + + @ParameterizedTest + @CsvSource(value = {"START,true", "END,false", "MOVE,false", "STATUS,false"}) + void isStartCommandType_ThisCommandType_ReturnCompareResult(final CommandType commandType, + boolean expected) { + assertThat(commandType.isStartCommandType()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"START,false", "END,false", "MOVE,true", "STATUS,false"}) + void isMoveCommandType_ThisCommandType_ReturnCompareResult(final CommandType commandType, + final boolean expected) { + assertThat(commandType.isMoveCommandType()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"START,false", "END,false", "MOVE,false", "STATUS,true"}) + void isStatusCommandType_ThisCommandType_ReturnCompareResult(final CommandType commandType, + final boolean expected) { + assertThat(commandType.isStatusCommandType()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"START,false", "END,true", "MOVE,false", "STATUS,false"}) + void isEndCommandType_ThisCommandType_ReturnCompareResult(final CommandType commandType, + final boolean expected) { + assertThat(commandType.isEndCommandType()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"START,1", "END,1", "MOVE,3", "STATUS,2"}) + void isCorrectArgumentsSize_CorrectCommandArgumentsSize_ReturnTrue(final CommandType commandType, + final int argumentsSize) { + assertThat(commandType.isCorrectArgumentsSize(argumentsSize)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"START,2", "END,2", "MOVE,1", "STATUS,0"}) + void isCorrectArgumentsSize_IncorrectCommandArgumentsSize_ReturnFalse(final CommandType commandType, + final int argumentsSize) { + assertThat(commandType.isCorrectArgumentsSize(argumentsSize)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/BlackTurnStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/BlackTurnStateTest.java new file mode 100644 index 0000000000..2d754f109a --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/BlackTurnStateTest.java @@ -0,0 +1,26 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class BlackTurnStateTest { + + @Test + void BlackTurnState_BlackPieceColor_GenerateInstance() { + assertThat(new BlackTurnState()).isInstanceOf(BlackTurnState.class); + } + + @Test + void shiftNextTurnState_WhitePieceColor_ReturnBlackTurnState() { + assertThat(new BlackTurnState().shiftNextTurnState()).isInstanceOf(WhiteTurnState.class); + } + + @Test + void getPieceColor_BlackTurnState_ReturnBlackPieceColor() { + assertThat(new BlackTurnState().getPieceColor()).isEqualTo(PieceColor.BLACK); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessEndStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessEndStateTest.java new file mode 100644 index 0000000000..b7ffe37a59 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessEndStateTest.java @@ -0,0 +1,42 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class ChessEndStateTest { + + @ParameterizedTest + @EnumSource(PieceColor.class) + void ChessEndState_PieceColor_GenerateInstance(final PieceColor pieceColor) { + final ChessEndState chessEndState = new EndState(pieceColor); + + assertThat(chessEndState).isInstanceOf(ChessEndState.class); + } + + @ParameterizedTest + @EnumSource(PieceColor.class) + void shiftNextTurnState_ChessEndState_ExceptionThrown(final PieceColor pieceColor) { + assertThatThrownBy(() -> new EndState(pieceColor).shiftNextTurnState()) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("지원하지 않는 기능입니다."); + } + + @ParameterizedTest + @EnumSource(PieceColor.class) + void shiftEndState_ChessEndState_ExceptionThrown(final PieceColor pieceColor) { + assertThatThrownBy(() -> new EndState(pieceColor).shiftEndState(true)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("지원하지 않는 기능입니다."); + } + + @Test + void isEndState_ChessEndState_ReturnFalse() { + assertThat(new EndState(PieceColor.BLACK).isEndState()).isTrue(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessTurnStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessTurnStateTest.java new file mode 100644 index 0000000000..18306ce6bb --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/ChessTurnStateTest.java @@ -0,0 +1,40 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ChessTurnStateTest { + + @Test + void ChessTurnState_PieceColor_GenerateChessTurnState() { + final ChessTurnState chessTurnState = new WhiteTurnState(); + + assertThat(chessTurnState).isInstanceOf(ChessTurnState.class); + } + + @Test + void shiftEndState_KingCaught_GenerateKingCaughtState() { + final ChessTurnState chessTurnState = new WhiteTurnState(); + + assertThat(chessTurnState.shiftEndState(true)).isInstanceOf(KingCaughtState.class); + } + + @Test + void shiftEndState_KingNotCaught_GenerateEndState() { + final ChessTurnState chessTurnState = new BlackTurnState(); + + assertThat(chessTurnState.shiftEndState(false)).isInstanceOf(EndState.class); + } + + @Test + void isEndState_ChessTurnState_ReturnFalse() { + assertThat(new WhiteTurnState().isEndState()).isFalse(); + } + + @Test + void isKingCaughtState_ChessTurnState_ReturnFalse() { + assertThat(new BlackTurnState().isKingCaughtState()).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/EndStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/EndStateTest.java new file mode 100644 index 0000000000..9e7b09fda8 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/EndStateTest.java @@ -0,0 +1,32 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class EndStateTest { + + @ParameterizedTest + @EnumSource(PieceColor.class) + void EndState_PieceColor_GenerateInstance(final PieceColor pieceColor) { + assertThat(new EndState(pieceColor)).isInstanceOf(EndState.class); + } + + @Test + void getPieceColor_PieceColor_ReturnPieceColor() { + final ChessEndState chessEndState = new EndState(PieceColor.BLACK); + + Assertions.assertThat(chessEndState.getPieceColor()).isEqualTo(PieceColor.BLACK); + } + + @Test + void isKingCaughtState_EndState_ReturnFalse() { + assertThat(new EndState(PieceColor.BLACK).isKingCaughtState()).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/KingCaughtStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/KingCaughtStateTest.java new file mode 100644 index 0000000000..2f07e17cc9 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/KingCaughtStateTest.java @@ -0,0 +1,31 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class KingCaughtStateTest { + + @ParameterizedTest + @EnumSource(PieceColor.class) + void KingCaughtState_PieceColor_GenerateInstance(PieceColor pieceColor) { + assertThat(new KingCaughtState(pieceColor)).isInstanceOf(KingCaughtState.class); + } + + @Test + void getPieceColor_PieceColor_ReturnPieceColor() { + final ChessEndState kingCaughtState = new KingCaughtState(PieceColor.BLACK); + + assertThat(kingCaughtState.getPieceColor()).isEqualTo(PieceColor.BLACK); + } + + @Test + void isKingCaughtState_KingCaughtState_ReturnTrue() { + assertThat(new KingCaughtState(PieceColor.BLACK).isKingCaughtState()).isTrue(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnStateTest.java b/src/test/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnStateTest.java new file mode 100644 index 0000000000..92bff9b461 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessGame/gameState/WhiteTurnStateTest.java @@ -0,0 +1,26 @@ +package wooteco.chess.domain.chessGame.gameState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class WhiteTurnStateTest { + + @Test + void WhiteTurnState_WhitePieceColor_GenerateInstance() { + assertThat(new WhiteTurnState()).isInstanceOf(WhiteTurnState.class); + } + + @Test + void shiftNextTurnState_WhitePieceColor_ReturnBlackTurnState() { + assertThat(new WhiteTurnState().shiftNextTurnState()).isInstanceOf(BlackTurnState.class); + } + + @Test + void getPieceColor_WhiteTurnState_ReturnWhitePieceColor() { + assertThat(new WhiteTurnState().getPieceColor()).isEqualTo(PieceColor.WHITE); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/ChessPieceTest.java b/src/test/java/wooteco/chess/domain/chessPiece/ChessPieceTest.java new file mode 100644 index 0000000000..392121626f --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/ChessPieceTest.java @@ -0,0 +1,168 @@ +package wooteco.chess.domain.chessPiece; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessPiece.pieceState.InitialState; +import wooteco.chess.domain.chessPiece.pieceState.MovedState; +import wooteco.chess.domain.chessPiece.pieceState.PieceState; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +class ChessPieceTest { + + @ParameterizedTest + @NullSource + void ChessPiece_NullPieceType_ExceptionThrown(final PieceType pieceType) { + assertThatThrownBy(() -> new ChessPiece(pieceType, new InitialState())) + .isInstanceOf(NullPointerException.class) + .hasMessage("피스 타입이 null입니다."); + } + + @ParameterizedTest + @NullSource + void ChessPiece_NullPieceState_ExceptionThrown(final PieceState pieceState) { + assertThatThrownBy(() -> new ChessPiece(PieceType.BLACK_KING, pieceState)) + .isInstanceOf(NullPointerException.class) + .hasMessage("피스 상태가 null입니다."); + } + + @Test + void ChessPiece_PieceTypeAndPieceState_GenerateInstance() { + assertThat(new ChessPiece(PieceType.BLACK_KING, new InitialState())).isInstanceOf(ChessPiece.class); + } + + @Test + void ChessPiece_PieceType_GenerateInstance() { + assertThat(new ChessPiece(PieceType.BLACK_KING)).isInstanceOf(ChessPiece.class); + } + + @ParameterizedTest + @NullSource + void validate_NullSourcePosition_ExceptionThrown(Position sourcePosition) { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThatThrownBy(() -> chessPiece.canCatch(sourcePosition, Position.of("b1"))) + .isInstanceOf(NullPointerException.class) + .hasMessage("소스 위치가 null입니다."); + } + + @ParameterizedTest + @NullSource + void validate_NullTargetPosition_ExceptionThrown(Position targetPosition) { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThatThrownBy(() -> chessPiece.canCatch(Position.of("b1"), targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("타겟 위치가 null입니다."); + } + + @Test + void canMove_CanMovePieceType_ShiftNextState() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + chessPiece.canMove(Position.of("b2"), Position.of("b3")); + + assertThat(chessPiece).extracting("pieceState").isInstanceOf(MovedState.class); + } + + @Test + void canMove_CanMovePieceType_ReturnTrue() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece.canMove(Position.of("b2"), Position.of("b3"))).isTrue(); + } + + @Test + void canMove_CanNotMovePieceType_InitialState() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + chessPiece.canMove(Position.of("b2"), Position.of("b4")); + + assertThat(chessPiece).extracting("pieceState").isInstanceOf(InitialState.class); + } + + @Test + void canMove_CanNotMovePieceType_ReturnFalse() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece.canMove(Position.of("b2"), Position.of("b4"))).isFalse(); + } + + @Test + void canCatch_CanCatchPieceType_ShiftNextState() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + chessPiece.canCatch(Position.of("b2"), Position.of("b3")); + + assertThat(chessPiece).extracting("pieceState").isInstanceOf(MovedState.class); + } + + @Test + void canCatch_CanCatchPieceType_ReturnTrue() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece.canCatch(Position.of("b2"), Position.of("b3"))).isTrue(); + } + + @Test + void canCatch_CanNotCatchPieceType_InitialState() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + chessPiece.canCatch(Position.of("b2"), Position.of("b4")); + + assertThat(chessPiece).extracting("pieceState").isInstanceOf(InitialState.class); + } + + @Test + void canCatch_CanNotCatchPieceType_ReturnFalse() { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece.canCatch(Position.of("b2"), Position.of("b4"))).isFalse(); + } + + @ParameterizedTest + @NullSource + void isSamePieceColor_NullChessPiece_ExceptionThrown(final ChessPiece chessPiece2) { + final ChessPiece chessPiece1 = new ChessPiece(PieceType.BLACK_KING); + + assertThatThrownBy(() -> chessPiece1.isSamePieceColor(chessPiece2)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 피스가 null입니다."); + } + + @Test + void isSamePieceColor_SamePieceColor_ReturnTrue() { + final ChessPiece chessPiece1 = new ChessPiece(PieceType.BLACK_KING); + final ChessPiece chessPiece2 = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece1.isSamePieceColor(chessPiece2)).isTrue(); + } + + @Test + void isSamePieceColor_NotSamePieceColor_ReturnFalse() { + final ChessPiece chessPiece1 = new ChessPiece(PieceType.BLACK_KING); + final ChessPiece chessPiece2 = new ChessPiece(PieceType.WHITE_KING); + + assertThat(chessPiece1.isSamePieceColor(chessPiece2)).isFalse(); + } + + @ParameterizedTest + @NullSource + void isSame_PieceColor_ReturnResultOfComparePieceColor(final PieceColor pieceColor) { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThatThrownBy(() -> chessPiece.isSame(pieceColor)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 색상이 null입니다."); + } + + @ParameterizedTest + @CsvSource(value = {"BLACK,true", "WHITE,false"}) + void isSame_PieceColor_ReturnResultOfComparePieceColor(final PieceColor pieceColor, final boolean expected) { + final ChessPiece chessPiece = new ChessPiece(PieceType.BLACK_KING); + + assertThat(chessPiece.isSame(pieceColor)).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceState/InitialStateTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceState/InitialStateTest.java new file mode 100644 index 0000000000..fa02bb1d77 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceState/InitialStateTest.java @@ -0,0 +1,19 @@ +package wooteco.chess.domain.chessPiece.pieceState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class InitialStateTest { + + @Test + void shiftNextState_InitialState_ReturnMovedState() { + assertThat(new InitialState().shiftNextState()).isInstanceOf(MovedState.class); + } + + @Test + void getPawnMovableRange_ReturnInitialStatePawnMovableRange() { + assertThat(new InitialState().getPawnMovableRange()).isEqualTo(2); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceState/MovedStateTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceState/MovedStateTest.java new file mode 100644 index 0000000000..dac8a44fc5 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceState/MovedStateTest.java @@ -0,0 +1,19 @@ +package wooteco.chess.domain.chessPiece.pieceState; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class MovedStateTest { + + @Test + void shiftNextState_MovedState_ReturnMovedState() { + assertThat(new MovedState().shiftNextState()).isInstanceOf(MovedState.class); + } + + @Test + void getPawnMovableRange_ReturnMovedStatePawnMovableRange() { + assertThat(new MovedState().getPawnMovableRange()).isEqualTo(1); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategyTest.java new file mode 100644 index 0000000000..76c47011d3 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BishopStrategyTest.java @@ -0,0 +1,50 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.position.Position; + +class BishopStrategyTest { + + @Test + void BishopStrategy_GenerateInstance() { + assertThat(new BishopStrategy()).isInstanceOf(BishopStrategy.class); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "a7", "h8", "g1"}) + void canMove_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new BishopStrategy().canMove(sourcePosition, targetPosition, 1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d1", "d8", "h4", "a4"}) + void canMove_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new BishopStrategy().canMove(sourcePosition, targetPosition, 1)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "a7", "h8", "g1"}) + void canCatch_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new BishopStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d1", "d8", "h4", "a4"}) + void canCatch_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new BishopStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategyTest.java new file mode 100644 index 0000000000..15020fad31 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/BlackPawnStrategyTest.java @@ -0,0 +1,72 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.chessPiece.pieceState.InitialState; +import wooteco.chess.domain.chessPiece.pieceState.MovedState; +import wooteco.chess.domain.position.Position; + +class BlackPawnStrategyTest { + + @Test + void BlackPawn_GenerateInstance() { + assertThat(new BlackPawnStrategy()).isInstanceOf(BlackPawnStrategy.class); + } + + @ParameterizedTest + @CsvSource(value = {"c6", "c5"}) + void canMove_InitialStateAndMovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c7"); + final int pawnMovableRange = new InitialState().getPawnMovableRange(); + + assertThat(new BlackPawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"c6"}) + void canMove_MovedStateAndMovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c7"); + final int pawnMovableRange = new MovedState().getPawnMovableRange(); + + assertThat(new BlackPawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"b2", "d2"}) + void canMove_InitialStateAndNotMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c3"); + final int pawnMovableRange = new InitialState().getPawnMovableRange(); + + assertThat(new BlackPawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"c5"}) + void canMove_MovedStateAndNotMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c7"); + final int pawnMovableRange = new MovedState().getPawnMovableRange(); + + assertThat(new BlackPawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"b2", "d2"}) + void canCatch_CatchableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c3"); + + assertThat(new BlackPawnStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"c2", "c1"}) + void canCatch_NonCatchableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c3"); + + assertThat(new BlackPawnStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategyTest.java new file mode 100644 index 0000000000..76b1d1598c --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KingStrategyTest.java @@ -0,0 +1,55 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.position.Position; + +class KingStrategyTest { + + @Test + void KingStrategy_GenerateInstance() { + assertThat(new KingStrategy()).isInstanceOf(KingStrategy.class); + } + + @Test + void canLeap_King_ReturnTrue() { + assertThat(new KingStrategy().canLeap()).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"a4", "a3", "b3", "c3", "c4", "c5", "b5", "a5"}) + void canMove_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("b4"); + + assertThat(new KingStrategy().canMove(sourcePosition, targetPosition, 1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d6", "a2", "a6", "d2", "d4"}) + void canMove_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("b4"); + + assertThat(new KingStrategy().canMove(sourcePosition, targetPosition, 1)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"a4", "a3", "b3", "c3", "c4", "c5", "b5", "a5"}) + void canCatch_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("b4"); + + assertThat(new KingStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d6", "a2", "a6", "d2", "d4"}) + void canCatch_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("b4"); + + assertThat(new KingStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategyTest.java new file mode 100644 index 0000000000..b9c88018fb --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/KnightStrategyTest.java @@ -0,0 +1,55 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.position.Position; + +class KnightStrategyTest { + + @Test + void KnightStrategy_GenerateInstance() { + assertThat(new KnightStrategy()).isInstanceOf(KnightStrategy.class); + } + + @Test + void canLeap_Knight_ReturnTrue() { + assertThat(new KnightStrategy().canLeap()).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"b3", "b5", "c2", "e2", "f3", "f5", "c6", "e6"}) + void canMove_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new KnightStrategy().canMove(sourcePosition, targetPosition, 1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d5", "e4", "d3", "c4"}) + void canMove_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new KnightStrategy().canMove(sourcePosition, targetPosition, 1)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"b3", "b5", "c2", "e2", "f3", "f5", "c6", "e6"}) + void canCatch_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new KnightStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"d5", "e4", "d3", "c4"}) + void canCatch_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new KnightStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategyTest.java new file mode 100644 index 0000000000..cb9b2baf96 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/QueenStrategyTest.java @@ -0,0 +1,50 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.position.Position; + +class QueenStrategyTest { + + @Test + void QueenStrategy_GenerateInstance() { + assertThat(new QueenStrategy()).isInstanceOf(QueenStrategy.class); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "d1", "d8", "a7", "h8", "g1", "a4", "h4"}) + void canMove_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new QueenStrategy().canMove(sourcePosition, targetPosition, 1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"b3", "c2", "f3", "e6"}) + void canMove_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new QueenStrategy().canMove(sourcePosition, targetPosition, 1)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "d1", "d8", "a7", "h8", "g1", "a4", "h4"}) + void canCatch_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new QueenStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"b3", "c2", "f3", "e6"}) + void canCatch_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new QueenStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategyTest.java new file mode 100644 index 0000000000..c09cdf8632 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/RookStrategyTest.java @@ -0,0 +1,50 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.position.Position; + +class RookStrategyTest { + + @Test + void RookStrategy_GenerateInstance() { + assertThat(new RookStrategy()).isInstanceOf(RookStrategy.class); + } + + @ParameterizedTest + @CsvSource(value = {"d1", "d8", "a4", "h4"}) + void canMove_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new RookStrategy().canMove(sourcePosition, targetPosition, 1)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "a7", "g1", "g7"}) + void canMove_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new RookStrategy().canMove(sourcePosition, targetPosition, 1)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"d1", "d8", "a4", "h4"}) + void canCatch_MovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new RookStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"a1", "a7", "g1", "g7"}) + void canCatch_NonMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("d4"); + + assertThat(new RookStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategyTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategyTest.java new file mode 100644 index 0000000000..9c1e5a6cf9 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceStrategy/WhitePawnStrategyTest.java @@ -0,0 +1,72 @@ +package wooteco.chess.domain.chessPiece.pieceStrategy; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import wooteco.chess.domain.chessPiece.pieceState.InitialState; +import wooteco.chess.domain.chessPiece.pieceState.MovedState; +import wooteco.chess.domain.position.Position; + +class WhitePawnStrategyTest { + + @Test + void WhitePawn_GenerateInstance() { + assertThat(new WhitePawnStrategy()).isInstanceOf(WhitePawnStrategy.class); + } + + @ParameterizedTest + @CsvSource(value = {"c4", "c5"}) + void canMove_InitialStateAndMovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c3"); + final int pawnMovableRange = new InitialState().getPawnMovableRange(); + + assertThat(new WhitePawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"c8"}) + void canMove_MovedStateAndMovableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c7"); + final int pawnMovableRange = new MovedState().getPawnMovableRange(); + + assertThat(new WhitePawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"b2", "d2"}) + void canMove_InitialStateAndNotMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c1"); + final int pawnMovableRange = new InitialState().getPawnMovableRange(); + + assertThat(new WhitePawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"c5"}) + void canMove_MovedStateAndNotMovableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c3"); + final int pawnMovableRange = new MovedState().getPawnMovableRange(); + + assertThat(new WhitePawnStrategy().canMove(sourcePosition, targetPosition, pawnMovableRange)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"b2", "d2"}) + void canCatch_CatchableSourcePositionAndTargetPosition_ReturnTrue(final Position targetPosition) { + final Position sourcePosition = Position.of("c1"); + + assertThat(new WhitePawnStrategy().canCatch(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"c2", "c3"}) + void canCatch_NonCatchableSourcePositionAndTargetPosition_ReturnFalse(final Position targetPosition) { + final Position sourcePosition = Position.of("c1"); + + assertThat(new WhitePawnStrategy().canCatch(sourcePosition, targetPosition)).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceColorTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceColorTest.java new file mode 100644 index 0000000000..f2d9e7fdec --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceColorTest.java @@ -0,0 +1,45 @@ +package wooteco.chess.domain.chessPiece.pieceType; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class PieceColorTest { + + @ParameterizedTest + @ValueSource(strings = {"white", "black"}) + void of_InputPieceColor_ReturnInstance(String color) { + assertThat(PieceColor.of(color)).isInstanceOf(PieceColor.class); + } + + @ParameterizedTest + @CsvSource(value = {"BLACK,f,F", "WHITE,G,g"}) + void convertFrom_PieceName_ConvertedNameByPieceColor(PieceColor pieceColor, String pieceName, String expected) { + assertThat(pieceColor.convertFrom(pieceName)).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void convertFrom_PieceName_ConvertedNameByPieceColor(String pieceName) { + assertThatThrownBy(() -> PieceColor.WHITE.convertFrom(pieceName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 이름이 존재하지 않습니다."); + } + + @ParameterizedTest + @CsvSource(value = {"BLACK,true", "WHITE,false"}) + void isBlack_ReturnCompareResult(PieceColor pieceColor, boolean expected) { + assertThat(pieceColor.isBlack()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"BLACK,false", "WHITE,true"}) + void isWhite_ReturnCompareResult(PieceColor pieceColor, boolean expected) { + assertThat(pieceColor.isWhite()).isEqualTo(expected); + } + +} + diff --git a/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceTypeTest.java b/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceTypeTest.java new file mode 100644 index 0000000000..690fdb1d0a --- /dev/null +++ b/src/test/java/wooteco/chess/domain/chessPiece/pieceType/PieceTypeTest.java @@ -0,0 +1,55 @@ +package wooteco.chess.domain.chessPiece.pieceType; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class PieceTypeTest { + + @Test + void isSame_NotSamePieceColor_ReturnFalse() { + assertThat(PieceType.BLACK_BISHOP.isSame(PieceColor.WHITE)).isFalse(); + } + + @Test + void isSame_SamePieceColor_ReturnTrue() { + assertThat(PieceType.BLACK_BISHOP.isSame(PieceColor.BLACK)).isTrue(); + } + + @Test + void isSame_NotSamePieceType_ReturnFalse() { + assertThat(PieceType.BLACK_BISHOP.isSamePieceColor(PieceType.WHITE_BISHOP)).isFalse(); + } + + @Test + void isSame_SamePieceType_ReturnTrue() { + assertThat(PieceType.BLACK_BISHOP.isSamePieceColor(PieceType.BLACK_KING)).isTrue(); + } + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"BLACK_PAWN", "WHITE_PAWN"}, value = PieceType.class) + void isPawn_NotPawnPieceType_ReturnFalse(final PieceType pieceType) { + assertThat(pieceType.isPawn()).isFalse(); + } + + @ParameterizedTest + @EnumSource(names = {"BLACK_PAWN", "WHITE_PAWN"}, value = PieceType.class) + void isPawn_PawnPieceType_ReturnTrue(final PieceType pieceType) { + assertThat(pieceType.isPawn()).isTrue(); + } + + @ParameterizedTest + @EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"BLACK_KING", "WHITE_KING"}, value = PieceType.class) + void isKing_NotKingPieceType_ReturnFalse(final PieceType pieceType) { + assertThat(pieceType.isKing()).isFalse(); + } + + @ParameterizedTest + @EnumSource(names = {"BLACK_KING", "WHITE_KING"}, value = PieceType.class) + void isKing_KingPieceType_ReturnTrue(final PieceType pieceType) { + assertThat(pieceType.isKing()).isTrue(); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/position/ChessFileTest.java b/src/test/java/wooteco/chess/domain/position/ChessFileTest.java new file mode 100644 index 0000000000..16ec4c7982 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/position/ChessFileTest.java @@ -0,0 +1,79 @@ +package wooteco.chess.domain.position; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class ChessFileTest { + + @ParameterizedTest + @ValueSource(chars = {'a', 'h'}) + void from_CharacterChessFile_ReturnInstance(final char chessFile) { + assertThat(ChessFile.from(chessFile)).isInstanceOf(ChessFile.class); + } + + @ParameterizedTest + @ValueSource(chars = {'i', 'z'}) + void from_InvalidCharacterChessFile_ExceptionThrown(final char chessFile) { + assertThatThrownBy(() -> ChessFile.from(chessFile)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 파일이 존재하지 않습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"a", "h"}) + void from_StringChessFile_ReturnInstance(final String chessFile) { + assertThat(ChessFile.from(chessFile)).isInstanceOf(ChessFile.class); + } + + @ParameterizedTest + @ValueSource(strings = {"i", "z"}) + void from_InvalidStringChessFile_ExceptionThrown(final String chessFile) { + assertThatThrownBy(() -> ChessFile.from(chessFile)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 파일이 존재하지 않습니다."); + } + + @ParameterizedTest + @ValueSource(ints = {1, 8}) + void from_IntegerFileValue_ReturnInstance(final int fileValue) { + assertThat(ChessFile.from(fileValue)).isInstanceOf(ChessFile.class); + } + + @ParameterizedTest + @ValueSource(ints = {0, 9}) + void from_InvalidIntegerFileValue_ExceptionThrown(final int fileValue) { + assertThatThrownBy(() -> ChessFile.from(fileValue)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 파일이 존재하지 않습니다."); + } + + @ParameterizedTest + @CsvSource(value = {"a,1,b", "c,4,g"}) + void move_MovingFileValue_ReturnMovedChessFile(final String chessFile, final int movingFileValue, + final String expected) { + assertThat(ChessFile.from(chessFile).move(movingFileValue)).isEqualTo(ChessFile.from(expected)); + } + + @ParameterizedTest + @CsvSource(value = {"a,b,1", "h,c,-5"}) + void gapTo_TargetChessFile_CalculateGapFromTargetChessFile(final String sourceChessFile, + final String targetChessFile, + final int expected) { + assertThat(ChessFile.from(sourceChessFile).gapTo(ChessFile.from(targetChessFile))).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void gapTo_NullChessFile_ExceptionThrown(final ChessFile targetChessFile) { + final ChessFile sourceChessFile = ChessFile.E; + + assertThatThrownBy(() -> sourceChessFile.gapTo(targetChessFile)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 파일이 null입니다."); + } + +} diff --git a/src/test/java/wooteco/chess/domain/position/ChessRankTest.java b/src/test/java/wooteco/chess/domain/position/ChessRankTest.java new file mode 100644 index 0000000000..ef8159d356 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/position/ChessRankTest.java @@ -0,0 +1,63 @@ +package wooteco.chess.domain.position; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class ChessRankTest { + + @ParameterizedTest + @ValueSource(ints = {1, 8}) + void from_IntegerChessRank_ReturnInstance(final int chessRank) { + assertThat(ChessRank.from(chessRank)).isInstanceOf(ChessRank.class); + } + + @ParameterizedTest + @ValueSource(ints = {0, 9}) + void from_InvalidIntegerChessRank_ExceptionThrown(final int chessRank) { + assertThatThrownBy(() -> ChessRank.from(chessRank)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 랭크가 존재하지 않습니다."); + } + + @ParameterizedTest + @ValueSource(chars = {'1', '8'}) + void from_CharacterChessRank_ReturnInstance(final char chessRank) { + assertThat(ChessRank.from(chessRank)).isInstanceOf(ChessRank.class); + } + + @ParameterizedTest + @ValueSource(chars = {'0', '9'}) + void from_InvalidCharacterChessRank_ExceptionThrown(final char chessRank) { + assertThatThrownBy(() -> ChessRank.from(chessRank)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 랭크가 존재하지 않습니다."); + } + + @ParameterizedTest + @CsvSource(value = {"1,1,2", "3,4,7"}) + void move_MovingRankValue_ReturnMovedChessRank(final int chessRank, final int movingRankValue, final int expected) { + assertThat(ChessRank.from(chessRank).move(movingRankValue)).isEqualTo(ChessRank.from(expected)); + } + + @ParameterizedTest + @CsvSource(value = {"1,2,1", "8,3,-5"}) + void gapTo_TargetChessRank_CalculateGapFromTargetChessRank(final int sourceChessRank, final int targetChessRank, + final int expected) { + assertThat(ChessRank.from(sourceChessRank).gapTo(ChessRank.from(targetChessRank))).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void gapTo_NullChessRank_ExceptionThrown(final ChessRank targetChessRank) { + final ChessRank sourceChessRank = ChessRank.FOUR; + + assertThatThrownBy(() -> sourceChessRank.gapTo(targetChessRank)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 랭크가 null입니다."); + } + +} diff --git a/src/test/java/wooteco/chess/domain/position/MoveDirectionTest.java b/src/test/java/wooteco/chess/domain/position/MoveDirectionTest.java new file mode 100644 index 0000000000..c75ec897c6 --- /dev/null +++ b/src/test/java/wooteco/chess/domain/position/MoveDirectionTest.java @@ -0,0 +1,32 @@ +package wooteco.chess.domain.position; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class MoveDirectionTest { + + @ParameterizedTest + @CsvSource(value = {"N,d5,d6", "NE,d5,e6", "E,d5,e5", "SE,d5,e4", "S,d5,d4", "SW,d5,c4", "W,d5,c5", "NW,d5,c6"}) + void isSameDirection_SourcePositionAndTargetPosition_ReturnTrue(final MoveDirection moveDirection, + final Position sourcePosition, final Position targetPosition) { + assertThat(moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)).isTrue(); + } + + @ParameterizedTest + @CsvSource(value = {"N,d5,e8", "NE,d5,a6", "E,c6,e5", "SE,e5,e4", "S,h5,d4", "SW,d5,c5", "W,f5,c8", "NW,d8,c6"}) + void isSameDirection_NotExistDirection_ReturnFalse(final MoveDirection moveDirection, final Position sourcePosition, + final Position targetPosition) { + assertThat(moveDirection.isSameDirectionFrom(sourcePosition, targetPosition)).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"N,d5", "NE,e5", "E,e4", "SE,e3", "S,d3", "SW,c3", "W,c4", "NW,c5"}) + void move_SourcePosition_MovedPosition(final MoveDirection moveDirection, final Position expected) { + final Position position = Position.of("d4"); + + assertThat(moveDirection.move(position)).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/domain/position/PositionTest.java b/src/test/java/wooteco/chess/domain/position/PositionTest.java new file mode 100644 index 0000000000..bac48c1ebc --- /dev/null +++ b/src/test/java/wooteco/chess/domain/position/PositionTest.java @@ -0,0 +1,125 @@ +package wooteco.chess.domain.position; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class PositionTest { + + @ParameterizedTest + @CsvSource(value = {"a,2", "d,8", "h,5"}) + void of_ChessFileAndChessRank_GenerateInstance(final char chessFile, final int chessRank) { + assertThat(Position.of(ChessFile.from(chessFile), ChessRank.from(chessRank))).isInstanceOf(Position.class); + } + + @ParameterizedTest + @NullSource + void of_NullChessFile_ExceptionThrown(final ChessFile chessFile) { + final ChessRank chessRank = ChessRank.from(4); + + assertThatThrownBy(() -> Position.of(chessFile, chessRank)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 파일이 null입니다."); + } + + @ParameterizedTest + @NullSource + void of_NullChessRank_ExceptionThrown(final ChessRank chessRank) { + final ChessFile chessFile = ChessFile.from('h'); + + assertThatThrownBy(() -> Position.of(chessFile, chessRank)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 랭크가 null입니다."); + } + + @ParameterizedTest + @CsvSource(value = {"a2", "d8", "h5"}) + void of_KeyOfPosition_GenerateInstance(final String key) { + assertThat(Position.of(key)).isInstanceOf(Position.class); + } + + @ParameterizedTest + @NullAndEmptySource + void validateEmpty_InvalidKey_ExceptionThrown(final String key) { + assertThatThrownBy(() -> Position.of(key)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효한 위치 입력이 아닙니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"a12", "hi9", "b"}) + void validateLength_InvalidLengthKey_ExceptionThrown(final String key) { + assertThatThrownBy(() -> Position.of(key)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효한 위치 입력이 아닙니다."); + } + + @ParameterizedTest + @CsvSource(value = {"0,1,b2,b3", "2,4,b3,d7"}) + void move_MovingFileValueAndRankValue_ReturnMovedPosition(final int movingFileValue, final int movingRankValue, + final Position position, final Position expected) { + assertThat(position.move(movingFileValue, movingRankValue)).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"a2,c4,2", "h6,f8,-2"}) + void calculateChessFileGapTo_TargetPosition_ChessFileGapFromTargetPosition(final Position sourcePosition, + final Position targetPosition, final int expected) { + assertThat(sourcePosition.calculateChessFileGapTo(targetPosition)).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void calculateChessFileGapTo_NullTargetPosition_ExceptionThrown(final Position targetPosition) { + final Position sourcePosition = Position.of("b2"); + + assertThatThrownBy(() -> sourcePosition.calculateChessFileGapTo(targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("타겟 위치가 null입니다."); + } + + @ParameterizedTest + @CsvSource(value = {"a2,c4,2", "f4,f8,4"}) + void calculateRankGapTo_TargetPosition_ChessRankGapFromTargetPosition(final Position sourcePosition, + final Position targetPosition, int expected) { + assertThat(sourcePosition.calculateChessRankGapTo(targetPosition)).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void calculateChessRankGapTo_NullTargetPosition_ExceptionThrown(final Position targetPosition) { + final Position sourcePosition = Position.of("b2"); + + assertThatThrownBy(() -> sourcePosition.calculateChessRankGapTo(targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("타겟 위치가 null입니다."); + } + + @ParameterizedTest + @CsvSource(value = {"c,true", "d,false"}) + void isSame_CompareChessFile_ReturnCompareResult(final char inputChessFile, final boolean expected) { + final Position position = Position.of("c3"); + final ChessFile chessFile = ChessFile.from(inputChessFile); + + assertThat(position.isSame(chessFile)).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void isSame_NullChessFile_ExceptionThrown(final ChessFile chessFile) { + assertThatThrownBy(() -> Position.of("c3").isSame(chessFile)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 파일이 null입니다."); + } + + @Test + void key_ThisChesFileAndChessRank_ReturnKeyValue() { + assertThat(Position.of("b1").key()).isEqualTo("b1"); + } + +} diff --git a/src/test/java/wooteco/chess/entity/BaseEntityTest.java b/src/test/java/wooteco/chess/entity/BaseEntityTest.java new file mode 100644 index 0000000000..edf11db089 --- /dev/null +++ b/src/test/java/wooteco/chess/entity/BaseEntityTest.java @@ -0,0 +1,36 @@ +package wooteco.chess.entity; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +class BaseEntityTest { + + @ParameterizedTest + @NullSource + void BaseEntity_NullCreatedTime_ExceptionThrown(final LocalDateTime localDateTime) { + assertThatThrownBy(() -> ChessGameEntity.of(localDateTime)) + .isInstanceOf(NullPointerException.class) + .hasMessage("생성 시간이 null입니다."); + } + + @Test + void BaseEntity_CreatedTime_GenerateInstance() { + final BaseEntity baseEntity = ChessGameEntity.of(LocalDateTime.now()); + + assertThat(baseEntity).isInstanceOf(BaseEntity.class); + } + + @Test + void compareTo_CompareByCreatedTime_ReturnCompareToCreatedTime() { + final BaseEntity earlyChessGame = ChessGameEntity.of(LocalDateTime.MIN); + final BaseEntity lateChessGame = ChessGameEntity.of(LocalDateTime.MAX); + + assertThat(earlyChessGame.compareTo(lateChessGame)).isEqualTo(LocalDateTime.MIN.compareTo(LocalDateTime.MAX)); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java b/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java new file mode 100644 index 0000000000..b16bbce8cb --- /dev/null +++ b/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java @@ -0,0 +1,41 @@ +package wooteco.chess.entity; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +class ChessGameEntityTest { + + private static final long DEFAULT_GAME_ID = 0L; + + @Test + void of_GameIdAndCreatedTime_GenerateInstance() { + assertThat(ChessGameEntity.of(1, LocalDateTime.now())).isInstanceOf(ChessGameEntity.class); + } + + @ParameterizedTest + @NullSource + void of_NullChessGameEntity_ExceptionThrown(final ChessGameEntity entity) { + assertThatThrownBy(() -> ChessGameEntity.of(1, entity)) + .isInstanceOf(NullPointerException.class) + .hasMessage("엔티티가 null입니다."); + } + + @Test + void of_GameIdAndChessGameEntity_GenerateInstance() { + final ChessGameEntity entity = ChessGameEntity.of(1, LocalDateTime.now()); + + assertThat(ChessGameEntity.of(1, entity)).isInstanceOf(ChessGameEntity.class); + } + + @Test + void of_CreatedTime_GenerateInstance() { + assertThat(ChessGameEntity.of(LocalDateTime.now())).isInstanceOf(ChessGameEntity.class) + .extracting("gameId").isEqualTo(DEFAULT_GAME_ID); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java b/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java new file mode 100644 index 0000000000..b24112b68a --- /dev/null +++ b/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java @@ -0,0 +1,80 @@ +package wooteco.chess.entity; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.Arrays; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessGame.ChessCommand; + +class ChessHistoryEntityTest { + + private static final String MOVE_COMMAND = "move"; + + @ParameterizedTest + @NullSource + void validate_NullStart_ExceptionThrown(final String start) { + assertThatThrownBy(() -> ChessHistoryEntity.of(1, 1, start, "b2", LocalDateTime.now())) + .isInstanceOf(NullPointerException.class) + .hasMessage("출발 위치가 null입니다."); + } + + @ParameterizedTest + @NullSource + void validate_NullEnd_ExceptionThrown(final String end) { + assertThatThrownBy(() -> ChessHistoryEntity.of(1, 1, "b1", end, LocalDateTime.now())) + .isInstanceOf(NullPointerException.class) + .hasMessage("도착 위치가 null입니다."); + } + + @Test + void of_HistoryIdAndGameIdAndStartAndEndAndCreatedTime_GenerateInstance() { + assertThat(ChessHistoryEntity.of(1, 1, "b1", "b2", LocalDateTime.now())).isInstanceOf(ChessHistoryEntity.class); + } + + @Test + void of_GameIdAndStartAndEndAndCreatedTime_GenerateInstance() { + assertThat(ChessHistoryEntity.of(1, "b1", "b2", LocalDateTime.now())).isInstanceOf(ChessHistoryEntity.class); + } + + @Test + void of_StartAndEnd_GenerateInstance() { + assertThat(ChessHistoryEntity.of("b1", "b2")).isInstanceOf(ChessHistoryEntity.class); + } + + @ParameterizedTest + @NullSource + void of_NullChessHistoryEntity_ExceptionThrown(final ChessHistoryEntity entity) { + assertThatThrownBy(() -> ChessHistoryEntity.of(1, entity)) + .isInstanceOf(NullPointerException.class) + .hasMessage("엔티티가 null입니다."); + } + + @Test + void of_HistoryIdAndChessHistoryEntity_GenerateInstance() { + final ChessHistoryEntity entity = ChessHistoryEntity.of("b1", "b2"); + + assertThat(ChessHistoryEntity.of(1, entity)).isInstanceOf(ChessHistoryEntity.class); + } + + @Test + void of_ChessIdAndStartAndEnd_GenerateInstance() { + assertThat(ChessHistoryEntity.of("b1", "b2")).isInstanceOf(ChessHistoryEntity.class); + } + + @Test + void generateMoveCommand_MoveCommandFromStartToEnd_ReturnChessCommand() { + final String start = "b1"; + final String end = "b2"; + final ChessHistoryEntity chessHistoryEntity = ChessHistoryEntity.of(start, end); + + final ChessCommand expected = ChessCommand.of(Arrays.asList(MOVE_COMMAND, start, end)); + Assertions.assertThat(chessHistoryEntity.generateMoveCommand()).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/ChessServiceTest.java b/src/test/java/wooteco/chess/service/ChessServiceTest.java new file mode 100644 index 0000000000..443249f615 --- /dev/null +++ b/src/test/java/wooteco/chess/service/ChessServiceTest.java @@ -0,0 +1,142 @@ +package wooteco.chess.service; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.dao.ChessGameDao; +import wooteco.chess.dao.ChessHistoryDao; +import wooteco.chess.dao.InMemoryChessGameDao; +import wooteco.chess.dao.InMemoryChessHistoryDao; +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.ChessCommand; +import wooteco.chess.domain.chessGame.ChessGame; +import wooteco.chess.entity.ChessGameEntity; +import wooteco.chess.entity.ChessHistoryEntity; +import wooteco.chess.service.dto.ChessGameDto; + +class ChessServiceTest { + + private static final String MOVE_COMMAND = "move"; + + private ChessGameDao chessGameDao; + private ChessHistoryDao chessHistoryDao; + + @BeforeEach + void setUp() { + chessGameDao = new InMemoryChessGameDao(); + chessHistoryDao = new InMemoryChessHistoryDao(); + } + + @ParameterizedTest + @NullSource + void ChessService_NullChessGameDao_ExceptionThrown(final ChessGameDao chessGameDao) { + assertThatThrownBy(() -> new ChessService(chessGameDao, chessHistoryDao)) + .isInstanceOf(NullPointerException.class) + .hasMessage("ChessGameDao가 null입니다."); + } + + @ParameterizedTest + @NullSource + void ChessService_NullChessHistoryDao_ExceptionThrown(final ChessHistoryDao chessHistoryDao) { + assertThatThrownBy(() -> new ChessService(chessGameDao, chessHistoryDao)) + .isInstanceOf(NullPointerException.class) + .hasMessage("ChessHistoryDao가 null입니다."); + } + + @Test + void ChessService_ChessHistoryDao_GenerateInstance() { + assertThat(new ChessService(chessGameDao, chessHistoryDao)).isInstanceOf(ChessService.class); + } + + @Test + void checkChessGameIsEmpty_EmptyChessGame_AddChessGame() { + new ChessService(chessGameDao, chessHistoryDao); + + assertThat(chessGameDao.findMaxGameId()).isEqualTo(1); + } + + @Test + void checkChessGameIsEmpty_ExistChessGame_NotAddChessGame() { + chessGameDao.add(ChessGameEntity.of(LocalDateTime.now())); + chessGameDao.add(ChessGameEntity.of(LocalDateTime.now())); + new ChessService(chessGameDao, chessHistoryDao); + + assertThat(chessGameDao.findMaxGameId()).isEqualTo(2); + } + + @Test + void loadChessGame_RecentChessGame_ReturnChessGameDto() { + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + final long gameId = chessGameDao.findMaxGameId(); + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessHistoryDao.add(ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now())); + chessHistoryDao.add(ChessHistoryEntity.of(gameId, "b7", "b5", LocalDateTime.now())); + chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b2", "b4"))); + chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b7", "b5"))); + + final ChessGameDto expected = ChessGameDto.of(chessGame); + assertThat(chessService.loadChessGame()).isEqualTo(expected); + } + + @ParameterizedTest + @NullSource + void playChessGame_NullSourcePosition_ExceptionThrown(final String sourcePosition) { + final String targetPosition = "b2"; + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + + assertThatThrownBy(() -> chessService.playChessGame(sourcePosition, targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("소스 위치가 null입니다."); + } + + @ParameterizedTest + @NullSource + void playChessGame_NullTargetPosition_ExceptionThrown(final String targetPosition) { + final String sourcePosition = "b2"; + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + + assertThatThrownBy(() -> chessService.playChessGame(sourcePosition, targetPosition)) + .isInstanceOf(NullPointerException.class) + .hasMessage("타겟 위치가 null입니다."); + } + + @Test + void playChessGame_SourcePositionAndTargetPosition_ReturnChessGameDto() { + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b2", "b4"))); + + final ChessGameDto expected = ChessGameDto.of(chessGame); + assertThat(chessService.playChessGame("b2", "b4")).isEqualTo(expected); + } + + @Test + void createChessGame_RemoveAllChessHistory_ReturnChessGameDto() { + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + chessHistoryDao.add(ChessHistoryEntity.of("b2", "b4")); + chessHistoryDao.add(ChessHistoryEntity.of("b7", "b5")); + + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + final ChessGameDto expected = ChessGameDto.of(chessGame); + assertThat(chessService.createChessGame()).isEqualTo(expected); + } + + @Test + void endChessGame_EndRecentChessGame() { + final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); + final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); + chessGame.end(); + + final ChessGameDto expected = ChessGameDto.of(chessGame); + assertThat(chessService.endChessGame()).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java b/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java new file mode 100644 index 0000000000..723b67be7a --- /dev/null +++ b/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java @@ -0,0 +1,45 @@ +package wooteco.chess.service.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessPiece.ChessPiece; +import wooteco.chess.domain.chessPiece.pieceType.PieceType; +import wooteco.chess.domain.position.Position; + +class ChessBoardDtoTest { + + private static final String IMAGE_SOURCE_FORMAT = ""; + + @ParameterizedTest + @NullSource + void of_NullChessBoard_ExceptionThrown(final ChessBoard chessBoard) { + assertThatThrownBy(() -> ChessBoardDto.of(chessBoard)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 보드가 null입니다."); + } + + @Test + void of_ChessBoard_GenerateInstance() { + final Map initialChessBoard = new HashMap<>(); + final Position rookPosition = Position.of("b1"); + final Position blackPawnPosition = Position.of("b3"); + initialChessBoard.put(rookPosition, new ChessPiece(PieceType.BLACK_ROOK)); + initialChessBoard.put(blackPawnPosition, new ChessPiece(PieceType.BLACK_PAWN)); + final ChessBoard chessBoard = new ChessBoard(initialChessBoard); + + final Map expected = new HashMap<>(); + expected.put(rookPosition.key(), String.format(IMAGE_SOURCE_FORMAT, "black-rook")); + expected.put(blackPawnPosition.key(), String.format(IMAGE_SOURCE_FORMAT, "black-pawn")); + assertThat(ChessBoardDto.of(chessBoard)).isInstanceOf(ChessBoardDto.class) + .extracting("chessBoard").isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java b/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java new file mode 100644 index 0000000000..11a30ad6f2 --- /dev/null +++ b/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java @@ -0,0 +1,31 @@ +package wooteco.chess.service.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.ChessGame; + +class ChessGameDtoTest { + + @ParameterizedTest + @NullSource + void of_NullChessGame_ExceptionThrown(final ChessGame chessGame) { + assertThatThrownBy(() -> ChessGameDto.of(chessGame)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 게임이 null입니다."); + } + + @Test + void of_ChessGame_GenerateInstance() { + final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + final ChessGame chessGame = ChessGame.from(chessBoard); + + assertThat(ChessGameDto.of(chessGame)).isInstanceOf(ChessGameDto.class); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/dto/ChessStatusDtoTest.java b/src/test/java/wooteco/chess/service/dto/ChessStatusDtoTest.java new file mode 100644 index 0000000000..7b8633bfaa --- /dev/null +++ b/src/test/java/wooteco/chess/service/dto/ChessStatusDtoTest.java @@ -0,0 +1,34 @@ +package wooteco.chess.service.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class ChessStatusDtoTest { + + @ParameterizedTest + @NullSource + void of_NullPieceColor_ExceptionThrown(final PieceColor pieceColor) { + assertThatThrownBy(() -> ChessStatusDto.of(pieceColor, 20.)) + .isInstanceOf(NullPointerException.class) + .hasMessage("피스 색상이 null입니다."); + } + + @ParameterizedTest + @NullSource + void of_NullScore_ExceptionThrown(final Double score) { + assertThatThrownBy(() -> ChessStatusDto.of(PieceColor.BLACK, score)) + .isInstanceOf(NullPointerException.class) + .hasMessage("점수가 null입니다."); + } + + @Test + void of_PieceColorAndScore_GenerateInstance() { + assertThat(ChessStatusDto.of(PieceColor.BLACK, 20.)).isInstanceOf(ChessStatusDto.class); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/dto/ChessStatusDtosTest.java b/src/test/java/wooteco/chess/service/dto/ChessStatusDtosTest.java new file mode 100644 index 0000000000..6a06e565cb --- /dev/null +++ b/src/test/java/wooteco/chess/service/dto/ChessStatusDtosTest.java @@ -0,0 +1,39 @@ +package wooteco.chess.service.dto; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; +import wooteco.chess.domain.chessGame.ChessStatus; +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class ChessStatusDtosTest { + + @ParameterizedTest + @NullSource + void of_NullChessStatus_ExceptionThrown(final ChessStatus chessStatus) { + assertThatThrownBy(() -> ChessStatusDtos.of(chessStatus)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 상태가 null입니다."); + } + + @Test + void of_ChessStatus_GenerateInstance() { + final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + final ChessStatus chessStatus = chessBoard.calculateStatus(); + + final List expected = new ArrayList<>(); + expected.add(ChessStatusDto.of(PieceColor.WHITE, 38.0)); + expected.add(ChessStatusDto.of(PieceColor.BLACK, 38.0)); + assertThat(ChessStatusDtos.of(chessStatus)).isInstanceOf(ChessStatusDtos.class) + .extracting("chessStatusDtos").asList().containsExactlyInAnyOrderElementsOf(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/dto/PieceColorDtoTest.java b/src/test/java/wooteco/chess/service/dto/PieceColorDtoTest.java new file mode 100644 index 0000000000..c3e138f3a0 --- /dev/null +++ b/src/test/java/wooteco/chess/service/dto/PieceColorDtoTest.java @@ -0,0 +1,27 @@ +package wooteco.chess.service.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessPiece.pieceType.PieceColor; + +class PieceColorDtoTest { + + @ParameterizedTest + @NullSource + void of_NullPieceColor_ExceptionThrown(final PieceColor pieceColor) { + assertThatThrownBy(() -> PieceColorDto.of(pieceColor)) + .isInstanceOf(NullPointerException.class) + .hasMessage("피스 색상이 null입니다."); + } + + @Test + void of_PieceColor_GenerateInstance() { + assertThat(PieceColorDto.of(PieceColor.BLACK)).isInstanceOf(PieceColorDto.class) + .extracting("pieceColor").isEqualTo("black"); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java b/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java new file mode 100644 index 0000000000..92b1deacdb --- /dev/null +++ b/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java @@ -0,0 +1,43 @@ +package wooteco.chess.util; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +import wooteco.chess.domain.chessBoard.ChessBoard; +import wooteco.chess.domain.chessBoard.ChessBoardInitializer; + +class ChessBoardRendererTest { + + @ParameterizedTest + @NullSource + void render_NullChessBoard_ExceptionThrown(final ChessBoard chessBoard) { + assertThatThrownBy(() -> ChessBoardRenderer.render(chessBoard)) + .isInstanceOf(NullPointerException.class) + .hasMessage("체스 보드가 null입니다."); + } + + @Test + void render_ChessBoard_ReturnRenderedChessBoardByStringList() { + final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); + + final List expected = Arrays.asList( + "R N B Q K B N R 8", + "P P P P P P P P 7", + ". . . . . . . . 6", + ". . . . . . . . 5", + ". . . . . . . . 4", + ". . . . . . . . 3", + "p p p p p p p p 2", + "r n b q k b n r 1", + "", + "a b c d e f g h"); + assertThat(ChessBoardRenderer.render(chessBoard)).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/util/StringUtilTest.java b/src/test/java/wooteco/chess/util/StringUtilTest.java new file mode 100644 index 0000000000..d959bf3b98 --- /dev/null +++ b/src/test/java/wooteco/chess/util/StringUtilTest.java @@ -0,0 +1,30 @@ +package wooteco.chess.util; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; + +class StringUtilTest { + + @ParameterizedTest + @NullSource + void splitChessCommand_NullChessCommand_ExceptionThrown(final String chessCommand) { + assertThatThrownBy(() -> StringUtil.splitChessCommand(chessCommand)) + .isInstanceOf(NullPointerException.class) + .hasMessage("분리할 명령어가 null입니다."); + } + + @Test + void splitChessCommand_ChessCommand_ReturnListOfSpiltChessCommand() { + final String chessCommand = "move b1 b2"; + + final List expected = Arrays.asList("move", "b1", "b2"); + assertThat(StringUtil.splitChessCommand(chessCommand)).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/web/PieceNameConverterTest.java b/src/test/java/wooteco/chess/web/PieceNameConverterTest.java new file mode 100644 index 0000000000..6f2d1edb15 --- /dev/null +++ b/src/test/java/wooteco/chess/web/PieceNameConverterTest.java @@ -0,0 +1,21 @@ +package wooteco.chess.web; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class PieceNameConverterTest { + + @Test + void of_InvalidChessPieceName_ExceptionThrown() { + assertThatThrownBy(() -> PieceNameConverter.of("M")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("체스 피스가 유효하지 않습니다."); + } + + @Test + void of_ChessPieceName_ReturnInstance() { + assertThat(PieceNameConverter.of("P")).isInstanceOf(PieceNameConverter.class); + } + +} \ No newline at end of file From 4834662a01148e7d33838959e2f27ea88d320140 Mon Sep 17 00:00:00 2001 From: Junyoung Lee Date: Mon, 27 Apr 2020 16:35:22 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[=EC=8A=A4=ED=8B=B0=EC=B9=98]=20=EC=B2=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=8A=A4=ED=94=84=EB=A7=81=20=EC=8B=A4=EC=8A=B5=20?= =?UTF-8?q?2=EB=8B=A8=EA=B3=84=20=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C?= =?UTF-8?q?=EC=9E=85=EB=8B=88=EB=8B=A4=20(#87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/wooteco/chess/database/DataSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/wooteco/chess/database/DataSource.java b/src/main/java/wooteco/chess/database/DataSource.java index a2779f2a1a..a7fc4afb9d 100644 --- a/src/main/java/wooteco/chess/database/DataSource.java +++ b/src/main/java/wooteco/chess/database/DataSource.java @@ -2,7 +2,6 @@ import java.sql.Connection; -// TODO: 2020/04/25 명명 수정하기 -> DataSource public interface DataSource { Connection getConnection(); From 0ee87df924667b9a52377bad079eb1afd72dcdd1 Mon Sep 17 00:00:00 2001 From: lxxjn0 Date: Wed, 29 Apr 2020 13:08:22 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat=20:=20Spring=20Data=20JDBC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Spring Data JDBC 적용을 위한 GameRoomRepository 구현 - schema.sql 구현 --- .../wooteco/chess/SparkChessApplication.java | 36 ----- .../controller/SparkChessController.java | 86 ----------- .../java/wooteco/chess/dao/ChessGameDao.java | 15 -- .../wooteco/chess/dao/ChessHistoryDao.java | 15 -- .../wooteco/chess/dao/MySqlChessGameDao.java | 62 -------- .../chess/dao/MySqlChessHistoryDao.java | 66 --------- .../wooteco/chess/database/DataSource.java | 9 -- .../chess/database/GameRoomRepository.java | 19 +++ .../wooteco/chess/database/JdbcTemplate.java | 66 --------- .../chess/database/MySqlDataSource.java | 46 ------ .../database/PreparedStatementSetter.java | 10 -- .../wooteco/chess/database/RowMapper.java | 10 -- src/main/resources/application.properties | 12 +- src/main/resources/schema.sql | 15 ++ .../chess/dao/InMemoryChessGameDao.java | 45 ------ .../chess/dao/InMemoryChessHistoryDao.java | 43 ------ .../chess/dao/MySqlChessGameDaoTest.java | 133 ------------------ .../chess/dao/MySqlChessHistoryDaoTest.java | 131 ----------------- .../database/GameRoomRepositoryTest.java | 44 ++++++ src/test/resources/application.properties | 11 ++ 20 files changed, 100 insertions(+), 774 deletions(-) delete mode 100644 src/main/java/wooteco/chess/SparkChessApplication.java delete mode 100644 src/main/java/wooteco/chess/controller/SparkChessController.java delete mode 100644 src/main/java/wooteco/chess/dao/ChessGameDao.java delete mode 100644 src/main/java/wooteco/chess/dao/ChessHistoryDao.java delete mode 100644 src/main/java/wooteco/chess/dao/MySqlChessGameDao.java delete mode 100644 src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java delete mode 100644 src/main/java/wooteco/chess/database/DataSource.java create mode 100644 src/main/java/wooteco/chess/database/GameRoomRepository.java delete mode 100644 src/main/java/wooteco/chess/database/JdbcTemplate.java delete mode 100644 src/main/java/wooteco/chess/database/MySqlDataSource.java delete mode 100644 src/main/java/wooteco/chess/database/PreparedStatementSetter.java delete mode 100644 src/main/java/wooteco/chess/database/RowMapper.java create mode 100644 src/main/resources/schema.sql delete mode 100644 src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java delete mode 100644 src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java delete mode 100644 src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java delete mode 100644 src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java create mode 100644 src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java create mode 100644 src/test/resources/application.properties diff --git a/src/main/java/wooteco/chess/SparkChessApplication.java b/src/main/java/wooteco/chess/SparkChessApplication.java deleted file mode 100644 index ca1dd2c26b..0000000000 --- a/src/main/java/wooteco/chess/SparkChessApplication.java +++ /dev/null @@ -1,36 +0,0 @@ -package wooteco.chess; - -import static spark.Spark.*; - -import wooteco.chess.controller.SparkChessController; -import wooteco.chess.dao.ChessGameDao; -import wooteco.chess.dao.ChessHistoryDao; -import wooteco.chess.dao.MySqlChessGameDao; -import wooteco.chess.dao.MySqlChessHistoryDao; -import wooteco.chess.database.DataSource; -import wooteco.chess.database.JdbcTemplate; -import wooteco.chess.database.MySqlDataSource; -import wooteco.chess.service.ChessService; - -public class SparkChessApplication { - - public static void main(String[] args) { - port(8080); - staticFileLocation("/public"); - - SparkChessController sparkChessController = initWebController(); - sparkChessController.run(); - } - - private static SparkChessController initWebController() { - DataSource dataSource = MySqlDataSource.getInstance(); - JdbcTemplate template = new JdbcTemplate(dataSource); - - ChessGameDao chessGameDao = new MySqlChessGameDao(template); - ChessHistoryDao chessHistoryDao = new MySqlChessHistoryDao(template); - ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - - return new SparkChessController(chessService); - } - -} diff --git a/src/main/java/wooteco/chess/controller/SparkChessController.java b/src/main/java/wooteco/chess/controller/SparkChessController.java deleted file mode 100644 index 4923a1b31d..0000000000 --- a/src/main/java/wooteco/chess/controller/SparkChessController.java +++ /dev/null @@ -1,86 +0,0 @@ -package wooteco.chess.controller; - -import static spark.Spark.*; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import spark.ModelAndView; -import spark.Request; -import spark.Response; -import spark.template.handlebars.HandlebarsTemplateEngine; -import wooteco.chess.service.ChessService; -import wooteco.chess.service.dto.ChessBoardDto; -import wooteco.chess.service.dto.ChessGameDto; -import wooteco.chess.service.dto.ChessStatusDtos; - -public class SparkChessController { - - private static final HandlebarsTemplateEngine HANDLEBARS_TEMPLATE_ENGINE = new HandlebarsTemplateEngine(); - - private final ChessService chessService; - - public SparkChessController(final ChessService chessService) { - Objects.requireNonNull(chessService, "체스 서비스가 null입니다."); - this.chessService = chessService; - } - - public void run() { - get("/", this::renderStartPage); - get("/chess", (request, response) -> renderGame(chessService.loadChessGame())); - - post("/chess_play", this::playChessGame); - post("/chess_new", this::newChessGame); - post("/chess_end", this::endChessGame); - } - - private String renderStartPage(final Request request, final Response response) { - return render(new HashMap<>(), "index.hbs"); - } - - private String playChessGame(final Request request, final Response response) { - final String sourcePosition = request.queryParams("sourcePosition").trim(); - final String targetPosition = request.queryParams("targetPosition").trim(); - final ChessGameDto chessGameDto = chessService.playChessGame(sourcePosition, targetPosition); - - if (chessGameDto.isEndState()) { - return renderResult(chessGameDto); - } - return renderGame(chessGameDto); - } - - private String newChessGame(final Request request, final Response response) { - return renderGame(chessService.createChessGame()); - } - - private String endChessGame(final Request request, final Response response) { - return renderResult(chessService.endChessGame()); - } - - private String renderGame(final ChessGameDto chessGameDto) { - final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); - final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); - - final Map model = new HashMap<>(chessBoardDto.getChessBoard()); - model.put("piece_color", chessGameDto.getPieceColorDto()); - model.put("status", chessStatusDtos.getChessStatusDtos()); - return render(model, "chess.hbs"); - } - - private String renderResult(final ChessGameDto chessGameDto) { - final ChessBoardDto chessBoardDto = chessGameDto.getChessBoardDto(); - final ChessStatusDtos chessStatusDtos = chessGameDto.getChessStatusDtos(); - - final Map model = new HashMap<>(chessBoardDto.getChessBoard()); - model.put("is_king_caught", chessGameDto.isKingCaught()); - model.put("piece_color", chessGameDto.getPieceColorDto()); - model.put("status", chessStatusDtos.getChessStatusDtos()); - return render(model, "result.hbs"); - } - - private static String render(Map model, String templatePath) { - return HANDLEBARS_TEMPLATE_ENGINE.render(new ModelAndView(model, templatePath)); - } - -} diff --git a/src/main/java/wooteco/chess/dao/ChessGameDao.java b/src/main/java/wooteco/chess/dao/ChessGameDao.java deleted file mode 100644 index 402b425a76..0000000000 --- a/src/main/java/wooteco/chess/dao/ChessGameDao.java +++ /dev/null @@ -1,15 +0,0 @@ -package wooteco.chess.dao; - -import wooteco.chess.entity.ChessGameEntity; - -public interface ChessGameDao { - - long add(final ChessGameEntity entity); - - long findMaxGameId(); - - boolean isEmpty(); - - void deleteAll(); - -} diff --git a/src/main/java/wooteco/chess/dao/ChessHistoryDao.java b/src/main/java/wooteco/chess/dao/ChessHistoryDao.java deleted file mode 100644 index 9fe17d0689..0000000000 --- a/src/main/java/wooteco/chess/dao/ChessHistoryDao.java +++ /dev/null @@ -1,15 +0,0 @@ -package wooteco.chess.dao; - -import java.util.List; - -import wooteco.chess.entity.ChessHistoryEntity; - -public interface ChessHistoryDao { - - List findAllByGameId(final long gameId); - - void add(final ChessHistoryEntity entity); - - void deleteAll(); - -} diff --git a/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java b/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java deleted file mode 100644 index ecdb88ee4b..0000000000 --- a/src/main/java/wooteco/chess/dao/MySqlChessGameDao.java +++ /dev/null @@ -1,62 +0,0 @@ -package wooteco.chess.dao; - -import java.sql.ResultSet; -import java.sql.Timestamp; -import java.util.Objects; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Repository; - -import wooteco.chess.database.JdbcTemplate; -import wooteco.chess.entity.ChessGameEntity; - -@Repository -public class MySqlChessGameDao implements ChessGameDao { - - public static final long EMPTY_CHESS_GAME = 0L; - private static final String CHESS_GAME_TABLE = "chess_games"; - - private final JdbcTemplate jdbcTemplate; - - public MySqlChessGameDao(@Qualifier("CustomJdbcTemplate") final JdbcTemplate jdbcTemplate) { - Objects.requireNonNull(jdbcTemplate, "JdbcTemplate이 null입니다."); - this.jdbcTemplate = jdbcTemplate; - } - - @Override - public long add(final ChessGameEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - final String query = "INSERT INTO " + CHESS_GAME_TABLE + " (created_time) VALUES (?)"; - - return jdbcTemplate.executeUpdate(query, preparedStatement -> { - preparedStatement.setTimestamp(1, Timestamp.valueOf(entity.getCreatedTime())); - }); - } - - @Override - public long findMaxGameId() { - final String query = "SELECT MAX(game_id) AS max_id FROM " + CHESS_GAME_TABLE; - - return jdbcTemplate.executeQuery(query, resultSet -> { - if (resultSet.next()) { - return resultSet.getLong("max_id"); - } - return EMPTY_CHESS_GAME; - }); - } - - @Override - public boolean isEmpty() { - final String query = "SELECT * FROM " + CHESS_GAME_TABLE; - - return !jdbcTemplate.executeQuery(query, ResultSet::next); - } - - @Override - public void deleteAll() { - final String query = "DELETE FROM " + CHESS_GAME_TABLE; - - jdbcTemplate.executeUpdate(query); - } - -} diff --git a/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java b/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java deleted file mode 100644 index 356edf85f3..0000000000 --- a/src/main/java/wooteco/chess/dao/MySqlChessHistoryDao.java +++ /dev/null @@ -1,66 +0,0 @@ -package wooteco.chess.dao; - -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Repository; - -import wooteco.chess.database.JdbcTemplate; -import wooteco.chess.entity.ChessHistoryEntity; - -@Repository -public class MySqlChessHistoryDao implements ChessHistoryDao { - - private static final String CHESS_HISTORY_TABLE = "chess_histories"; - - private final JdbcTemplate jdbcTemplate; - - public MySqlChessHistoryDao(@Qualifier("CustomJdbcTemplate") final JdbcTemplate jdbcTemplate) { - Objects.requireNonNull(jdbcTemplate, "JdbcTemplate이 null입니다."); - this.jdbcTemplate = jdbcTemplate; - } - - @Override - public List findAllByGameId(final long gameId) { - final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE + " WHERE game_id = ?"; - - return jdbcTemplate.executeQuery(query, resultSet -> { - final List entities = new ArrayList<>(); - - while (resultSet.next()) { - entities.add(ChessHistoryEntity.of( - resultSet.getLong("history_id"), - resultSet.getLong("game_id"), - resultSet.getString("start"), - resultSet.getString("end"), - resultSet.getTimestamp("created_time").toLocalDateTime())); - } - return entities; - }, preparedStatement -> preparedStatement.setLong(1, gameId)); - } - - @Override - public void add(final ChessHistoryEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - final String query = - "INSERT INTO " + CHESS_HISTORY_TABLE + " (game_id, start, end, created_time) VALUES (?, ?, ?, ?)"; - - jdbcTemplate.executeUpdate(query, preparedStatement -> { - preparedStatement.setLong(1, entity.getGameId()); - preparedStatement.setString(2, entity.getStart()); - preparedStatement.setString(3, entity.getEnd()); - preparedStatement.setTimestamp(4, Timestamp.valueOf(entity.getCreatedTime())); - }); - } - - @Override - public void deleteAll() { - final String query = "DELETE FROM " + CHESS_HISTORY_TABLE; - - jdbcTemplate.executeUpdate(query); - } - -} diff --git a/src/main/java/wooteco/chess/database/DataSource.java b/src/main/java/wooteco/chess/database/DataSource.java deleted file mode 100644 index a7fc4afb9d..0000000000 --- a/src/main/java/wooteco/chess/database/DataSource.java +++ /dev/null @@ -1,9 +0,0 @@ -package wooteco.chess.database; - -import java.sql.Connection; - -public interface DataSource { - - Connection getConnection(); - -} diff --git a/src/main/java/wooteco/chess/database/GameRoomRepository.java b/src/main/java/wooteco/chess/database/GameRoomRepository.java new file mode 100644 index 0000000000..2a4ad4ef7f --- /dev/null +++ b/src/main/java/wooteco/chess/database/GameRoomRepository.java @@ -0,0 +1,19 @@ +package wooteco.chess.database; + +import java.util.List; + +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +import wooteco.chess.entity.GameRoom; + +public interface GameRoomRepository extends CrudRepository { + + @Override + List findAll(); + + @Query("SELECT * FROM game_room WHERE name = :name") + GameRoom findByName(@Param("name") final String name); + +} \ No newline at end of file diff --git a/src/main/java/wooteco/chess/database/JdbcTemplate.java b/src/main/java/wooteco/chess/database/JdbcTemplate.java deleted file mode 100644 index 9abfb0c2aa..0000000000 --- a/src/main/java/wooteco/chess/database/JdbcTemplate.java +++ /dev/null @@ -1,66 +0,0 @@ -package wooteco.chess.database; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -@Component("CustomJdbcTemplate") -public class JdbcTemplate { - - private final DataSource dataSource; - - public JdbcTemplate(@Qualifier("CustomDataSource") final DataSource dataSource) { - this.dataSource = dataSource; - } - - public T executeQuery(String query, RowMapper rowMapper, PreparedStatementSetter preparedStatementSetter) { - try (Connection connection = dataSource.getConnection(); - PreparedStatement preparedStatement = connection.prepareStatement(query) - ) { - preparedStatementSetter.setArgument(preparedStatement); - return rowMapper.mapRow(preparedStatement.executeQuery()); - } catch (SQLException exception) { - System.err.println(exception.getMessage()); - } - return null; - } - - public T executeQuery(String query, RowMapper rowMapper) { - return executeQuery(query, rowMapper, preparedStatement -> { - }); - } - - public Long executeUpdate(String query, PreparedStatementSetter preparedStatementSetter) { - try (Connection connection = dataSource.getConnection(); - PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS) - ) { - preparedStatementSetter.setArgument(preparedStatement); - preparedStatement.executeUpdate(); - return generatedKey(preparedStatement); - } catch (SQLException e) { - throw new IllegalArgumentException(e.getMessage()); - } - } - - private Long generatedKey(final PreparedStatement preparedStatement) { - try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) { - if (resultSet.next()) { - return resultSet.getLong(1); - } - return null; - } catch (SQLException e) { - throw new IllegalArgumentException(e.getMessage()); - } - } - - public Long executeUpdate(String query) { - return executeUpdate(query, preparedStatement -> { - }); - } - -} diff --git a/src/main/java/wooteco/chess/database/MySqlDataSource.java b/src/main/java/wooteco/chess/database/MySqlDataSource.java deleted file mode 100644 index 5199bccde1..0000000000 --- a/src/main/java/wooteco/chess/database/MySqlDataSource.java +++ /dev/null @@ -1,46 +0,0 @@ -package wooteco.chess.database; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -import org.springframework.stereotype.Component; - -@Component("CustomDataSource") -public class MySqlDataSource implements DataSource { - - private static final String server = "127.0.0.1:13306"; // MySQL 서버 주소 - private static final String database = "woowa_level_01_chess"; // MySQL DATABASE 이름 - private static final String option = "?useSSL=false&serverTimezone=UTC"; - private static final String userName = "root"; // MySQL 서버 아이디 - private static final String password = "root"; // MySQL 서버 비밀번호 - private static final String CONNECTION_FORMAT = "jdbc:mysql://%s/%s%s"; - - private MySqlDataSource() { - } - - public static MySqlDataSource getInstance() { - return LazyHolder.INSTANCE; - } - - @Override - public Connection getConnection() { - try { - Class.forName("com.mysql.cj.jdbc.Driver"); - return DriverManager.getConnection(String.format(CONNECTION_FORMAT, server, database, option), userName, - password); - } catch (ClassNotFoundException e) { - System.err.println("JDBC Driver load 오류: " + e.getMessage()); - e.printStackTrace(); - } catch (SQLException e) { - System.err.println("연결 오류:" + e.getMessage()); - e.printStackTrace(); - } - return null; - } - - private static class LazyHolder { - private static final MySqlDataSource INSTANCE = new MySqlDataSource(); - } - -} diff --git a/src/main/java/wooteco/chess/database/PreparedStatementSetter.java b/src/main/java/wooteco/chess/database/PreparedStatementSetter.java deleted file mode 100644 index 86042a15bb..0000000000 --- a/src/main/java/wooteco/chess/database/PreparedStatementSetter.java +++ /dev/null @@ -1,10 +0,0 @@ -package wooteco.chess.database; - -import java.sql.PreparedStatement; -import java.sql.SQLException; - -public interface PreparedStatementSetter { - - void setArgument(PreparedStatement preparedStatement) throws SQLException; - -} diff --git a/src/main/java/wooteco/chess/database/RowMapper.java b/src/main/java/wooteco/chess/database/RowMapper.java deleted file mode 100644 index 94c39cdf9d..0000000000 --- a/src/main/java/wooteco/chess/database/RowMapper.java +++ /dev/null @@ -1,10 +0,0 @@ -package wooteco.chess.database; - -import java.sql.ResultSet; -import java.sql.SQLException; - -public interface RowMapper { - - T mapRow(ResultSet resultSet) throws SQLException; - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 69b89983cb..8f3e0fb1a2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,11 @@ -spring.h2.console.enabled=true \ No newline at end of file +# H2 +#spring.h2.console.enabled=true +# MySql +spring.datasource.url=jdbc:mysql://localhost:13306/woowa_level_02_chess?useSSL=false&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=root +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.initialization-mode=ALWAYS +# Logging +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.jdbc=TRACE \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..c4338a08fc --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE if not exists game_room +( + id bigint AUTO_INCREMENT primary key, + name varchar(255) NOT NULL unique, + state boolean NOT NULL +); + +CREATE TABLE if not exists game_history +( + id bigint AUTO_INCREMENT primary key, + game_room bigint NOT NULL, + source_position varchar(5) NOT NULL, + target_position varchar(5) NOT NULL, + created_time timestamp NOT NULL +); \ No newline at end of file diff --git a/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java b/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java deleted file mode 100644 index 8eed5866d2..0000000000 --- a/src/test/java/wooteco/chess/dao/InMemoryChessGameDao.java +++ /dev/null @@ -1,45 +0,0 @@ -package wooteco.chess.dao; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import wooteco.chess.entity.ChessGameEntity; - -public class InMemoryChessGameDao implements ChessGameDao { - - private final Map chessGameRepository = new HashMap<>(); - private long autoIncrement; - - public InMemoryChessGameDao() { - this.autoIncrement = 0L; - } - - @Override - public long add(final ChessGameEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - autoIncrement++; - - final ChessGameEntity chessGameEntity = ChessGameEntity.of(autoIncrement, entity); - - chessGameRepository.put(autoIncrement, chessGameEntity); - return autoIncrement; - } - - @Override - public long findMaxGameId() { - return autoIncrement; - } - - @Override - public boolean isEmpty() { - return chessGameRepository.isEmpty(); - } - - @Override - public void deleteAll() { - autoIncrement = 0L; - chessGameRepository.clear(); - } - -} diff --git a/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java b/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java deleted file mode 100644 index d67c80a634..0000000000 --- a/src/test/java/wooteco/chess/dao/InMemoryChessHistoryDao.java +++ /dev/null @@ -1,43 +0,0 @@ -package wooteco.chess.dao; - -import static java.util.stream.Collectors.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import wooteco.chess.entity.ChessHistoryEntity; - -public class InMemoryChessHistoryDao implements ChessHistoryDao { - - private final Map chessHistoryRepository = new HashMap<>(); - private long autoIncrement; - - public InMemoryChessHistoryDao() { - this.autoIncrement = 0L; - } - - @Override - public List findAllByGameId(final long gameId) { - return chessHistoryRepository.values().stream() - .filter(entity -> entity.getGameId() == gameId) - .collect(toList()); - } - - @Override - public void add(final ChessHistoryEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - autoIncrement++; - - final ChessHistoryEntity chessHistoryEntity = ChessHistoryEntity.of(autoIncrement, entity); - - chessHistoryRepository.put(autoIncrement, chessHistoryEntity); - } - - @Override - public void deleteAll() { - chessHistoryRepository.clear(); - } - -} diff --git a/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java b/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java deleted file mode 100644 index b0eb5f40ad..0000000000 --- a/src/test/java/wooteco/chess/dao/MySqlChessGameDaoTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package wooteco.chess.dao; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -import wooteco.chess.database.JdbcTemplate; -import wooteco.chess.database.MySqlDataSource; -import wooteco.chess.entity.ChessGameEntity; - -class MySqlChessGameDaoTest { - - private static final String CHESS_GAME_TABLE = "chess_games"; - private static final long INIT_AUTO_INCREMENT = 1L; - - private JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - jdbcTemplate = new JdbcTemplate(MySqlDataSource.getInstance()); - - final String query1 = "DELETE FROM " + CHESS_GAME_TABLE; - jdbcTemplate.executeUpdate(query1); - - final String query2 = "ALTER TABLE " + CHESS_GAME_TABLE + " AUTO_INCREMENT = ?"; - jdbcTemplate.executeUpdate(query2, - preparedStatement -> preparedStatement.setLong(1, INIT_AUTO_INCREMENT)); - } - - @ParameterizedTest - @NullSource - void MySqlChessGameDao_NullJdbcTemplate_ExceptionThrown(final JdbcTemplate jdbcTemplate) { - assertThatThrownBy(() -> new MySqlChessGameDao(jdbcTemplate)) - .isInstanceOf(NullPointerException.class) - .hasMessage("JdbcTemplate이 null입니다."); - } - - @Test - void MySqlChessGameDao_JdbcTemplate_GenerateInstance() { - assertThat(new MySqlChessGameDao(jdbcTemplate)).isInstanceOf(MySqlChessGameDao.class); - } - - @ParameterizedTest - @NullSource - void add_NullChessGameEntity_ExceptionThrown(final ChessGameEntity entity) { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - - assertThatThrownBy(() -> mySqlChessGameDao.add(entity)) - .isInstanceOf(NullPointerException.class) - .hasMessage("엔티티가 null입니다."); - } - - @Test - void add_ChessGameEntity_InsertChessGame() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); - - assertThat(mySqlChessGameDao.add(entity)).isEqualTo(INIT_AUTO_INCREMENT); - } - - @Test - void findMaxGameId_EmptyChessGame_ExceptionThrown() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - - assertThat(mySqlChessGameDao.findMaxGameId()).isEqualTo(MySqlChessGameDao.EMPTY_CHESS_GAME); - } - - @Test - void findMaxGameId_ReturnMaxGameId() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); - mySqlChessGameDao.add(entity); - - assertThat(mySqlChessGameDao.findMaxGameId()).isEqualTo(INIT_AUTO_INCREMENT); - } - - @Test - void isEmpty_EmptyChessGame_ReturnTrue() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - - assertThat(mySqlChessGameDao.isEmpty()).isTrue(); - } - - @Test - void isEmpty_NotEmptyChessGame_ReturnFalse() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); - mySqlChessGameDao.add(entity); - - assertThat(mySqlChessGameDao.isEmpty()).isFalse(); - } - - @Test - void deleteAll_ExistChessGame_DeleteAllFromChessGameTable() { - final MySqlChessGameDao mySqlChessGameDao = new MySqlChessGameDao(jdbcTemplate); - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now()); - mySqlChessGameDao.add(entity); - mySqlChessGameDao.deleteAll(); - - assertThat(findAll().isEmpty()).isTrue(); - } - - private List findAll() { - final String query = "SELECT * FROM " + CHESS_GAME_TABLE; - - return jdbcTemplate.executeQuery(query, resultSet -> { - final List entities = new ArrayList<>(); - - while (resultSet.next()) { - entities.add(ChessGameEntity.of( - resultSet.getLong("game_id"), - resultSet.getTimestamp("created_time").toLocalDateTime())); - } - return entities; - }); - } - - @AfterEach - void tearDown() { - final String query = "DELETE FROM " + CHESS_GAME_TABLE; - - jdbcTemplate.executeUpdate(query); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java b/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java deleted file mode 100644 index e779623f96..0000000000 --- a/src/test/java/wooteco/chess/dao/MySqlChessHistoryDaoTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package wooteco.chess.dao; - -import static org.assertj.core.api.Assertions.*; - -import java.sql.ResultSet; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -import wooteco.chess.database.JdbcTemplate; -import wooteco.chess.database.MySqlDataSource; -import wooteco.chess.entity.ChessHistoryEntity; - -class MySqlChessHistoryDaoTest { - - private static final String CHESS_HISTORY_TABLE = "chess_histories"; - private static final long INIT_AUTO_INCREMENT = 1L; - - private JdbcTemplate jdbcTemplate; - - @BeforeEach - void setUp() { - jdbcTemplate = new JdbcTemplate(MySqlDataSource.getInstance()); - - final String query1 = "DELETE FROM " + CHESS_HISTORY_TABLE; - jdbcTemplate.executeUpdate(query1); - - final String query2 = "ALTER TABLE " + CHESS_HISTORY_TABLE + " AUTO_INCREMENT = ?"; - jdbcTemplate.executeUpdate(query2, - preparedStatement -> preparedStatement.setLong(1, INIT_AUTO_INCREMENT)); - } - - @ParameterizedTest - @NullSource - void MySqlChessHistoryDao_NullJdbcTemplate_ExceptionThrown(final JdbcTemplate jdbcTemplate) { - assertThatThrownBy(() -> new MySqlChessHistoryDao(jdbcTemplate)) - .isInstanceOf(NullPointerException.class) - .hasMessage("JdbcTemplate이 null입니다."); - } - - @Test - void MySqlChessHistoryDao_JdbcTemplate_GenerateInstance() { - assertThat(new MySqlChessHistoryDao(jdbcTemplate)).isInstanceOf(MySqlChessHistoryDao.class); - } - - @Test - void findAllByGameId_GameId_ReturnChessHistoryEntities() { - final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); - final long gameId = 1; - final ChessHistoryEntity value = ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now()); - mySqlChessHistoryDao.add(value); - - assertThat(isSame(mySqlChessHistoryDao.findAllByGameId(gameId), value)).isTrue(); - } - - private boolean isSame(final List actual, final ChessHistoryEntity expected) { - return actual.size() == 1 - && actual.get(0).getGameId() == expected.getGameId() - && actual.get(0).getStart().equals(expected.getStart()) - && actual.get(0).getEnd().equals(expected.getEnd()); - } - - @ParameterizedTest - @NullSource - void add_NullChessHistoryEntity_ExceptionThrown(final ChessHistoryEntity entity) { - final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); - - assertThatThrownBy(() -> mySqlChessHistoryDao.add(entity)) - .isInstanceOf(NullPointerException.class) - .hasMessage("엔티티가 null입니다."); - } - - @Test - void add_ChessHistoryEntity_InsertChessHistory() { - final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); - final long gameId = 1; - mySqlChessHistoryDao.add(ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now())); - - assertThat(isChessHistoryExist("b2", "b4")).isTrue(); - } - - private boolean isChessHistoryExist(final String start, final String end) { - final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE + " WHERE start = ? AND end = ?"; - - return jdbcTemplate.executeQuery(query, ResultSet::next, preparedStatement -> { - preparedStatement.setString(1, start); - preparedStatement.setString(2, end); - }); - } - - @Test - void deleteAll_DeleteAllChessHistoryFromChessHistoryTable() { - final MySqlChessHistoryDao mySqlChessHistoryDao = new MySqlChessHistoryDao(jdbcTemplate); - mySqlChessHistoryDao.add(ChessHistoryEntity.of("b2", "b4")); - mySqlChessHistoryDao.deleteAll(); - - assertThat(findAll().isEmpty()).isTrue(); - } - - private List findAll() { - final String query = "SELECT * FROM " + CHESS_HISTORY_TABLE; - - return jdbcTemplate.executeQuery(query, resultSet -> { - final List entities = new ArrayList<>(); - - while (resultSet.next()) { - entities.add(ChessHistoryEntity.of( - resultSet.getLong("history_id"), - resultSet.getLong("game_id"), - resultSet.getString("start"), - resultSet.getString("end"), - resultSet.getTimestamp("created_time").toLocalDateTime())); - } - return entities; - }); - } - - @AfterEach - void tearDown() { - final String query = "DELETE FROM " + CHESS_HISTORY_TABLE; - - jdbcTemplate.executeUpdate(query); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java b/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java new file mode 100644 index 0000000000..e73209327d --- /dev/null +++ b/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java @@ -0,0 +1,44 @@ +package wooteco.chess.database; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; +import org.springframework.data.relational.core.conversion.DbActionExecutionException; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import wooteco.chess.entity.GameRoom; + +@ExtendWith(SpringExtension.class) +@DataJdbcTest +@Transactional +class GameRoomRepositoryTest { + + @Autowired + GameRoomRepository gameRoomRepository; + + @DisplayName("이름으로 게임방을 검색하기") + @Test + void findByName() { + String gameRoomName = "test"; + GameRoom savedGameRoom = gameRoomRepository.save(new GameRoom(gameRoomName)); + + GameRoom expected = gameRoomRepository.findByName(gameRoomName); + assertThat(savedGameRoom.getName()).isEqualTo(expected.getName()); + } + + @DisplayName("중복된 이름의 게임방 생성시 오류 발생") + @Test + void save_duplicatedName_ExceptionThrown() { + String game = "test"; + + gameRoomRepository.save(new GameRoom(game)); + assertThatThrownBy(() -> gameRoomRepository.save(new GameRoom(game))) + .isInstanceOf(DbActionExecutionException.class); + } + +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000000..ecd2662b61 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,11 @@ +# H2 +spring.h2.console.enabled=true +# MySql +#spring.datasource.url=jdbc:mysql://localhost:13306/woowa_level_02_chess?useSSL=false&serverTimezone=UTC +#spring.datasource.username=root +#spring.datasource.password=root +#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +#spring.datasource.initialization-mode=ALWAYS +# Logging +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.jdbc=TRACE From 8edb79ff96900421798ca8bb4ce73dadf766d46c Mon Sep 17 00:00:00 2001 From: lxxjn0 Date: Wed, 29 Apr 2020 13:14:46 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat=20:=20GameHistory,=20GameRoom=20Enti?= =?UTF-8?q?ty=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpringChessController.java | 50 ++++-- .../java/wooteco/chess/entity/BaseEntity.java | 24 --- .../wooteco/chess/entity/ChessGameEntity.java | 34 ----- .../chess/entity/ChessHistoryEntity.java | 76 ---------- .../wooteco/chess/entity/GameHistory.java | 66 ++++++++ .../java/wooteco/chess/entity/GameRoom.java | 58 +++++++ .../wooteco/chess/service/ChessService.java | 99 ++++++------ .../wooteco/chess/entity/BaseEntityTest.java | 36 ----- .../chess/entity/ChessGameEntityTest.java | 41 ----- .../chess/entity/ChessHistoryEntityTest.java | 80 ---------- .../chess/service/ChessServiceTest.java | 142 ------------------ 11 files changed, 211 insertions(+), 495 deletions(-) delete mode 100644 src/main/java/wooteco/chess/entity/BaseEntity.java delete mode 100644 src/main/java/wooteco/chess/entity/ChessGameEntity.java delete mode 100644 src/main/java/wooteco/chess/entity/ChessHistoryEntity.java create mode 100644 src/main/java/wooteco/chess/entity/GameHistory.java create mode 100644 src/main/java/wooteco/chess/entity/GameRoom.java delete mode 100644 src/test/java/wooteco/chess/entity/BaseEntityTest.java delete mode 100644 src/test/java/wooteco/chess/entity/ChessGameEntityTest.java delete mode 100644 src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java delete mode 100644 src/test/java/wooteco/chess/service/ChessServiceTest.java diff --git a/src/main/java/wooteco/chess/controller/SpringChessController.java b/src/main/java/wooteco/chess/controller/SpringChessController.java index 5aa1c91747..e23308a9c5 100644 --- a/src/main/java/wooteco/chess/controller/SpringChessController.java +++ b/src/main/java/wooteco/chess/controller/SpringChessController.java @@ -1,7 +1,11 @@ package wooteco.chess.controller; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -21,36 +25,52 @@ public SpringChessController(final ChessService chessService) { } @GetMapping("/") - public String start() { + public String index(Model model) { + model.addAttribute("gameNames", chessService.showAllGames()); return "index"; } - @GetMapping("/chess") - public String loadChessGame(final Model model) { - final ChessGameDto chessGameDto = chessService.loadChessGame(); + @PostMapping("/game/new") + public String createGame(@RequestParam("name") final String name, final Model model, HttpServletResponse response) { + ChessGameDto chessGameDto = chessService.createChessGame(name); + Cookie cookie = new Cookie("gameId", String.valueOf(chessGameDto.getId())); + cookie.setPath("/"); + response.addCookie(cookie); return renderGame(chessGameDto, model); } - @PostMapping("/chess_play") - public String playChessGame(@RequestParam final String sourcePosition, @RequestParam final String targetPosition, - final Model model) { - final ChessGameDto chessGameDto = chessService.playChessGame(sourcePosition.trim(), targetPosition.trim()); + @PostMapping("/game") + public String showGame(@RequestParam("name") final String name, final Model model, HttpServletResponse response) { + ChessGameDto chessGameDto = chessService.loadChessGameByName(name); + Long gameId = chessGameDto.getId(); - if (chessGameDto.isEndState()) { + if (chessService.isEndGame(gameId)) { return renderResult(chessGameDto, model); } + + Cookie cookie = new Cookie("gameId", String.valueOf(gameId)); + cookie.setPath("/"); + response.addCookie(cookie); return renderGame(chessGameDto, model); } - @PostMapping("/chess_new") - public String newChessGame(final Model model) { - return renderGame(chessService.createChessGame(), model); + @PostMapping("/game/play") + public String playChessGame(@CookieValue("gameId") Cookie gameIdCookie, @RequestParam final String sourcePosition, + @RequestParam final String targetPosition, final Model model) { + final Long gameId = Long.parseLong(gameIdCookie.getValue()); + final ChessGameDto chessGameDto = chessService.playChessGame(gameId, sourcePosition.trim(), + targetPosition.trim()); + + if (chessGameDto.isEndState()) { + return renderResult(chessGameDto, model); + } + return renderGame(chessGameDto, model); } - @PostMapping("/chess_end") - public String endChessGame(final Model model) { - final ChessGameDto chessGameDto = chessService.endChessGame(); + @PostMapping("/game/end") + public String endChessGame(@CookieValue("gameId") Long gameId, final Model model) { + final ChessGameDto chessGameDto = chessService.endChessGame(gameId); return renderResult(chessGameDto, model); } diff --git a/src/main/java/wooteco/chess/entity/BaseEntity.java b/src/main/java/wooteco/chess/entity/BaseEntity.java deleted file mode 100644 index 32d8ff50b5..0000000000 --- a/src/main/java/wooteco/chess/entity/BaseEntity.java +++ /dev/null @@ -1,24 +0,0 @@ -package wooteco.chess.entity; - -import java.time.LocalDateTime; -import java.util.Objects; - -public abstract class BaseEntity implements Comparable { - - protected final LocalDateTime createdTime; - - protected BaseEntity(final LocalDateTime createdTime) { - Objects.requireNonNull(createdTime, "생성 시간이 null입니다."); - this.createdTime = createdTime; - } - - public LocalDateTime getCreatedTime() { - return createdTime; - } - - @Override - public int compareTo(final BaseEntity that) { - return this.createdTime.compareTo(that.createdTime); - } - -} diff --git a/src/main/java/wooteco/chess/entity/ChessGameEntity.java b/src/main/java/wooteco/chess/entity/ChessGameEntity.java deleted file mode 100644 index a1d1a3e3f6..0000000000 --- a/src/main/java/wooteco/chess/entity/ChessGameEntity.java +++ /dev/null @@ -1,34 +0,0 @@ -package wooteco.chess.entity; - -import java.time.LocalDateTime; -import java.util.Objects; - -public class ChessGameEntity extends BaseEntity { - - private static final long DEFAULT_GAME_ID = 0L; - - private final long gameId; - - private ChessGameEntity(final long gameId, final LocalDateTime createdTime) { - super(createdTime); - this.gameId = gameId; - } - - public static ChessGameEntity of(final long gameId, final LocalDateTime createdTime) { - return new ChessGameEntity(gameId, createdTime); - } - - public static ChessGameEntity of(final long gameId, final ChessGameEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - return new ChessGameEntity(gameId, entity.createdTime); - } - - public static ChessGameEntity of(final LocalDateTime createdTime) { - return new ChessGameEntity(DEFAULT_GAME_ID, createdTime); - } - - public long getGameId() { - return gameId; - } - -} diff --git a/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java b/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java deleted file mode 100644 index 5bd3ac8ea0..0000000000 --- a/src/main/java/wooteco/chess/entity/ChessHistoryEntity.java +++ /dev/null @@ -1,76 +0,0 @@ -package wooteco.chess.entity; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Arrays; -import java.util.Objects; - -import wooteco.chess.domain.chessGame.ChessCommand; - -public class ChessHistoryEntity extends BaseEntity { - - private static final long DEFAULT_HISTORY_ID = 0L; - private static final long DEFAULT_GAME_ID = 0L; - private static final String MOVE_COMMAND = "move"; - - private final long historyId; - private final long gameId; - private final String start; - private final String end; - - private ChessHistoryEntity(final long historyId, final long gameId, final String start, final String end, - final LocalDateTime createdTime) { - super(createdTime); - validate(start, end); - this.historyId = historyId; - this.gameId = gameId; - this.start = start; - this.end = end; - } - - public static ChessHistoryEntity of(final long historyId, final long gameId, final String start, final String end, - final LocalDateTime createdTime) { - return new ChessHistoryEntity(historyId, gameId, start, end, createdTime); - } - - public static ChessHistoryEntity of(final long gameId, final String start, final String end, - final LocalDateTime createdTime) { - return new ChessHistoryEntity(DEFAULT_HISTORY_ID, gameId, start, end, createdTime); - } - - public static ChessHistoryEntity of(final String start, final String end) { - return new ChessHistoryEntity(DEFAULT_HISTORY_ID, DEFAULT_GAME_ID, start, end, - LocalDateTime.now(ZoneId.of("Asia/Seoul"))); - } - - public static ChessHistoryEntity of(final long historyId, final ChessHistoryEntity entity) { - Objects.requireNonNull(entity, "엔티티가 null입니다."); - return new ChessHistoryEntity(historyId, entity.gameId, entity.start, entity.end, entity.createdTime); - } - - private void validate(final String start, final String end) { - Objects.requireNonNull(start, "출발 위치가 null입니다."); - Objects.requireNonNull(end, "도착 위치가 null입니다."); - } - - public ChessCommand generateMoveCommand() { - return ChessCommand.of(Arrays.asList(MOVE_COMMAND, start, end)); - } - - public long getHistoryId() { - return historyId; - } - - public long getGameId() { - return gameId; - } - - public String getStart() { - return start; - } - - public String getEnd() { - return end; - } - -} diff --git a/src/main/java/wooteco/chess/entity/GameHistory.java b/src/main/java/wooteco/chess/entity/GameHistory.java new file mode 100644 index 0000000000..d2f84436d9 --- /dev/null +++ b/src/main/java/wooteco/chess/entity/GameHistory.java @@ -0,0 +1,66 @@ +package wooteco.chess.entity; + +import java.time.LocalDateTime; +import java.util.Arrays; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import wooteco.chess.domain.chessGame.ChessCommand; + +@Table("game_history") +public class GameHistory { + + private static final String MOVE_COMMAND = "move"; + + @Id + private Long id; + + @Column("source_position") + private String sourcePosition; + + @Column("target_position") + private String targetPosition; + + @Column("created_time") + private LocalDateTime createdTime; + + @Column("game_room") + private Long gameRoom; + + public GameHistory() { + } + + public GameHistory(final String sourcePosition, final String targetPosition, final Long gameRoom) { + this.sourcePosition = sourcePosition; + this.targetPosition = targetPosition; + this.createdTime = LocalDateTime.now(); + this.gameRoom = gameRoom; + } + + public ChessCommand generateMoveCommand() { + return ChessCommand.of(Arrays.asList(MOVE_COMMAND, sourcePosition, targetPosition)); + } + + public Long getId() { + return id; + } + + public String getSourcePosition() { + return sourcePosition; + } + + public String getTargetPosition() { + return targetPosition; + } + + public LocalDateTime getCreatedTime() { + return createdTime; + } + + public Long getGameRoom() { + return gameRoom; + } + +} diff --git a/src/main/java/wooteco/chess/entity/GameRoom.java b/src/main/java/wooteco/chess/entity/GameRoom.java new file mode 100644 index 0000000000..aad1ba5411 --- /dev/null +++ b/src/main/java/wooteco/chess/entity/GameRoom.java @@ -0,0 +1,58 @@ +package wooteco.chess.entity; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +@Table("game_room") +public class GameRoom { + + @Id + private Long id; + private String name; + private Boolean state; + private Set gameHistories; + + public GameRoom() { + } + + public GameRoom(final String name) { + this.name = name; + this.state = false; + this.gameHistories = new LinkedHashSet<>(); + } + + public GameRoom(final GameRoom gameRoom, final boolean state) { + this.id = gameRoom.id; + this.name = gameRoom.name; + this.state = state; + this.gameHistories = gameRoom.gameHistories; + } + + public void addGameHistory(GameHistory gameHistory) { + gameHistories.add(gameHistory); + } + + public void removeGameHistory(GameHistory gameHistory) { + gameHistories.remove(gameHistory); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Boolean getState() { + return state; + } + + public Set getGameHistories() { + return gameHistories; + } + +} diff --git a/src/main/java/wooteco/chess/service/ChessService.java b/src/main/java/wooteco/chess/service/ChessService.java index c97fadbb04..c91b02a8b0 100644 --- a/src/main/java/wooteco/chess/service/ChessService.java +++ b/src/main/java/wooteco/chess/service/ChessService.java @@ -1,20 +1,21 @@ package wooteco.chess.service; -import java.time.LocalDateTime; -import java.time.ZoneId; +import static java.util.stream.Collectors.*; + import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import org.springframework.stereotype.Service; -import wooteco.chess.dao.ChessGameDao; -import wooteco.chess.dao.ChessHistoryDao; +import wooteco.chess.database.GameRoomRepository; import wooteco.chess.domain.chessBoard.ChessBoard; import wooteco.chess.domain.chessBoard.ChessBoardInitializer; import wooteco.chess.domain.chessGame.ChessCommand; import wooteco.chess.domain.chessGame.ChessGame; -import wooteco.chess.entity.ChessGameEntity; -import wooteco.chess.entity.ChessHistoryEntity; +import wooteco.chess.entity.GameHistory; +import wooteco.chess.entity.GameRoom; import wooteco.chess.service.dto.ChessGameDto; @Service @@ -22,72 +23,76 @@ public class ChessService { private static final String MOVE_COMMAND = "move"; - private final ChessGameDao chessGameDao; - private final ChessHistoryDao chessHistoryDao; - - public ChessService(final ChessGameDao chessGameDao, final ChessHistoryDao chessHistoryDao) { - Objects.requireNonNull(chessGameDao, "ChessGameDao가 null입니다."); - Objects.requireNonNull(chessHistoryDao, "ChessHistoryDao가 null입니다."); - this.chessGameDao = chessGameDao; - this.chessHistoryDao = chessHistoryDao; - checkChessGameIsEmpty(chessGameDao); - } - - private void checkChessGameIsEmpty(final ChessGameDao chessGameDao) { - if (chessGameDao.isEmpty()) { - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + private final GameRoomRepository gameRoomRepository; - chessGameDao.add(entity); - } + public ChessService(final GameRoomRepository gameRoomRepository) { + this.gameRoomRepository = gameRoomRepository; } - public ChessGameDto loadChessGame() { - final long gameId = chessGameDao.findMaxGameId(); - return ChessGameDto.of(initChessGameOf(gameId)); + public ChessGameDto loadChessGameByName(String name) { + GameRoom gameRoom = gameRoomRepository.findByName(name); + return ChessGameDto.of(gameRoom.getId(), initChessGameOf(gameRoom)); } - private ChessGame initChessGameOf(final long gameId) { + private ChessGame initChessGameOf(final GameRoom gameRoom) { final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); final ChessGame chessGame = ChessGame.from(chessBoard); - chessHistoryDao.findAllByGameId(gameId).stream() - .sorted(ChessHistoryEntity::compareTo) - .map(ChessHistoryEntity::generateMoveCommand) + gameRoom.getGameHistories().stream() + .map(GameHistory::generateMoveCommand) .forEach(chessGame::move); + return chessGame; } - public ChessGameDto playChessGame(final String sourcePosition, final String targetPosition) { + public ChessGameDto playChessGame(final Long gameId, final String sourcePosition, final String targetPosition) { Objects.requireNonNull(sourcePosition, "소스 위치가 null입니다."); Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); - return moveChessPiece(sourcePosition, targetPosition); + return moveChessPiece(gameId, sourcePosition, targetPosition); } - private ChessGameDto moveChessPiece(final String sourcePosition, final String targetPosition) { - final long gameId = chessGameDao.findMaxGameId(); - final ChessGame chessGame = initChessGameOf(gameId); + private ChessGameDto moveChessPiece(final Long gameId, final String sourcePosition, final String targetPosition) { + GameRoom gameRoom = gameRoomRepository.findById(gameId) + .orElseThrow(() -> new NoSuchElementException("게임이 존재하지 않습니다.")); + + final ChessGame chessGame = initChessGameOf(gameRoom); final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, sourcePosition, targetPosition)); - final ChessHistoryEntity chessHistoryEntity = - ChessHistoryEntity.of(gameId, sourcePosition, targetPosition, LocalDateTime.now(ZoneId.of("Asia/Seoul"))); chessGame.move(chessCommand); - chessHistoryDao.add(chessHistoryEntity); - return ChessGameDto.of(chessGame); - } + gameRoom.addGameHistory(new GameHistory(sourcePosition, targetPosition, gameId)); - public ChessGameDto createChessGame() { - final ChessGameEntity entity = ChessGameEntity.of(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); - final long gameId = chessGameDao.add(entity); + gameRoomRepository.save(gameRoom); - return ChessGameDto.of(initChessGameOf(gameId)); + return ChessGameDto.of(gameRoom.getId(), chessGame); } - public ChessGameDto endChessGame() { - final long gameId = chessGameDao.findMaxGameId(); - final ChessGame chessGame = initChessGameOf(gameId); + public ChessGameDto createChessGame(final String name) { + GameRoom savedGameRoom = gameRoomRepository.save(new GameRoom(name)); + return ChessGameDto.of(savedGameRoom.getId(), initChessGameOf(savedGameRoom)); + } + + public ChessGameDto endChessGame(final Long gameId) { + GameRoom gameRoom = gameRoomRepository.findById(gameId) + .orElseThrow(() -> new NoSuchElementException("게임이 존재하지 않습니다.")); + final ChessGame chessGame = initChessGameOf(gameRoom); chessGame.end(); - return ChessGameDto.of(chessGame); + gameRoomRepository.save(new GameRoom(gameRoom, true)); + return ChessGameDto.of(gameRoom.getId(), chessGame); + } + + public boolean isEndGame(final Long gameId) { + GameRoom gameRoom = gameRoomRepository.findById(gameId) + .orElseThrow(() -> new NoSuchElementException("게임이 존재하지 않습니다.")); + return gameRoom.getState(); + } + + public List showAllGames() { + List gameRooms = gameRoomRepository.findAll(); + + return gameRooms.stream() + .map(GameRoom::getName) + .collect(toList()); } } diff --git a/src/test/java/wooteco/chess/entity/BaseEntityTest.java b/src/test/java/wooteco/chess/entity/BaseEntityTest.java deleted file mode 100644 index edf11db089..0000000000 --- a/src/test/java/wooteco/chess/entity/BaseEntityTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package wooteco.chess.entity; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -class BaseEntityTest { - - @ParameterizedTest - @NullSource - void BaseEntity_NullCreatedTime_ExceptionThrown(final LocalDateTime localDateTime) { - assertThatThrownBy(() -> ChessGameEntity.of(localDateTime)) - .isInstanceOf(NullPointerException.class) - .hasMessage("생성 시간이 null입니다."); - } - - @Test - void BaseEntity_CreatedTime_GenerateInstance() { - final BaseEntity baseEntity = ChessGameEntity.of(LocalDateTime.now()); - - assertThat(baseEntity).isInstanceOf(BaseEntity.class); - } - - @Test - void compareTo_CompareByCreatedTime_ReturnCompareToCreatedTime() { - final BaseEntity earlyChessGame = ChessGameEntity.of(LocalDateTime.MIN); - final BaseEntity lateChessGame = ChessGameEntity.of(LocalDateTime.MAX); - - assertThat(earlyChessGame.compareTo(lateChessGame)).isEqualTo(LocalDateTime.MIN.compareTo(LocalDateTime.MAX)); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java b/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java deleted file mode 100644 index b16bbce8cb..0000000000 --- a/src/test/java/wooteco/chess/entity/ChessGameEntityTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package wooteco.chess.entity; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -class ChessGameEntityTest { - - private static final long DEFAULT_GAME_ID = 0L; - - @Test - void of_GameIdAndCreatedTime_GenerateInstance() { - assertThat(ChessGameEntity.of(1, LocalDateTime.now())).isInstanceOf(ChessGameEntity.class); - } - - @ParameterizedTest - @NullSource - void of_NullChessGameEntity_ExceptionThrown(final ChessGameEntity entity) { - assertThatThrownBy(() -> ChessGameEntity.of(1, entity)) - .isInstanceOf(NullPointerException.class) - .hasMessage("엔티티가 null입니다."); - } - - @Test - void of_GameIdAndChessGameEntity_GenerateInstance() { - final ChessGameEntity entity = ChessGameEntity.of(1, LocalDateTime.now()); - - assertThat(ChessGameEntity.of(1, entity)).isInstanceOf(ChessGameEntity.class); - } - - @Test - void of_CreatedTime_GenerateInstance() { - assertThat(ChessGameEntity.of(LocalDateTime.now())).isInstanceOf(ChessGameEntity.class) - .extracting("gameId").isEqualTo(DEFAULT_GAME_ID); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java b/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java deleted file mode 100644 index b24112b68a..0000000000 --- a/src/test/java/wooteco/chess/entity/ChessHistoryEntityTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package wooteco.chess.entity; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; -import java.util.Arrays; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -import wooteco.chess.domain.chessGame.ChessCommand; - -class ChessHistoryEntityTest { - - private static final String MOVE_COMMAND = "move"; - - @ParameterizedTest - @NullSource - void validate_NullStart_ExceptionThrown(final String start) { - assertThatThrownBy(() -> ChessHistoryEntity.of(1, 1, start, "b2", LocalDateTime.now())) - .isInstanceOf(NullPointerException.class) - .hasMessage("출발 위치가 null입니다."); - } - - @ParameterizedTest - @NullSource - void validate_NullEnd_ExceptionThrown(final String end) { - assertThatThrownBy(() -> ChessHistoryEntity.of(1, 1, "b1", end, LocalDateTime.now())) - .isInstanceOf(NullPointerException.class) - .hasMessage("도착 위치가 null입니다."); - } - - @Test - void of_HistoryIdAndGameIdAndStartAndEndAndCreatedTime_GenerateInstance() { - assertThat(ChessHistoryEntity.of(1, 1, "b1", "b2", LocalDateTime.now())).isInstanceOf(ChessHistoryEntity.class); - } - - @Test - void of_GameIdAndStartAndEndAndCreatedTime_GenerateInstance() { - assertThat(ChessHistoryEntity.of(1, "b1", "b2", LocalDateTime.now())).isInstanceOf(ChessHistoryEntity.class); - } - - @Test - void of_StartAndEnd_GenerateInstance() { - assertThat(ChessHistoryEntity.of("b1", "b2")).isInstanceOf(ChessHistoryEntity.class); - } - - @ParameterizedTest - @NullSource - void of_NullChessHistoryEntity_ExceptionThrown(final ChessHistoryEntity entity) { - assertThatThrownBy(() -> ChessHistoryEntity.of(1, entity)) - .isInstanceOf(NullPointerException.class) - .hasMessage("엔티티가 null입니다."); - } - - @Test - void of_HistoryIdAndChessHistoryEntity_GenerateInstance() { - final ChessHistoryEntity entity = ChessHistoryEntity.of("b1", "b2"); - - assertThat(ChessHistoryEntity.of(1, entity)).isInstanceOf(ChessHistoryEntity.class); - } - - @Test - void of_ChessIdAndStartAndEnd_GenerateInstance() { - assertThat(ChessHistoryEntity.of("b1", "b2")).isInstanceOf(ChessHistoryEntity.class); - } - - @Test - void generateMoveCommand_MoveCommandFromStartToEnd_ReturnChessCommand() { - final String start = "b1"; - final String end = "b2"; - final ChessHistoryEntity chessHistoryEntity = ChessHistoryEntity.of(start, end); - - final ChessCommand expected = ChessCommand.of(Arrays.asList(MOVE_COMMAND, start, end)); - Assertions.assertThat(chessHistoryEntity.generateMoveCommand()).isEqualTo(expected); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/service/ChessServiceTest.java b/src/test/java/wooteco/chess/service/ChessServiceTest.java deleted file mode 100644 index 443249f615..0000000000 --- a/src/test/java/wooteco/chess/service/ChessServiceTest.java +++ /dev/null @@ -1,142 +0,0 @@ -package wooteco.chess.service; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; -import java.util.Arrays; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -import wooteco.chess.dao.ChessGameDao; -import wooteco.chess.dao.ChessHistoryDao; -import wooteco.chess.dao.InMemoryChessGameDao; -import wooteco.chess.dao.InMemoryChessHistoryDao; -import wooteco.chess.domain.chessBoard.ChessBoard; -import wooteco.chess.domain.chessBoard.ChessBoardInitializer; -import wooteco.chess.domain.chessGame.ChessCommand; -import wooteco.chess.domain.chessGame.ChessGame; -import wooteco.chess.entity.ChessGameEntity; -import wooteco.chess.entity.ChessHistoryEntity; -import wooteco.chess.service.dto.ChessGameDto; - -class ChessServiceTest { - - private static final String MOVE_COMMAND = "move"; - - private ChessGameDao chessGameDao; - private ChessHistoryDao chessHistoryDao; - - @BeforeEach - void setUp() { - chessGameDao = new InMemoryChessGameDao(); - chessHistoryDao = new InMemoryChessHistoryDao(); - } - - @ParameterizedTest - @NullSource - void ChessService_NullChessGameDao_ExceptionThrown(final ChessGameDao chessGameDao) { - assertThatThrownBy(() -> new ChessService(chessGameDao, chessHistoryDao)) - .isInstanceOf(NullPointerException.class) - .hasMessage("ChessGameDao가 null입니다."); - } - - @ParameterizedTest - @NullSource - void ChessService_NullChessHistoryDao_ExceptionThrown(final ChessHistoryDao chessHistoryDao) { - assertThatThrownBy(() -> new ChessService(chessGameDao, chessHistoryDao)) - .isInstanceOf(NullPointerException.class) - .hasMessage("ChessHistoryDao가 null입니다."); - } - - @Test - void ChessService_ChessHistoryDao_GenerateInstance() { - assertThat(new ChessService(chessGameDao, chessHistoryDao)).isInstanceOf(ChessService.class); - } - - @Test - void checkChessGameIsEmpty_EmptyChessGame_AddChessGame() { - new ChessService(chessGameDao, chessHistoryDao); - - assertThat(chessGameDao.findMaxGameId()).isEqualTo(1); - } - - @Test - void checkChessGameIsEmpty_ExistChessGame_NotAddChessGame() { - chessGameDao.add(ChessGameEntity.of(LocalDateTime.now())); - chessGameDao.add(ChessGameEntity.of(LocalDateTime.now())); - new ChessService(chessGameDao, chessHistoryDao); - - assertThat(chessGameDao.findMaxGameId()).isEqualTo(2); - } - - @Test - void loadChessGame_RecentChessGame_ReturnChessGameDto() { - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - final long gameId = chessGameDao.findMaxGameId(); - final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); - chessHistoryDao.add(ChessHistoryEntity.of(gameId, "b2", "b4", LocalDateTime.now())); - chessHistoryDao.add(ChessHistoryEntity.of(gameId, "b7", "b5", LocalDateTime.now())); - chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b2", "b4"))); - chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b7", "b5"))); - - final ChessGameDto expected = ChessGameDto.of(chessGame); - assertThat(chessService.loadChessGame()).isEqualTo(expected); - } - - @ParameterizedTest - @NullSource - void playChessGame_NullSourcePosition_ExceptionThrown(final String sourcePosition) { - final String targetPosition = "b2"; - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - - assertThatThrownBy(() -> chessService.playChessGame(sourcePosition, targetPosition)) - .isInstanceOf(NullPointerException.class) - .hasMessage("소스 위치가 null입니다."); - } - - @ParameterizedTest - @NullSource - void playChessGame_NullTargetPosition_ExceptionThrown(final String targetPosition) { - final String sourcePosition = "b2"; - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - - assertThatThrownBy(() -> chessService.playChessGame(sourcePosition, targetPosition)) - .isInstanceOf(NullPointerException.class) - .hasMessage("타겟 위치가 null입니다."); - } - - @Test - void playChessGame_SourcePositionAndTargetPosition_ReturnChessGameDto() { - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); - chessGame.move(ChessCommand.of(Arrays.asList(MOVE_COMMAND, "b2", "b4"))); - - final ChessGameDto expected = ChessGameDto.of(chessGame); - assertThat(chessService.playChessGame("b2", "b4")).isEqualTo(expected); - } - - @Test - void createChessGame_RemoveAllChessHistory_ReturnChessGameDto() { - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - chessHistoryDao.add(ChessHistoryEntity.of("b2", "b4")); - chessHistoryDao.add(ChessHistoryEntity.of("b7", "b5")); - - final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); - final ChessGameDto expected = ChessGameDto.of(chessGame); - assertThat(chessService.createChessGame()).isEqualTo(expected); - } - - @Test - void endChessGame_EndRecentChessGame() { - final ChessService chessService = new ChessService(chessGameDao, chessHistoryDao); - final ChessGame chessGame = ChessGame.from(new ChessBoard(ChessBoardInitializer.create())); - chessGame.end(); - - final ChessGameDto expected = ChessGameDto.of(chessGame); - assertThat(chessService.endChessGame()).isEqualTo(expected); - } - -} \ No newline at end of file From f63dbb085b098dd3ef02ee4e8d83d2f06d304f98 Mon Sep 17 00:00:00 2001 From: lxxjn0 Date: Wed, 29 Apr 2020 13:16:20 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor=20:=20=EA=B2=8C=EC=9E=84?= =?UTF-8?q?=EB=B0=A9=20=EC=83=9D=EC=84=B1=EA=B3=BC=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EC=9A=B4=20=EB=B0=A9=EC=9D=84=20=EC=83=9D=EC=84=B1=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20index.hbs=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/service/dto/ChessGameDto.java | 13 +++++++++--- .../wooteco/chess/web/PieceNameConverter.java | 2 +- .../images/black-bishop.png | Bin .../{public => static}/images/black-king.png | Bin .../images/black-knight.png | Bin .../{public => static}/images/black-pawn.png | Bin .../{public => static}/images/black-queen.png | Bin .../{public => static}/images/black-rook.png | Bin .../images/white-bishop.png | Bin .../{public => static}/images/white-king.png | Bin .../images/white-knight.png | Bin .../{public => static}/images/white-pawn.png | Bin .../{public => static}/images/white-queen.png | Bin .../{public => static}/images/white-rook.png | Bin .../resources/{public => static}/style.css | 19 ++++++++++++++++++ src/main/resources/templates/chess.hbs | 12 +++++------ src/main/resources/templates/index.hbs | 17 ++++++++++++++-- src/main/resources/templates/result.hbs | 10 ++++----- .../chess/service/dto/ChessBoardDtoTest.java | 2 +- .../chess/service/dto/ChessGameDtoTest.java | 4 ++-- 20 files changed, 59 insertions(+), 20 deletions(-) rename src/main/resources/{public => static}/images/black-bishop.png (100%) rename src/main/resources/{public => static}/images/black-king.png (100%) rename src/main/resources/{public => static}/images/black-knight.png (100%) rename src/main/resources/{public => static}/images/black-pawn.png (100%) rename src/main/resources/{public => static}/images/black-queen.png (100%) rename src/main/resources/{public => static}/images/black-rook.png (100%) rename src/main/resources/{public => static}/images/white-bishop.png (100%) rename src/main/resources/{public => static}/images/white-king.png (100%) rename src/main/resources/{public => static}/images/white-knight.png (100%) rename src/main/resources/{public => static}/images/white-pawn.png (100%) rename src/main/resources/{public => static}/images/white-queen.png (100%) rename src/main/resources/{public => static}/images/white-rook.png (100%) rename src/main/resources/{public => static}/style.css (91%) diff --git a/src/main/java/wooteco/chess/service/dto/ChessGameDto.java b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java index e12f3a9b46..3a42b01344 100644 --- a/src/main/java/wooteco/chess/service/dto/ChessGameDto.java +++ b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java @@ -6,14 +6,16 @@ public class ChessGameDto { + private final Long id; private final ChessBoardDto chessBoardDto; private final PieceColorDto pieceColorDto; private final ChessStatusDtos chessStatusDtos; private final boolean isEndState; private final boolean isKingCaught; - private ChessGameDto(final ChessBoardDto chessBoardDto, final PieceColorDto pieceColorDto, + private ChessGameDto(final Long id, final ChessBoardDto chessBoardDto, final PieceColorDto pieceColorDto, final ChessStatusDtos chessStatusDtos, final boolean isEndState, final boolean isKingCaught) { + this.id = id; this.chessBoardDto = chessBoardDto; this.pieceColorDto = pieceColorDto; this.chessStatusDtos = chessStatusDtos; @@ -21,7 +23,8 @@ private ChessGameDto(final ChessBoardDto chessBoardDto, final PieceColorDto piec this.isKingCaught = isKingCaught; } - public static ChessGameDto of(final ChessGame chessGame) { + // NOTE: 2020/04/28 DTO가 관리해야하는 필드의 종류 + public static ChessGameDto of(final Long id, final ChessGame chessGame) { Objects.requireNonNull(chessGame, "체스 게임이 null입니다."); final ChessBoardDto chessBoardDto = ChessBoardDto.of(chessGame.getChessBoard()); @@ -30,7 +33,11 @@ public static ChessGameDto of(final ChessGame chessGame) { final boolean isEndStatus = chessGame.isEndState(); final boolean isKingCaught = chessGame.isKingCaught(); - return new ChessGameDto(chessBoardDto, pieceColorDto, chessStatusDtos, isEndStatus, isKingCaught); + return new ChessGameDto(id, chessBoardDto, pieceColorDto, chessStatusDtos, isEndStatus, isKingCaught); + } + + public Long getId() { + return id; } public ChessBoardDto getChessBoardDto() { diff --git a/src/main/java/wooteco/chess/web/PieceNameConverter.java b/src/main/java/wooteco/chess/web/PieceNameConverter.java index 2a0f2008c3..174baffad7 100644 --- a/src/main/java/wooteco/chess/web/PieceNameConverter.java +++ b/src/main/java/wooteco/chess/web/PieceNameConverter.java @@ -17,7 +17,7 @@ public enum PieceNameConverter { BLACK_PAWN("P", "black-pawn"), WHITE_PAWN("p", "white-pawn"); - private static final String IMAGE_SOURCE_FORMAT = ""; + private static final String IMAGE_SOURCE_FORMAT = ""; private final String chessPieceName; private final String imageFileName; diff --git a/src/main/resources/public/images/black-bishop.png b/src/main/resources/static/images/black-bishop.png similarity index 100% rename from src/main/resources/public/images/black-bishop.png rename to src/main/resources/static/images/black-bishop.png diff --git a/src/main/resources/public/images/black-king.png b/src/main/resources/static/images/black-king.png similarity index 100% rename from src/main/resources/public/images/black-king.png rename to src/main/resources/static/images/black-king.png diff --git a/src/main/resources/public/images/black-knight.png b/src/main/resources/static/images/black-knight.png similarity index 100% rename from src/main/resources/public/images/black-knight.png rename to src/main/resources/static/images/black-knight.png diff --git a/src/main/resources/public/images/black-pawn.png b/src/main/resources/static/images/black-pawn.png similarity index 100% rename from src/main/resources/public/images/black-pawn.png rename to src/main/resources/static/images/black-pawn.png diff --git a/src/main/resources/public/images/black-queen.png b/src/main/resources/static/images/black-queen.png similarity index 100% rename from src/main/resources/public/images/black-queen.png rename to src/main/resources/static/images/black-queen.png diff --git a/src/main/resources/public/images/black-rook.png b/src/main/resources/static/images/black-rook.png similarity index 100% rename from src/main/resources/public/images/black-rook.png rename to src/main/resources/static/images/black-rook.png diff --git a/src/main/resources/public/images/white-bishop.png b/src/main/resources/static/images/white-bishop.png similarity index 100% rename from src/main/resources/public/images/white-bishop.png rename to src/main/resources/static/images/white-bishop.png diff --git a/src/main/resources/public/images/white-king.png b/src/main/resources/static/images/white-king.png similarity index 100% rename from src/main/resources/public/images/white-king.png rename to src/main/resources/static/images/white-king.png diff --git a/src/main/resources/public/images/white-knight.png b/src/main/resources/static/images/white-knight.png similarity index 100% rename from src/main/resources/public/images/white-knight.png rename to src/main/resources/static/images/white-knight.png diff --git a/src/main/resources/public/images/white-pawn.png b/src/main/resources/static/images/white-pawn.png similarity index 100% rename from src/main/resources/public/images/white-pawn.png rename to src/main/resources/static/images/white-pawn.png diff --git a/src/main/resources/public/images/white-queen.png b/src/main/resources/static/images/white-queen.png similarity index 100% rename from src/main/resources/public/images/white-queen.png rename to src/main/resources/static/images/white-queen.png diff --git a/src/main/resources/public/images/white-rook.png b/src/main/resources/static/images/white-rook.png similarity index 100% rename from src/main/resources/public/images/white-rook.png rename to src/main/resources/static/images/white-rook.png diff --git a/src/main/resources/public/style.css b/src/main/resources/static/style.css similarity index 91% rename from src/main/resources/public/style.css rename to src/main/resources/static/style.css index 63f78d3783..4901f5f294 100644 --- a/src/main/resources/public/style.css +++ b/src/main/resources/static/style.css @@ -20,6 +20,25 @@ article > form { text-align: center; } +#game__room { + display: inline-block; + margin: 0.5rem auto; + padding: 0.8rem; + width: 10rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +.game__room::placeholder { + color: #999; +} + #start__button { display: inline-block; padding: 15px 0; diff --git a/src/main/resources/templates/chess.hbs b/src/main/resources/templates/chess.hbs index 9352cc2923..921fbd37ec 100644 --- a/src/main/resources/templates/chess.hbs +++ b/src/main/resources/templates/chess.hbs @@ -3,7 +3,7 @@ - + 스티치의 체스 게임 @@ -102,12 +102,12 @@
{{#status}} {{pieceColor}}의 점수 : {{score}} -
+
{{/status}}
-
+ 이동시킬 피스 : 
@@ -118,10 +118,10 @@
-
- + +
-
+
diff --git a/src/main/resources/templates/index.hbs b/src/main/resources/templates/index.hbs index 2eee3b2f7e..68bad6d066 100644 --- a/src/main/resources/templates/index.hbs +++ b/src/main/resources/templates/index.hbs @@ -3,7 +3,7 @@ - + 스티치의 체스 게임 @@ -11,9 +11,22 @@

Chess Game

-
+ + 게임 찾기:  +
+
+ 새로운 게임:  + + +
+ +
diff --git a/src/main/resources/templates/result.hbs b/src/main/resources/templates/result.hbs index 2e15e115aa..274b3e833a 100644 --- a/src/main/resources/templates/result.hbs +++ b/src/main/resources/templates/result.hbs @@ -3,7 +3,7 @@ - + 스티치의 체스 게임 @@ -100,14 +100,14 @@
{{#status}} - {{pieceColor}}의 점수 : {{score}} -
+ {{pieceColor}}의 점수 : {{score}} +
{{/status}}
-
- + +
diff --git a/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java b/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java index 723b67be7a..f7553ae03a 100644 --- a/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java +++ b/src/test/java/wooteco/chess/service/dto/ChessBoardDtoTest.java @@ -16,7 +16,7 @@ class ChessBoardDtoTest { - private static final String IMAGE_SOURCE_FORMAT = ""; + private static final String IMAGE_SOURCE_FORMAT = ""; @ParameterizedTest @NullSource diff --git a/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java b/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java index 11a30ad6f2..9ea76484c7 100644 --- a/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java +++ b/src/test/java/wooteco/chess/service/dto/ChessGameDtoTest.java @@ -15,7 +15,7 @@ class ChessGameDtoTest { @ParameterizedTest @NullSource void of_NullChessGame_ExceptionThrown(final ChessGame chessGame) { - assertThatThrownBy(() -> ChessGameDto.of(chessGame)) + assertThatThrownBy(() -> ChessGameDto.of(Long.valueOf(1), chessGame)) .isInstanceOf(NullPointerException.class) .hasMessage("체스 게임이 null입니다."); } @@ -25,7 +25,7 @@ void of_ChessGame_GenerateInstance() { final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); final ChessGame chessGame = ChessGame.from(chessBoard); - assertThat(ChessGameDto.of(chessGame)).isInstanceOf(ChessGameDto.class); + assertThat(ChessGameDto.of(Long.valueOf(1), chessGame)).isInstanceOf(ChessGameDto.class); } } \ No newline at end of file From 97e29202f4f36136ba136922e357669ab204c93a Mon Sep 17 00:00:00 2001 From: lxxjn0 Date: Wed, 29 Apr 2020 14:57:14 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor=20:=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=EB=90=9C=20=EA=B2=8C=EC=9E=84=20=ED=91=9C=EC=8B=9C=20=EB=B0=8F?= =?UTF-8?q?=20=EC=99=95=EC=9D=B4=20=EC=9E=A1=ED=9E=88=EB=A9=B4=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=EC=9D=B4=20=EC=A2=85=EB=A3=8C=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/ConsoleChessApplication.java | 6 ++-- ...oller.java => ConsoleChessController.java} | 4 +-- .../controller/SpringChessController.java | 2 +- .../wooteco/chess/service/ChessService.java | 14 ++++---- .../chess/service/dto/GameRoomDto.java | 36 +++++++++++++++++++ src/main/resources/static/style.css | 30 ++++++++++++++-- src/main/resources/templates/index.hbs | 16 ++++----- 7 files changed, 83 insertions(+), 25 deletions(-) rename src/main/java/wooteco/chess/controller/{ChessController.java => ConsoleChessController.java} (94%) create mode 100644 src/main/java/wooteco/chess/service/dto/GameRoomDto.java diff --git a/src/main/java/wooteco/chess/ConsoleChessApplication.java b/src/main/java/wooteco/chess/ConsoleChessApplication.java index a7b4b09124..9b09689387 100644 --- a/src/main/java/wooteco/chess/ConsoleChessApplication.java +++ b/src/main/java/wooteco/chess/ConsoleChessApplication.java @@ -2,7 +2,7 @@ import java.util.List; -import wooteco.chess.controller.ChessController; +import wooteco.chess.controller.ConsoleChessController; import wooteco.chess.domain.chessBoard.ChessBoard; import wooteco.chess.domain.chessBoard.ChessBoardInitializer; import wooteco.chess.domain.chessGame.ChessCommand; @@ -16,11 +16,11 @@ public class ConsoleChessApplication { public static void main(String[] args) { ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); ChessGame chessGame = ChessGame.from(chessBoard); - ChessController chessController = new ChessController(chessGame); + ConsoleChessController consoleChessController = new ConsoleChessController(chessGame); ConsoleOutputView.printChessStart(); if (isStartChessCommand()) { - chessController.run(); + consoleChessController.run(); } ConsoleOutputView.printChessEnd(); } diff --git a/src/main/java/wooteco/chess/controller/ChessController.java b/src/main/java/wooteco/chess/controller/ConsoleChessController.java similarity index 94% rename from src/main/java/wooteco/chess/controller/ChessController.java rename to src/main/java/wooteco/chess/controller/ConsoleChessController.java index d8d243518a..2cae30cbce 100644 --- a/src/main/java/wooteco/chess/controller/ChessController.java +++ b/src/main/java/wooteco/chess/controller/ConsoleChessController.java @@ -10,11 +10,11 @@ import wooteco.chess.domain.chessGame.ChessGame; import wooteco.chess.util.ChessBoardRenderer; -public class ChessController { +public class ConsoleChessController { private final ChessGame chessGame; - public ChessController(ChessGame chessGame) { + public ConsoleChessController(ChessGame chessGame) { Objects.requireNonNull(chessGame, "체스 게임이 null입니다."); this.chessGame = chessGame; } diff --git a/src/main/java/wooteco/chess/controller/SpringChessController.java b/src/main/java/wooteco/chess/controller/SpringChessController.java index e23308a9c5..36fbd43f67 100644 --- a/src/main/java/wooteco/chess/controller/SpringChessController.java +++ b/src/main/java/wooteco/chess/controller/SpringChessController.java @@ -26,7 +26,7 @@ public SpringChessController(final ChessService chessService) { @GetMapping("/") public String index(Model model) { - model.addAttribute("gameNames", chessService.showAllGames()); + model.addAttribute("games", chessService.showAllGames()); return "index"; } diff --git a/src/main/java/wooteco/chess/service/ChessService.java b/src/main/java/wooteco/chess/service/ChessService.java index c91b02a8b0..1fa0804a34 100644 --- a/src/main/java/wooteco/chess/service/ChessService.java +++ b/src/main/java/wooteco/chess/service/ChessService.java @@ -17,6 +17,7 @@ import wooteco.chess.entity.GameHistory; import wooteco.chess.entity.GameRoom; import wooteco.chess.service.dto.ChessGameDto; +import wooteco.chess.service.dto.GameRoomDto; @Service public class ChessService { @@ -52,17 +53,14 @@ public ChessGameDto playChessGame(final Long gameId, final String sourcePosition } private ChessGameDto moveChessPiece(final Long gameId, final String sourcePosition, final String targetPosition) { - GameRoom gameRoom = gameRoomRepository.findById(gameId) + final GameRoom gameRoom = gameRoomRepository.findById(gameId) .orElseThrow(() -> new NoSuchElementException("게임이 존재하지 않습니다.")); - final ChessGame chessGame = initChessGameOf(gameRoom); final ChessCommand chessCommand = ChessCommand.of(Arrays.asList(MOVE_COMMAND, sourcePosition, targetPosition)); chessGame.move(chessCommand); gameRoom.addGameHistory(new GameHistory(sourcePosition, targetPosition, gameId)); - - gameRoomRepository.save(gameRoom); - + gameRoomRepository.save(new GameRoom(gameRoom, chessGame.isEndState())); return ChessGameDto.of(gameRoom.getId(), chessGame); } @@ -77,7 +75,7 @@ public ChessGameDto endChessGame(final Long gameId) { final ChessGame chessGame = initChessGameOf(gameRoom); chessGame.end(); - gameRoomRepository.save(new GameRoom(gameRoom, true)); + gameRoomRepository.save(new GameRoom(gameRoom, chessGame.isEndState())); return ChessGameDto.of(gameRoom.getId(), chessGame); } @@ -87,11 +85,11 @@ public boolean isEndGame(final Long gameId) { return gameRoom.getState(); } - public List showAllGames() { + public List showAllGames() { List gameRooms = gameRoomRepository.findAll(); return gameRooms.stream() - .map(GameRoom::getName) + .map(GameRoomDto::of) .collect(toList()); } diff --git a/src/main/java/wooteco/chess/service/dto/GameRoomDto.java b/src/main/java/wooteco/chess/service/dto/GameRoomDto.java new file mode 100644 index 0000000000..065bf48b6a --- /dev/null +++ b/src/main/java/wooteco/chess/service/dto/GameRoomDto.java @@ -0,0 +1,36 @@ +package wooteco.chess.service.dto; + +import java.util.Objects; + +import wooteco.chess.entity.GameRoom; + +public class GameRoomDto { + + private Long id; + private String name; + private Boolean state; + + private GameRoomDto(final Long id, final String name, final Boolean state) { + this.id = id; + this.name = name; + this.state = state; + } + + public static GameRoomDto of(final GameRoom gameRoom) { + Objects.requireNonNull(gameRoom, "게임이 null입니다."); + return new GameRoomDto(gameRoom.getId(), gameRoom.getName(), gameRoom.getState()); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Boolean getState() { + return state; + } + +} diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css index 4901f5f294..5bf2aebe1a 100644 --- a/src/main/resources/static/style.css +++ b/src/main/resources/static/style.css @@ -20,11 +20,34 @@ article > form { text-align: center; } -#game__room { +article.game__start { + text-align: center; + line-height: 1.6; + font-size: 2.5rem; +} + +#game__room__list { display: inline-block; margin: 0.5rem auto; padding: 0.8rem; - width: 10rem; + width: 12rem; + height: 3rem; + text-align: center; + font-size: 2rem; + border: 3px solid #333; + background-color: #777; + color: #fff; + border-radius: 10px; + text-decoration: none; + cursor: pointer; +} + +#game__room__new { + display: inline-block; + margin: 0.5rem auto; + padding: 0.8rem; + width: 12rem; + height: 3rem; text-align: center; font-size: 2rem; border: 3px solid #333; @@ -35,7 +58,7 @@ article > form { cursor: pointer; } -.game__room::placeholder { +#game__room__new::placeholder { color: #999; } @@ -52,6 +75,7 @@ article > form { border-radius: 10px; text-decoration: none; cursor: pointer; + vertical-align: middle; } #start__button:hover { diff --git a/src/main/resources/templates/index.hbs b/src/main/resources/templates/index.hbs index 68bad6d066..82e399fa52 100644 --- a/src/main/resources/templates/index.hbs +++ b/src/main/resources/templates/index.hbs @@ -10,19 +10,19 @@

Chess Game

-
+
- 게임 찾기:  - + {{#games}} + + {{/games}}
- 새로운 게임:  - + 새로운 게임 :  +
From a5938ab1a10a5337544cb8a1f0916e8e1046dd70 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 May 2020 21:55:47 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor=20:=20ConsoleApplication=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B0=8F=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/ConsoleChessApplication.java | 37 ---------- .../controller/ConsoleChessController.java | 67 ------------------- .../chess/util/ChessBoardRenderer.java | 61 ----------------- .../java/wooteco/chess/util/StringUtil.java | 21 ------ .../wooteco/chess/view/ConsoleInputView.java | 14 ---- .../wooteco/chess/view/ConsoleOutputView.java | 40 ----------- .../chess/util/ChessBoardRendererTest.java | 43 ------------ .../wooteco/chess/util/StringUtilTest.java | 31 --------- 8 files changed, 314 deletions(-) delete mode 100644 src/main/java/wooteco/chess/ConsoleChessApplication.java delete mode 100644 src/main/java/wooteco/chess/controller/ConsoleChessController.java delete mode 100644 src/main/java/wooteco/chess/util/ChessBoardRenderer.java delete mode 100644 src/main/java/wooteco/chess/util/StringUtil.java delete mode 100644 src/main/java/wooteco/chess/view/ConsoleInputView.java delete mode 100644 src/main/java/wooteco/chess/view/ConsoleOutputView.java delete mode 100644 src/test/java/wooteco/chess/util/ChessBoardRendererTest.java delete mode 100644 src/test/java/wooteco/chess/util/StringUtilTest.java diff --git a/src/main/java/wooteco/chess/ConsoleChessApplication.java b/src/main/java/wooteco/chess/ConsoleChessApplication.java deleted file mode 100644 index bdbcdf287d..0000000000 --- a/src/main/java/wooteco/chess/ConsoleChessApplication.java +++ /dev/null @@ -1,37 +0,0 @@ -package wooteco.chess; - -import wooteco.chess.controller.ConsoleChessController; -import wooteco.chess.domain.chessBoard.ChessBoard; -import wooteco.chess.domain.chessBoard.ChessBoardInitializer; -import wooteco.chess.domain.chessGame.ChessCommand; -import wooteco.chess.domain.chessGame.ChessGame; -import wooteco.chess.util.StringUtil; -import wooteco.chess.view.ConsoleInputView; -import wooteco.chess.view.ConsoleOutputView; - -import java.util.List; - -public class ConsoleChessApplication { - - public static void main(String[] args) { - ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); - ChessGame chessGame = ChessGame.from(chessBoard); - ConsoleChessController consoleChessController = new ConsoleChessController(chessGame); - - ConsoleOutputView.printChessStart(); - if (isStartChessCommand()) { - consoleChessController.run(); - } - ConsoleOutputView.printChessEnd(); - } - - private static boolean isStartChessCommand() { - List commandArguments = StringUtil.splitChessCommand(ConsoleInputView.inputChessCommand()); - - if (!ChessCommand.of(commandArguments).isStartChessCommand()) { - throw new IllegalArgumentException("게임을 시작해야 입력 가능한 명령어입니다."); - } - return true; - } - -} diff --git a/src/main/java/wooteco/chess/controller/ConsoleChessController.java b/src/main/java/wooteco/chess/controller/ConsoleChessController.java deleted file mode 100644 index 359356eb88..0000000000 --- a/src/main/java/wooteco/chess/controller/ConsoleChessController.java +++ /dev/null @@ -1,67 +0,0 @@ -package wooteco.chess.controller; - -import wooteco.chess.domain.chessGame.ChessCommand; -import wooteco.chess.domain.chessGame.ChessGame; -import wooteco.chess.util.ChessBoardRenderer; - -import java.util.Objects; - -import static wooteco.chess.util.StringUtil.splitChessCommand; -import static wooteco.chess.view.ConsoleInputView.inputChessCommand; -import static wooteco.chess.view.ConsoleOutputView.*; - -public class ConsoleChessController { - - private final ChessGame chessGame; - - public ConsoleChessController(ChessGame chessGame) { - Objects.requireNonNull(chessGame, "체스 게임이 null입니다."); - this.chessGame = chessGame; - } - - public void run() { - do { - printChessBoard(ChessBoardRenderer.render(chessGame.getChessBoard())); - - ChessCommand chessCommand = receiveChessCommand(); - playChessGameBy(chessCommand); - } while (!isEndState()); - } - - private ChessCommand receiveChessCommand() { - ChessCommand chessCommand = ChessCommand.of(splitChessCommand(inputChessCommand())); - - if (chessCommand.isStartChessCommand()) { - throw new IllegalArgumentException("start 명령어은 최초 시작 때만 사용 가능합니다."); - } - return chessCommand; - } - - private void playChessGameBy(ChessCommand chessCommand) { - if (chessCommand.isMoveChessCommand()) { - chessGame.move(chessCommand); - } - if (chessCommand.isStatusChessCommand()) { - double score = chessGame.status(chessCommand); - printStatus(chessCommand.getStatusPieceColor(), score); - } - if (chessCommand.isEndChessCommand()) { - chessGame.end(); - } - } - - private boolean isEndState() { - if (!chessGame.isEndState()) { - return false; - } - return checkKingCaught(); - } - - private boolean checkKingCaught() { - if (chessGame.isKingCaught()) { - printKingCaught(chessGame.getCurrentPieceColor()); - } - return true; - } - -} diff --git a/src/main/java/wooteco/chess/util/ChessBoardRenderer.java b/src/main/java/wooteco/chess/util/ChessBoardRenderer.java deleted file mode 100644 index eea007fcfc..0000000000 --- a/src/main/java/wooteco/chess/util/ChessBoardRenderer.java +++ /dev/null @@ -1,61 +0,0 @@ -package wooteco.chess.util; - -import wooteco.chess.domain.chessBoard.ChessBoard; -import wooteco.chess.domain.position.ChessFile; -import wooteco.chess.domain.position.ChessRank; -import wooteco.chess.domain.position.Position; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import static java.util.stream.Collectors.*; - -public class ChessBoardRenderer { - - private static final String EMPTY_SPACE = ""; - private static final String DELIMITER = " "; - private static final String RANK_APPEND_FORMAT = "%s %s"; - private static final String EMPTY_POSITION = "."; - - public static List render(final ChessBoard chessBoard) { - Objects.requireNonNull(chessBoard, "체스 보드가 null입니다."); - final List renderedChessBoard = renderEachChessRankFrom(chessBoard); - - Collections.reverse(renderedChessBoard); - appendChessFileName(renderedChessBoard); - return renderedChessBoard; - } - - private static List renderEachChessRankFrom(final ChessBoard chessBoard) { - return Arrays.stream(ChessRank.values()) - .map(chessRank -> renderChessRankFrom(chessBoard, chessRank)) - .collect(toList()); - } - - private static String renderChessRankFrom(final ChessBoard chessBoard, final ChessRank chessRank) { - return Arrays.stream(ChessFile.values()) - .map(chessFile -> renderChessPieceFrom(chessBoard, chessRank, chessFile)) - .collect(collectingAndThen(joining(DELIMITER), - renderedRank -> String.format(RANK_APPEND_FORMAT, renderedRank, chessRank))); - } - - private static String renderChessPieceFrom(final ChessBoard chessBoard, final ChessRank chessRank, - final ChessFile chessFile) { - final Position renderingPosition = Position.of(chessFile, chessRank); - - if (chessBoard.isChessPieceOn(renderingPosition)) { - return chessBoard.getChessPieceNameOn(renderingPosition); - } - return EMPTY_POSITION; - } - - private static void appendChessFileName(final List renderedChessBoard) { - renderedChessBoard.add(EMPTY_SPACE); - renderedChessBoard.add(Arrays.stream(ChessFile.values()) - .map(ChessFile::toString) - .collect(joining(DELIMITER))); - } - -} diff --git a/src/main/java/wooteco/chess/util/StringUtil.java b/src/main/java/wooteco/chess/util/StringUtil.java deleted file mode 100644 index 11be7e5fb9..0000000000 --- a/src/main/java/wooteco/chess/util/StringUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -package wooteco.chess.util; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import static java.util.stream.Collectors.toList; - -public class StringUtil { - - private static final String DELIMITER = " "; - - public static List splitChessCommand(final String chessCommand) { - Objects.requireNonNull(chessCommand, "분리할 명령어가 null입니다."); - - return Arrays.stream(chessCommand.split(DELIMITER)) - .map(String::trim) - .collect(toList()); - } - -} diff --git a/src/main/java/wooteco/chess/view/ConsoleInputView.java b/src/main/java/wooteco/chess/view/ConsoleInputView.java deleted file mode 100644 index 3038359267..0000000000 --- a/src/main/java/wooteco/chess/view/ConsoleInputView.java +++ /dev/null @@ -1,14 +0,0 @@ -package wooteco.chess.view; - -import java.util.Scanner; - -public class ConsoleInputView { - - private static final Scanner SCANNER = new Scanner(System.in); - - public static String inputChessCommand() { - System.out.print(ConsoleOutputView.PROMPT); - return SCANNER.nextLine(); - } - -} diff --git a/src/main/java/wooteco/chess/view/ConsoleOutputView.java b/src/main/java/wooteco/chess/view/ConsoleOutputView.java deleted file mode 100644 index 92640d3ef0..0000000000 --- a/src/main/java/wooteco/chess/view/ConsoleOutputView.java +++ /dev/null @@ -1,40 +0,0 @@ -package wooteco.chess.view; - -import wooteco.chess.domain.chessPiece.pieceType.PieceColor; - -import java.util.List; - -public class ConsoleOutputView { - - static final String PROMPT = "> "; - - private static void printNewLine() { - System.out.println(); - } - - public static void printChessStart() { - System.out.println(PROMPT + "체스 게임을 시작합니다."); - System.out.println(PROMPT + "게임 시작 : start"); - System.out.println(PROMPT + "게임 이동 : move source위치 target위치 - 예. move b2 b3"); - System.out.println(PROMPT + "게임 점수 : status 체스 색상 - 예. status white, status black"); - System.out.println(PROMPT + "게임 종료 : end"); - } - - public static void printChessBoard(List renderedChessBoard) { - printNewLine(); - renderedChessBoard.forEach(System.out::println); - } - - public static void printStatus(PieceColor statusPieceColor, double score) { - System.out.println(String.format("%s 점수 : %.1f", statusPieceColor.getColor(), score)); - } - - public static void printKingCaught(PieceColor catchingPieceColor) { - System.out.println(String.format("%s가 킹을 잡았습니다.", catchingPieceColor.getColor())); - } - - public static void printChessEnd() { - System.out.println(PROMPT + "게임 종료!"); - } - -} diff --git a/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java b/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java deleted file mode 100644 index 2d509922fc..0000000000 --- a/src/test/java/wooteco/chess/util/ChessBoardRendererTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package wooteco.chess.util; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; -import wooteco.chess.domain.chessBoard.ChessBoard; -import wooteco.chess.domain.chessBoard.ChessBoardInitializer; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class ChessBoardRendererTest { - - @ParameterizedTest - @NullSource - void render_NullChessBoard_ExceptionThrown(final ChessBoard chessBoard) { - assertThatThrownBy(() -> ChessBoardRenderer.render(chessBoard)) - .isInstanceOf(NullPointerException.class) - .hasMessage("체스 보드가 null입니다."); - } - - @Test - void render_ChessBoard_ReturnRenderedChessBoardByStringList() { - final ChessBoard chessBoard = new ChessBoard(ChessBoardInitializer.create()); - - final List expected = Arrays.asList( - "R N B Q K B N R 8", - "P P P P P P P P 7", - ". . . . . . . . 6", - ". . . . . . . . 5", - ". . . . . . . . 4", - ". . . . . . . . 3", - "p p p p p p p p 2", - "r n b q k b n r 1", - "", - "a b c d e f g h"); - assertThat(ChessBoardRenderer.render(chessBoard)).isEqualTo(expected); - } - -} \ No newline at end of file diff --git a/src/test/java/wooteco/chess/util/StringUtilTest.java b/src/test/java/wooteco/chess/util/StringUtilTest.java deleted file mode 100644 index 425b604037..0000000000 --- a/src/test/java/wooteco/chess/util/StringUtilTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package wooteco.chess.util; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullSource; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class StringUtilTest { - - @ParameterizedTest - @NullSource - void splitChessCommand_NullChessCommand_ExceptionThrown(final String chessCommand) { - assertThatThrownBy(() -> StringUtil.splitChessCommand(chessCommand)) - .isInstanceOf(NullPointerException.class) - .hasMessage("분리할 명령어가 null입니다."); - } - - @Test - void splitChessCommand_ChessCommand_ReturnListOfSpiltChessCommand() { - final String chessCommand = "move b1 b2"; - - final List expected = Arrays.asList("move", "b1", "b2"); - assertThat(StringUtil.splitChessCommand(chessCommand)).isEqualTo(expected); - } - -} \ No newline at end of file From 17ec337e50b3887c8eef1c055bbc8defb9323c73 Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 May 2020 22:01:33 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor=20:=20Service=20=ED=9D=90?= =?UTF-8?q?=EB=A6=84=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wooteco/chess/service/ChessService.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/wooteco/chess/service/ChessService.java b/src/main/java/wooteco/chess/service/ChessService.java index 7a32e6ce39..5994c3c9a4 100644 --- a/src/main/java/wooteco/chess/service/ChessService.java +++ b/src/main/java/wooteco/chess/service/ChessService.java @@ -1,6 +1,14 @@ package wooteco.chess.service; +import static java.util.stream.Collectors.*; + +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + import org.springframework.stereotype.Service; + import wooteco.chess.database.GameRoomRepository; import wooteco.chess.domain.chessBoard.ChessBoard; import wooteco.chess.domain.chessBoard.ChessBoardInitializer; @@ -11,13 +19,6 @@ import wooteco.chess.service.dto.ChessGameDto; import wooteco.chess.service.dto.GameRoomDto; -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; - -import static java.util.stream.Collectors.toList; - @Service public class ChessService { @@ -29,9 +30,17 @@ public ChessService(final GameRoomRepository gameRoomRepository) { this.gameRoomRepository = gameRoomRepository; } - public ChessGameDto loadChessGameByName(String name) { - GameRoom gameRoom = gameRoomRepository.findByName(name); - return ChessGameDto.of(gameRoom.getId(), initChessGameOf(gameRoom)); + public List showAllGames() { + List gameRooms = gameRoomRepository.findAll(); + + return gameRooms.stream() + .map(GameRoomDto::of) + .collect(toList()); + } + + public ChessGameDto createChessGame(final String name) { + GameRoom savedGameRoom = gameRoomRepository.save(new GameRoom(name)); + return ChessGameDto.of(savedGameRoom.getId(), initChessGameOf(savedGameRoom)); } private ChessGame initChessGameOf(final GameRoom gameRoom) { @@ -45,6 +54,11 @@ private ChessGame initChessGameOf(final GameRoom gameRoom) { return chessGame; } + public ChessGameDto loadChessGameByName(String name) { + GameRoom gameRoom = gameRoomRepository.findByName(name); + return ChessGameDto.of(gameRoom.getId(), initChessGameOf(gameRoom)); + } + public ChessGameDto playChessGame(final Long gameId, final String sourcePosition, final String targetPosition) { Objects.requireNonNull(sourcePosition, "소스 위치가 null입니다."); Objects.requireNonNull(targetPosition, "타겟 위치가 null입니다."); @@ -63,11 +77,6 @@ private ChessGameDto moveChessPiece(final Long gameId, final String sourcePositi return ChessGameDto.of(gameRoom.getId(), chessGame); } - public ChessGameDto createChessGame(final String name) { - GameRoom savedGameRoom = gameRoomRepository.save(new GameRoom(name)); - return ChessGameDto.of(savedGameRoom.getId(), initChessGameOf(savedGameRoom)); - } - public ChessGameDto endChessGame(final Long gameId) { GameRoom gameRoom = gameRoomRepository.findById(gameId) .orElseThrow(() -> new NoSuchElementException("게임이 존재하지 않습니다.")); @@ -84,12 +93,4 @@ public boolean isEndGame(final Long gameId) { return gameRoom.getState(); } - public List showAllGames() { - List gameRooms = gameRoomRepository.findAll(); - - return gameRooms.stream() - .map(GameRoomDto::of) - .collect(toList()); - } - } From 06ac56e204dcd49294cef2d2fab1d0a65a609e8c Mon Sep 17 00:00:00 2001 From: moon Date: Wed, 6 May 2020 22:09:12 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat=20:=20DB=EC=97=90=20GameRoom?= =?UTF-8?q?=EC=9D=B4=20=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=98=88=EC=99=B8=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wooteco/chess/database/GameRoomRepository.java | 8 +++++--- src/main/java/wooteco/chess/service/ChessService.java | 3 ++- .../chess/database/GameRoomRepositoryTest.java | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/wooteco/chess/database/GameRoomRepository.java b/src/main/java/wooteco/chess/database/GameRoomRepository.java index cf06555096..2cee1dc682 100644 --- a/src/main/java/wooteco/chess/database/GameRoomRepository.java +++ b/src/main/java/wooteco/chess/database/GameRoomRepository.java @@ -1,11 +1,13 @@ package wooteco.chess.database; +import java.util.List; +import java.util.Optional; + import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; -import wooteco.chess.entity.GameRoom; -import java.util.List; +import wooteco.chess.entity.GameRoom; public interface GameRoomRepository extends CrudRepository { @@ -13,6 +15,6 @@ public interface GameRoomRepository extends CrudRepository { List findAll(); @Query("SELECT * FROM game_room WHERE name = :name") - GameRoom findByName(@Param("name") final String name); + Optional findByName(@Param("name") final String name); } \ No newline at end of file diff --git a/src/main/java/wooteco/chess/service/ChessService.java b/src/main/java/wooteco/chess/service/ChessService.java index 5994c3c9a4..433819d324 100644 --- a/src/main/java/wooteco/chess/service/ChessService.java +++ b/src/main/java/wooteco/chess/service/ChessService.java @@ -55,7 +55,8 @@ private ChessGame initChessGameOf(final GameRoom gameRoom) { } public ChessGameDto loadChessGameByName(String name) { - GameRoom gameRoom = gameRoomRepository.findByName(name); + GameRoom gameRoom = gameRoomRepository.findByName(name) + .orElseThrow(() -> new NoSuchElementException("해당 이름을 가진 게임방이 존재하지 않습니다.")); return ChessGameDto.of(gameRoom.getId(), initChessGameOf(gameRoom)); } diff --git a/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java b/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java index fc3ffcfeff..9395a7828f 100644 --- a/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java +++ b/src/test/java/wooteco/chess/database/GameRoomRepositoryTest.java @@ -1,5 +1,9 @@ package wooteco.chess.database; +import static org.assertj.core.api.Assertions.*; + +import java.util.NoSuchElementException; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -8,10 +12,8 @@ import org.springframework.data.relational.core.conversion.DbActionExecutionException; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; -import wooteco.chess.entity.GameRoom; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import wooteco.chess.entity.GameRoom; @ExtendWith(SpringExtension.class) @DataJdbcTest @@ -27,7 +29,8 @@ void findByName() { String gameRoomName = "test"; GameRoom savedGameRoom = gameRoomRepository.save(new GameRoom(gameRoomName)); - GameRoom expected = gameRoomRepository.findByName(gameRoomName); + GameRoom expected = gameRoomRepository.findByName(gameRoomName) + .orElseThrow(() -> new NoSuchElementException("해당 이름을 가진 게임방이 존재하지 않습니다.")); assertThat(savedGameRoom.getName()).isEqualTo(expected.getName()); } From 51f78a01bd5da99b7556dce0c790175c89d5dbcd Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 May 2020 18:13:34 +0900 Subject: [PATCH 10/11] =?UTF-8?q?feat:=20MockMvc=EB=A5=BC=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpringChessControllerTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/test/java/wooteco/chess/controller/SpringChessControllerTest.java diff --git a/src/test/java/wooteco/chess/controller/SpringChessControllerTest.java b/src/test/java/wooteco/chess/controller/SpringChessControllerTest.java new file mode 100644 index 0000000000..eb8bbfc7b8 --- /dev/null +++ b/src/test/java/wooteco/chess/controller/SpringChessControllerTest.java @@ -0,0 +1,38 @@ +package wooteco.chess.controller; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import wooteco.chess.service.ChessService; +import wooteco.chess.service.dto.ChessGameDto; + +@RunWith(SpringRunner.class) +@WebMvcTest(SpringChessController.class) +class SpringChessControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ChessService chessService; + + @MockBean + private ChessGameDto chessGameDto; + + @DisplayName("index페이지로 간다.") + @Test + void goToIndexPage() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(view().name("index")); + } +} \ No newline at end of file From 82ae630d3db3be61cb5d823095e92f222f6935e1 Mon Sep 17 00:00:00 2001 From: moon Date: Fri, 8 May 2020 18:15:15 +0900 Subject: [PATCH 11/11] =?UTF-8?q?chore=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20todo=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/wooteco/chess/service/dto/ChessGameDto.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/wooteco/chess/service/dto/ChessGameDto.java b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java index 129a39c2e2..a538f7389e 100644 --- a/src/main/java/wooteco/chess/service/dto/ChessGameDto.java +++ b/src/main/java/wooteco/chess/service/dto/ChessGameDto.java @@ -1,9 +1,9 @@ package wooteco.chess.service.dto; -import wooteco.chess.domain.chessGame.ChessGame; - import java.util.Objects; +import wooteco.chess.domain.chessGame.ChessGame; + public class ChessGameDto { private final Long id; @@ -23,7 +23,6 @@ private ChessGameDto(final Long id, final ChessBoardDto chessBoardDto, final Pie this.isKingCaught = isKingCaught; } - // NOTE: 2020/04/28 DTO가 관리해야하는 필드의 종류 public static ChessGameDto of(final Long id, final ChessGame chessGame) { Objects.requireNonNull(chessGame, "체스 게임이 null입니다.");