diff --git a/.gitignore b/.gitignore index 5e0f049c593..6529aa8c8f9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,4 @@ out/ ### VS Code ### .vscode/ - db/ - -docker-compose.yml diff --git a/README.md b/README.md index 294703de882..7eb97509e33 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # java-chess -## 페어와 지킬 컨벤션 -1. 클래스 정의 다음 줄은 공백으로 한다. -2. test code에 사용하는 메서드는 `static import`한다. -3. `this`는 같은 클래스의 객체가 파라미터로 넘어왔을 때, 파라미터 변수 명이 필드의 변수 명과 겹칠 때 사용한다. +## 실행 방법 +1. [database](./database) 폴더로 이동한다. +2. [docker-compose.yml](./database/docker-compose.yml)를 통하여 docker를 실행한다. +3. [init.sql](./database/init.sql)의 명령어를 docker 내부에서 차례로 실행한다. +4. IDE를 통해 ChessApplication를 실행한다. ## 기능 요구 사항 ### 체스 말 - [x] 무슨 팀인지 알려준다. +- [x] 자신의 기물 점수를 알려준다. + - [x] queen 9점, rook 5점, bishop 3점, knight 2.5점, king 0점 + - [x] pawn은 1점, 같은 세로줄에 같은 색의 폰이 있는 경우 0.5점 ### 움직임 - [x] 이동 가능한지 판단한다. @@ -17,6 +21,9 @@ ### 팀 - [x] 흰색, 검은색을 구분한다. +### 말 점수 +- [x] 말 점수를 서로 더할 수 있다. + ### 체스 보드 - [x] 체스 말 위치 초기화를 한다. - [x] 해당 위치에 어떤 말이 있는지 알려준다. @@ -26,7 +33,8 @@ - [x] 이동 경로에 다른 말이 있을 경우 예외 - [x] 마지막 위치에 적 말이 있을 경우, 잡아먹는다. - [x] 흰색부터 번갈아가며 플레이한다. - + - [x] 상대편 왕을 잡으면, 해당 팀의 게임 승리로 게임을 종료한다. +- [x] 현재 각 팀별 기물 점수를 구한다. ### 위치 - [x] 가로 위치(왼쪽부터 a~h)를 저장한다. @@ -34,5 +42,11 @@ - [x] 서로 같은 위치인지 판단한다. - [x] 다음 동, 서, 남, 북쪽 위치를 알려준다. +### 저장소 +- [x] 각 말을 한 번 움직임을 저장할 수 있다. +- [x] 말의 전체 위치를 저장할 수 있다. +- [x] 게임이 끝나, 저장소를 초기화할 수 있다. +- [x] 현재 진행중인 게임이 있는지 알 수 있다. + ### 출력 - [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. diff --git a/build.gradle b/build.gradle index 3697236c6fb..20ad08a5a5e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,8 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + + runtimeOnly("com.mysql:mysql-connector-j:8.3.0") } java { diff --git a/database/docker-compose.yml b/database/docker-compose.yml new file mode 100644 index 00000000000..558a1d5a53f --- /dev/null +++ b/database/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.9" +services: + db: + image: mysql:8.0.28 + platform: linux/x86_64 + restart: always + ports: + - "13306:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: chess + MYSQL_USER: user + MYSQL_PASSWORD: password + TZ: Asia/Seoul + volumes: + - ./db/mysql/data:/var/lib/mysql + - ./db/mysql/config:/etc/mysql/conf.d + - ./db/mysql/init:/docker-entrypoint-initdb.d diff --git a/database/init.sql b/database/init.sql new file mode 100644 index 00000000000..a264767b107 --- /dev/null +++ b/database/init.sql @@ -0,0 +1,16 @@ +CREATE DATABASE IF NOT EXISTS chess; + +USE chess; + +CREATE TABLE chess_game ( + turn VARCHAR(10) CHECK (turn IN ('BLACK', 'WHITE')) not null, + PRIMARY KEY (turn) +); + +CREATE TABLE pieces ( + board_file VARCHAR(2) CHECK (board_file IN ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H')) not null, + board_rank VARCHAR(2) CHECK (board_rank IN ('1', '2', '3', '4', '5', '6', '7', '8')) not null, + type VARCHAR(10) CHECK (type IN ('KING', 'QUEEN', 'ROOK', 'BISHOP', 'KNIGHT', 'PAWN', 'EMPTY')) not null, + team VARCHAR(10) CHECK (team IN ('BLACK', 'WHITE', 'EMPTY')) not null, + PRIMARY KEY (board_rank, board_file) +); diff --git a/src/main/java/chess/ChessApplication.java b/src/main/java/chess/ChessApplication.java index a6c126dbce0..fe9461b6d58 100644 --- a/src/main/java/chess/ChessApplication.java +++ b/src/main/java/chess/ChessApplication.java @@ -1,19 +1,35 @@ package chess; +import chess.dao.ChessDao; +import chess.dao.ChessGameRepository; +import chess.dao.ConnectionManager; +import chess.dao.MysqlChessGameRepository; +import chess.dao.MysqlPieceRepository; +import chess.dao.PieceRepository; import chess.view.InputView; import chess.view.OutputView; public class ChessApplication { + private static final InputView INPUT_VIEW = new InputView(); + private static final OutputView OUTPUT_VIEW = new OutputView(); + public static void main(String[] args) { - InputView inputView = new InputView(); - OutputView outputView = new OutputView(); - ChessGame chessGame = new ChessGame(inputView, outputView); + ConnectionManager connectionManager = new ConnectionManager(); + ChessGameRepository chessGameRepository = new MysqlChessGameRepository(connectionManager); + PieceRepository pieceRepository = new MysqlPieceRepository(connectionManager); + ChessDao chessDao = new ChessDao(chessGameRepository, pieceRepository); + ChessService chessService = new ChessService(chessDao); + + ChessGame chessGame = new ChessGame(INPUT_VIEW, OUTPUT_VIEW, chessService); + run(chessGame); + } + private static void run(ChessGame chessGame) { try { - chessGame.start(); + chessGame.run(); } catch (IllegalArgumentException exception) { - outputView.printExceptionMessage(exception); + OUTPUT_VIEW.printExceptionMessage(exception); } } } diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java index 45178bfcdc4..a35c4cbbddb 100644 --- a/src/main/java/chess/ChessGame.java +++ b/src/main/java/chess/ChessGame.java @@ -1,81 +1,97 @@ package chess; -import chess.domain.Board; -import chess.domain.BoardFactory; -import chess.domain.piece.Piece; +import chess.domain.Team; import chess.domain.position.Position; import chess.dto.PieceDto; +import chess.dto.ProgressStatus; import chess.view.GameCommand; import chess.view.InputView; import chess.view.OutputView; -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Optional; public class ChessGame { private final InputView inputView; private final OutputView outputView; + private final ChessService chessService; - public ChessGame(InputView inputView, OutputView outputView) { + public ChessGame(InputView inputView, OutputView outputView, ChessService chessService) { this.inputView = inputView; this.outputView = outputView; + this.chessService = chessService; } - public void start() { + public void run() { + startGame(); + play(); + } + + private void startGame() { outputView.printStartGame(); GameCommand command = inputView.readCommand(); if (command.isStart()) { - Board board = BoardFactory.createInitBoard(); - showBoard(board); - play(board); - } - if (command.isMove()) { - throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); + initBoard(); + showCurrentTeam(); + showBoard(); + return; } + throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); } - private void play(Board board) { - while (true) { - processTurn(board); - } + private void initBoard() { + chessService.init(); + } + + private void showCurrentTeam() { + Team turn = chessService.findCurrentTurn(); + outputView.printCurrentTurn(turn); } - private void processTurn(Board board) { + private void play() { + ProgressStatus status; + do { + status = processTurn(); + } while (status.isContinue()); + showResult(status); + } + + private ProgressStatus processTurn() { GameCommand command = inputView.readCommand(); if (command.isStart()) { throw new IllegalArgumentException("이미 게임을 시작했습니다."); } - if (command.isEnd()) { - System.exit(0); + if (command.isMove()) { + return executeMove(); + } + if (command.isStatus()) { + return executeStatus(); } - executeMove(board); + return ProgressStatus.END_GAME; } - private void executeMove(Board board) { + private ProgressStatus executeMove() { Position start = inputView.readPosition(); Position end = inputView.readPosition(); - board.move(start, end); - showBoard(board); + ProgressStatus status = chessService.moveTo(start, end); + showBoard(); + return status; } - private void showBoard(Board board) { - List positions = Position.ALL_POSITIONS; - Map boardDto = new HashMap<>(); - positions.forEach(position -> addPiece(board, position, boardDto)); - outputView.printBoard(boardDto); + private ProgressStatus executeStatus() { + Map statusDto = chessService.calculatePiecePoints(); + outputView.printStatus(statusDto); + return ProgressStatus.PROGRESS; } - private void addPiece(Board board, Position position, Map boardDto) { - Optional optionalPiece = board.find(position); - - if (optionalPiece.isEmpty()) { + private void showResult(ProgressStatus status) { + if (status.isInputEndCommand()) { return; } + outputView.printWinnerMessage(status); + } - Piece piece = optionalPiece.get(); - PieceDto pieceDto = PieceDto.from(piece); - boardDto.put(position, pieceDto); + private void showBoard() { + Map boardDto = chessService.findTotalBoard(); + outputView.printBoard(boardDto); } } diff --git a/src/main/java/chess/ChessService.java b/src/main/java/chess/ChessService.java new file mode 100644 index 00000000000..0b353f1e42f --- /dev/null +++ b/src/main/java/chess/ChessService.java @@ -0,0 +1,121 @@ +package chess; + +import chess.dao.ChessDao; +import chess.dao.PieceEntity; +import chess.domain.Board; +import chess.domain.BoardFactory; +import chess.domain.Point; +import chess.domain.Team; +import chess.domain.piece.Piece; +import chess.domain.position.Position; +import chess.dto.PieceDto; +import chess.dto.ProgressStatus; +import chess.dto.TurnType; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public class ChessService { + + private Board board; + private final ChessDao chessDao; + + public ChessService(ChessDao chessDao) { + this.chessDao = chessDao; + } + + public void init() { + validateInitState(); + if (chessDao.isExistSavingGame()) { + List pieceEntities = chessDao.findAllPieces(); + TurnType turn = chessDao.findCurrentTurn(); + board = toBoard(pieceEntities, turn); + return; + } + board = BoardFactory.createInitBoard(); + saveBoard(); + } + + private void validateInitState() { + if (board != null) { + throw new IllegalStateException("보드가 이미 초기화 되었습니다."); + } + } + + private Board toBoard(List pieceEntities, TurnType turnType) { + Map board = pieceEntities.stream() + .filter(PieceEntity::isExistPiece) + .collect(Collectors.toMap( + PieceEntity::getPosition, + PieceEntity::toPiece + )); + Team turn = turnType.getTeam(); + return new Board(board, turn); + } + + private void saveBoard() { + List entities = findEntities(); + TurnType turn = TurnType.from(board.findCurrentTurn()); + chessDao.saveBoard(entities, turn); + } + + private List findEntities() { + return Position.ALL_POSITIONS.stream() + .map(this::findPieceToEntity) + .toList(); + } + + public ProgressStatus moveTo(Position start, Position end) { + ProgressStatus progressStatus = board.move(start, end); + + if (progressStatus.isContinue()) { + saveMoving(start, end); + return progressStatus; + } + chessDao.deleteAll(); + return progressStatus; + } + + private void saveMoving(Position start, Position end) { + PieceEntity movedPiece = findPieceToEntity(end); + TurnType turnType = TurnType.from(board.findCurrentTurn()); + chessDao.saveMoving(movedPiece, start, turnType); + } + + public Map findTotalBoard() { + return Position.ALL_POSITIONS.stream() + .map(this::toResultEntry) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + } + + private Entry toResultEntry(Position position) { + PieceDto pieceDto = board.find(position) + .map(PieceDto::from) + .orElse(PieceDto.createEmptyPiece()); + return Map.entry(position, pieceDto); + } + + private PieceEntity findPieceToEntity(Position position) { + return board.find(position) + .map(piece -> new PieceEntity(position, piece)) + .orElse(PieceEntity.createEmptyPiece(position)); + } + + public Map calculatePiecePoints() { + Map status = board.calculateTotalPoints(); + return toDto(status); + } + + private Map toDto(Map status) { + return status.entrySet().stream() + .collect(Collectors.toMap( + Entry::getKey, + entry -> entry.getValue().toDouble() + )); + } + + public Team findCurrentTurn() { + return board.findCurrentTurn(); + } +} diff --git a/src/main/java/chess/dao/ChessDao.java b/src/main/java/chess/dao/ChessDao.java new file mode 100644 index 00000000000..cc22f51b8f3 --- /dev/null +++ b/src/main/java/chess/dao/ChessDao.java @@ -0,0 +1,46 @@ +package chess.dao; + +import chess.domain.position.Position; +import chess.dto.TurnType; +import java.util.List; + +public class ChessDao { + + private final ChessGameRepository chessGameRepository; + private final PieceRepository pieceRepository; + + public ChessDao(ChessGameRepository chessGameRepository, PieceRepository pieceRepository) { + this.chessGameRepository = chessGameRepository; + this.pieceRepository = pieceRepository; + } + + public boolean isExistSavingGame() { + return chessGameRepository.isExistGame(); + } + + public TurnType findCurrentTurn() { + return chessGameRepository.find(); + } + + public List findAllPieces() { + return pieceRepository.findAll(); + } + + public void saveBoard(List pieces, TurnType currentTurn) { + pieceRepository.saveAll(pieces); + chessGameRepository.update(currentTurn); + } + + public void saveMoving(PieceEntity piece, Position previous, TurnType currentTurn) { + PieceEntity emptyPiece = PieceEntity.createEmptyPiece(previous); + + pieceRepository.update(piece); + pieceRepository.update(emptyPiece); + chessGameRepository.update(currentTurn); + } + + public void deleteAll() { + pieceRepository.deleteAll(); + chessGameRepository.deleteAll(); + } +} diff --git a/src/main/java/chess/dao/ChessGameRepository.java b/src/main/java/chess/dao/ChessGameRepository.java new file mode 100644 index 00000000000..50b2787f38e --- /dev/null +++ b/src/main/java/chess/dao/ChessGameRepository.java @@ -0,0 +1,14 @@ +package chess.dao; + +import chess.dto.TurnType; + +public interface ChessGameRepository { + + boolean isExistGame(); + + TurnType find(); + + void update(TurnType turn); + + void deleteAll(); +} diff --git a/src/main/java/chess/dao/ConnectionManager.java b/src/main/java/chess/dao/ConnectionManager.java new file mode 100644 index 00000000000..fbdf79b2f7e --- /dev/null +++ b/src/main/java/chess/dao/ConnectionManager.java @@ -0,0 +1,31 @@ +package chess.dao; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class ConnectionManager { + + private static final String SERVER = "localhost:13306"; + private static final String DATABASE = "chess"; + private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; + + private static final String URL = "jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION; + private static final String USERNAME = "root"; + private static final String PASSWORD = "root"; + + private static final Connection SINGLE_CONNECTION = createConnection(); + + private static Connection createConnection() { + try { + return DriverManager.getConnection(URL, USERNAME, PASSWORD); + } catch (final SQLException e) { + System.err.println("DB 연결 오류:" + e.getMessage()); + throw new IllegalStateException("DB와 연결할 수 없습니다.", e); + } + } + + public Connection getConnection() { + return SINGLE_CONNECTION; + } +} diff --git a/src/main/java/chess/dao/MysqlChessGameRepository.java b/src/main/java/chess/dao/MysqlChessGameRepository.java new file mode 100644 index 00000000000..f31cc2774f2 --- /dev/null +++ b/src/main/java/chess/dao/MysqlChessGameRepository.java @@ -0,0 +1,81 @@ +package chess.dao; + +import chess.dto.TurnType; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +public class MysqlChessGameRepository implements ChessGameRepository { + + private static final Map TURN_TO_STING = Map.of( + TurnType.BLACK, "BLACK", TurnType.WHITE, "WHITE"); + private static final Map STRING_TO_TURN = Map.of( + "BLACK", TurnType.BLACK, "WHITE", TurnType.WHITE); + + private final ConnectionManager connectionManager; + + public MysqlChessGameRepository(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Override + public boolean isExistGame() { + return countRow() >= 1; + } + + private int countRow() { + Connection connection = connectionManager.getConnection(); + String query = "SELECT COUNT(*) FROM chess_game"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + resultSet.next(); + return resultSet.getInt(1); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + @Override + public TurnType find() { + Connection connection = connectionManager.getConnection(); + String query = "SELECT turn FROM chess_game"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + resultSet.next(); + String turn = resultSet.getString("turn"); + return STRING_TO_TURN.get(turn); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + @Override + public void update(TurnType turn) { + deleteAll(); + save(turn); + } + + private void save(TurnType team) { + Connection connection = connectionManager.getConnection(); + String query = "INSERT INTO chess_game (turn) VALUES (?)"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setString(1, TURN_TO_STING.get(team)); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + @Override + public void deleteAll() { + Connection connection = connectionManager.getConnection(); + String query = "DELETE FROM chess_game;"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/main/java/chess/dao/MysqlPieceRepository.java b/src/main/java/chess/dao/MysqlPieceRepository.java new file mode 100644 index 00000000000..a77984187cd --- /dev/null +++ b/src/main/java/chess/dao/MysqlPieceRepository.java @@ -0,0 +1,151 @@ +package chess.dao; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.PieceType; +import chess.dto.TeamType; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MysqlPieceRepository implements PieceRepository { + + private static final Map FILE_TO_STRING = Map.of( + File.A, "A", File.B, "B", File.C, "C", File.D, "D", + File.E, "E", File.F, "F", File.G, "G", File.H, "H"); + private static final Map STRING_TO_FILE = Map.of( + "A", File.A, "B", File.B, "C", File.C, "D", File.D, + "E", File.E, "F", File.F, "G", File.G, "H", File.H); + private static final Map RANK_TO_STRING = Map.of( + Rank.ONE, "1", Rank.TWO, "2", Rank.THREE, "3", Rank.FOUR, "4", + Rank.FIVE, "5", Rank.SIX, "6", Rank.SEVEN, "7", Rank.EIGHT, "8"); + private static final Map STRING_TO_RANK = Map.of( + "1", Rank.ONE, "2", Rank.TWO, "3", Rank.THREE, "4", Rank.FOUR, + "5", Rank.FIVE, "6", Rank.SIX, "7", Rank.SEVEN, "8", Rank.EIGHT); + private static final Map PIECE_TYPE_TO_STRING = Map.of( + PieceType.KING, "KING", PieceType.QUEEN, "QUEEN", PieceType.ROOK, "ROOK", PieceType.BISHOP, "BISHOP", + PieceType.KNIGHT, "KNIGHT", PieceType.PAWN, "PAWN", PieceType.EMPTY, "EMPTY"); + private static final Map STRING_TO_PIECE_TYPE = Map.of( + "KING", PieceType.KING, "QUEEN", PieceType.QUEEN, "ROOK", PieceType.ROOK, "BISHOP", PieceType.BISHOP, + "KNIGHT", PieceType.KNIGHT, "PAWN", PieceType.PAWN, "EMPTY", PieceType.EMPTY); + private static final Map TEAM_TYPE_TO_STRING = Map.of( + TeamType.WHITE, "WHITE", TeamType.BLACK, "BLACK", TeamType.EMPTY, "EMPTY"); + private static final Map STRING_TO_TEAM_TYPE = Map.of( + "WHITE", TeamType.WHITE, "BLACK", TeamType.BLACK, "EMPTY", TeamType.EMPTY); + + private final ConnectionManager connectionManager; + + public MysqlPieceRepository(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + @Override + public List findAll() { + Connection connection = connectionManager.getConnection(); + String query = "SELECT board_file, board_rank, type, team FROM pieces"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + ResultSet resultSet = preparedStatement.executeQuery(); + return toPieceEntities(resultSet); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private List toPieceEntities(ResultSet resultSet) throws SQLException { + List result = new ArrayList<>(); + while (resultSet.next()) { + PieceEntity pieceEntity = toPieceEntity(resultSet); + result.add(pieceEntity); + } + return result; + } + + private PieceEntity toPieceEntity(ResultSet resultSet) throws SQLException { + String fileString = resultSet.getString("board_file"); + String rankString = resultSet.getString("board_rank"); + String pieceString = resultSet.getString("type"); + String teamString = resultSet.getString("team"); + + File file = STRING_TO_FILE.get(fileString); + Rank rank = STRING_TO_RANK.get(rankString); + Position position = new Position(file, rank); + PieceType pieceType = STRING_TO_PIECE_TYPE.get(pieceString); + TeamType teamType = STRING_TO_TEAM_TYPE.get(teamString); + + return new PieceEntity(position, pieceType, teamType); + } + + @Override + public List saveAll(List pieces) { + Connection connection = connectionManager.getConnection(); + String query = "INSERT INTO pieces(board_file, board_rank, type, team) VALUES (?, ?, ?, ?)"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + addBatch(pieces, preparedStatement); + preparedStatement.executeBatch(); + return pieces; + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private void addBatch(List pieces, PreparedStatement preparedStatement) throws SQLException { + for (PieceEntity entity : pieces) { + setPreparedStatementForInsert(entity, preparedStatement); + preparedStatement.addBatch(); + } + } + + private void setPreparedStatementForInsert(PieceEntity entity, PreparedStatement preparedStatement) + throws SQLException { + String fileString = FILE_TO_STRING.get(entity.getFile()); + String rankString = RANK_TO_STRING.get(entity.getRank()); + String pieceString = PIECE_TYPE_TO_STRING.get(entity.getPieceType()); + String teamString = TEAM_TYPE_TO_STRING.get(entity.getTeamType()); + + preparedStatement.setString(1, fileString); + preparedStatement.setString(2, rankString); + preparedStatement.setString(3, pieceString); + preparedStatement.setString(4, teamString); + } + + @Override + public void update(PieceEntity piece) { + Connection connection = connectionManager.getConnection(); + String query = "UPDATE pieces SET type = ?, team = ? WHERE board_file = ? AND board_rank = ?"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + setPreparedStatementForUpdate(piece, preparedStatement); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } + + private void setPreparedStatementForUpdate(PieceEntity entity, PreparedStatement preparedStatement) + throws SQLException { + String pieceString = PIECE_TYPE_TO_STRING.get(entity.getPieceType()); + String teamString = TEAM_TYPE_TO_STRING.get(entity.getTeamType()); + String fileString = FILE_TO_STRING.get(entity.getFile()); + String rankString = RANK_TO_STRING.get(entity.getRank()); + + preparedStatement.setString(1, pieceString); + preparedStatement.setString(2, teamString); + preparedStatement.setString(3, fileString); + preparedStatement.setString(4, rankString); + } + + @Override + public void deleteAll() { + Connection connection = connectionManager.getConnection(); + String query = "DELETE FROM pieces"; + try (PreparedStatement preparedStatement = connection.prepareStatement(query)) { + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/main/java/chess/dao/PieceEntity.java b/src/main/java/chess/dao/PieceEntity.java new file mode 100644 index 00000000000..df895400eb4 --- /dev/null +++ b/src/main/java/chess/dao/PieceEntity.java @@ -0,0 +1,62 @@ +package chess.dao; + +import chess.domain.Team; +import chess.domain.piece.Piece; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.PieceType; +import chess.dto.TeamType; + +public class PieceEntity { + + private final Position position; + private final PieceType pieceType; + private final TeamType teamType; + + public PieceEntity(Position position, Piece piece) { + this(position, PieceType.from(piece), TeamType.from(piece.getTeam())); + } + + public PieceEntity(Position position, PieceType pieceType, TeamType teamType) { + this.position = position; + this.pieceType = pieceType; + this.teamType = teamType; + } + + public static PieceEntity createEmptyPiece(Position position) { + PieceType pieceType = PieceType.getEmptyType(); + TeamType teamType = TeamType.getEmptyType(); + + return new PieceEntity(position, pieceType, teamType); + } + + public Piece toPiece() { + Team team = teamType.getTeam(); + return pieceType.createPiece(team); + } + + public boolean isExistPiece() { + return !pieceType.isEmpty(); + } + + public Rank getRank() { + return position.getRank(); + } + + public File getFile() { + return position.getFile(); + } + + public Position getPosition() { + return position; + } + + public PieceType getPieceType() { + return pieceType; + } + + public TeamType getTeamType() { + return teamType; + } +} diff --git a/src/main/java/chess/dao/PieceRepository.java b/src/main/java/chess/dao/PieceRepository.java new file mode 100644 index 00000000000..bae3453f32d --- /dev/null +++ b/src/main/java/chess/dao/PieceRepository.java @@ -0,0 +1,14 @@ +package chess.dao; + +import java.util.List; + +public interface PieceRepository { + + List findAll(); + + List saveAll(List pieces); + + void update(PieceEntity piece); + + void deleteAll(); +} diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java index 2e31f5249e8..3cd1459b06a 100644 --- a/src/main/java/chess/domain/Board.java +++ b/src/main/java/chess/domain/Board.java @@ -1,20 +1,29 @@ package chess.domain; import chess.domain.piece.Piece; +import chess.domain.position.File; import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.ProgressStatus; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; public class Board { private final Map board; private Team turn; - public Board(Map board) { + public Board(Map board, Team turn) { this.board = new HashMap<>(board); - this.turn = Team.WHITE; + this.turn = turn; + } + + public Board(Map board) { + this(board, Team.WHITE); } public Optional find(Position position) { @@ -22,18 +31,16 @@ public Optional find(Position position) { return Optional.ofNullable(piece); } - public void move(Position start, Position end) { + public ProgressStatus move(Position start, Position end) { validate(start, end); Piece piece = board.get(start); List path = piece.findPath(start, end, isExistEnemy(end)); validateEmpty(path); - board.remove(start); - board.put(end, piece); - turn = turn.nextTurn(); + return movePiece(start, end); } - public void validate(Position start, Position end) { + private void validate(Position start, Position end) { if (isNotExistPiece(start)) { throw new IllegalArgumentException("해당 위치에 말이 없습니다."); } @@ -72,11 +79,76 @@ private boolean isBlocked(List path) { .anyMatch(this::isExistPiece); } + private ProgressStatus movePiece(Position start, Position end) { + Piece movingPiece = find(start).orElseThrow(); + ProgressStatus status = findStatus(end); + + board.remove(start); + board.put(end, movingPiece); + turn = turn.nextTurn(); + + return status; + } + + private ProgressStatus findStatus(Position end) { + boolean isKingCaptured = find(end).map(Piece::isKing) + .orElse(false); + + if (!isKingCaptured) { + return ProgressStatus.PROGRESS; + } + if (turn.isBlack()) { + return ProgressStatus.BLACK_WIN; + } + return ProgressStatus.WHITE_WIN; + } + private boolean isExistPiece(Position position) { - return board.get(position) != null; + return board.containsKey(position); } private boolean isNotExistPiece(Position position) { return !isExistPiece(position); } + + public Map calculateTotalPoints() { + return Arrays.stream(Team.values()) + .collect(Collectors.toMap( + team -> team, + this::calculateTotalPoints + )); + } + + private Point calculateTotalPoints(Team team) { + return Arrays.stream(File.values()) + .map(file -> calculatePoints(team, file)) + .reduce(Point.ZERO, Point::add); + } + + private Point calculatePoints(Team team, File file) { + List sameFilePieces = Arrays.stream(Rank.values()) + .map(rank -> new Position(file, rank)) + .map(this::find) + .flatMap(Optional::stream) + .filter(piece -> piece.isSameTeam(team)) + .toList(); + return calculatePoints(sameFilePieces); + } + + private Point calculatePoints(List teamPieces) { + boolean isPawnOverlappedInFilePawn = isOverlappedPawn(teamPieces); + return teamPieces.stream() + .map(piece -> piece.getPoint(isPawnOverlappedInFilePawn)) + .reduce(Point.ZERO, Point::add); + } + + private boolean isOverlappedPawn(List pieces) { + return pieces.stream() + .filter(Piece::isPawn) + .count() >= 2; + } + + public Team findCurrentTurn() { + return turn; + } } diff --git a/src/main/java/chess/domain/Point.java b/src/main/java/chess/domain/Point.java new file mode 100644 index 00000000000..2b7d899e360 --- /dev/null +++ b/src/main/java/chess/domain/Point.java @@ -0,0 +1,46 @@ +package chess.domain; + +import java.util.Objects; + +public class Point { + + public static final Point ZERO = new Point(0); + + private final double value; + + public Point(double value) { + validate(value); + this.value = value; + } + + private void validate(double value) { + if (value < 0) { + throw new IllegalArgumentException("점수로 음수를 가질 수 없습니다."); + } + } + + public Point add(Point other) { + return new Point(this.value + other.value); + } + + public double toDouble() { + return value; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + Point point = (Point) object; + return Double.compare(value, point.value) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/chess/domain/Team.java b/src/main/java/chess/domain/Team.java index 938bdc55409..89dddda6a7f 100644 --- a/src/main/java/chess/domain/Team.java +++ b/src/main/java/chess/domain/Team.java @@ -2,14 +2,16 @@ public enum Team { - BLACK, WHITE; + BLACK, + WHITE, + ; public boolean isBlack() { return this == BLACK; } public Team nextTurn() { - if (this == BLACK) { + if (this.isBlack()) { return WHITE; } return BLACK; diff --git a/src/main/java/chess/domain/movement/BlackPawnDefaultMovement.java b/src/main/java/chess/domain/movement/BlackPawnDefaultMovement.java deleted file mode 100644 index 8ea827b1f8f..00000000000 --- a/src/main/java/chess/domain/movement/BlackPawnDefaultMovement.java +++ /dev/null @@ -1,27 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import java.util.List; - -public final class BlackPawnDefaultMovement implements MovementRule { - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isEmptyAtEnd = !isAttack; - boolean isMatchDifference = rankDifference == -1 && fileDifference == 0; - - return isEmptyAtEnd && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - return List.of(end); - } -} diff --git a/src/main/java/chess/domain/movement/BlackPawnDiagonalMovement.java b/src/main/java/chess/domain/movement/BlackPawnDiagonalMovement.java deleted file mode 100644 index e8c940e8a64..00000000000 --- a/src/main/java/chess/domain/movement/BlackPawnDiagonalMovement.java +++ /dev/null @@ -1,25 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import java.util.List; - -public final class BlackPawnDiagonalMovement implements MovementRule { - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isMatchDifference = rankDifference == -1 && Math.abs(fileDifference) == 1; - return isAttack && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - return List.of(end); - } -} diff --git a/src/main/java/chess/domain/movement/BlackPawnFirstMovement.java b/src/main/java/chess/domain/movement/BlackPawnFirstMovement.java deleted file mode 100644 index 8c27e09b904..00000000000 --- a/src/main/java/chess/domain/movement/BlackPawnFirstMovement.java +++ /dev/null @@ -1,32 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import chess.domain.position.Rank; -import java.util.List; - -public final class BlackPawnFirstMovement implements MovementRule { - - private static final Rank BLACK_PAWN_INIT_RANK = Rank.SEVEN; - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isEmptyAtEnd = !isAttack; - boolean isExistInitPosition = start.isSameRank(BLACK_PAWN_INIT_RANK); - boolean isMatchDifference = rankDifference == -2 && fileDifference == 0; - - return isEmptyAtEnd && isExistInitPosition && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - Position middle = start.moveToSouth(); - return List.of(middle, end); - } -} diff --git a/src/main/java/chess/domain/movement/WhitePawnDefaultMovement.java b/src/main/java/chess/domain/movement/WhitePawnDefaultMovement.java deleted file mode 100644 index e2c1969d012..00000000000 --- a/src/main/java/chess/domain/movement/WhitePawnDefaultMovement.java +++ /dev/null @@ -1,27 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import java.util.List; - -public final class WhitePawnDefaultMovement implements MovementRule { - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isEmptyAtEnd = !isAttack; - boolean isMatchDifference = rankDifference == 1 && fileDifference == 0; - - return isEmptyAtEnd && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - return List.of(end); - } -} diff --git a/src/main/java/chess/domain/movement/WhitePawnDiagonalMovement.java b/src/main/java/chess/domain/movement/WhitePawnDiagonalMovement.java deleted file mode 100644 index 751de051131..00000000000 --- a/src/main/java/chess/domain/movement/WhitePawnDiagonalMovement.java +++ /dev/null @@ -1,25 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import java.util.List; - -public final class WhitePawnDiagonalMovement implements MovementRule { - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isMatchDifference = rankDifference == 1 && Math.abs(fileDifference) == 1; - return isAttack && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - return List.of(end); - } -} diff --git a/src/main/java/chess/domain/movement/WhitePawnFirstMovement.java b/src/main/java/chess/domain/movement/WhitePawnFirstMovement.java deleted file mode 100644 index 40e6df668cd..00000000000 --- a/src/main/java/chess/domain/movement/WhitePawnFirstMovement.java +++ /dev/null @@ -1,32 +0,0 @@ -package chess.domain.movement; - -import chess.domain.position.Position; -import chess.domain.position.Rank; -import java.util.List; - -public final class WhitePawnFirstMovement implements MovementRule { - - private static final Rank WHITE_PAWN_INIT_RANK = Rank.TWO; - - @Override - public boolean isMovable(Position start, Position end, boolean isAttack) { - int rankDifference = start.calculateRankDifference(end); - int fileDifference = start.calculateFileDifference(end); - - boolean isEmptyAtEnd = !isAttack; - boolean isExistInitPosition = start.isSameRank(WHITE_PAWN_INIT_RANK); - boolean isMatchDifference = rankDifference == 2 && fileDifference == 0; - - return isEmptyAtEnd && isExistInitPosition && isMatchDifference; - } - - @Override - public List findPath(Position start, Position end, boolean isAttack) { - if (!isMovable(start, end, isAttack)) { - throw new IllegalArgumentException("경로가 존재하지 않습니다."); - } - - Position middle = start.moveToNorth(); - return List.of(middle, end); - } -} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java new file mode 100644 index 00000000000..ea749607c76 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.position.Position; +import java.util.List; + +public final class BlackPawnDefaultMovement extends PawnForwardMovement { + + private static final int TO_SOUTH_ONE_BLOCK = -1; + private static final int SAME_FILE = 0; + + @Override + protected boolean isMovablePath(int rankDifference, int fileDifference) { + return (rankDifference == TO_SOUTH_ONE_BLOCK && fileDifference == SAME_FILE); + } + + @Override + protected boolean isSatisfyStart(Position start) { + return true; + } + + @Override + protected List findPath(Position start, Position end) { + return List.of(end); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java new file mode 100644 index 00000000000..fc3b85f5187 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java @@ -0,0 +1,12 @@ +package chess.domain.movement.pawn; + +public final class BlackPawnDiagonalMovement extends PawnDiagonalMovement { + + private static final int TO_SOUTH_ONE_BLOCK = -1; + private static final int ONE_BLOCK = 1; + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return (rankDifference == TO_SOUTH_ONE_BLOCK && Math.abs(fileDifference) == ONE_BLOCK); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java new file mode 100644 index 00000000000..60a828d3569 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java @@ -0,0 +1,28 @@ +package chess.domain.movement.pawn; + +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class BlackPawnFirstMovement extends PawnForwardMovement { + + private static final Rank BLACK_PAWN_INIT_RANK = Rank.SEVEN; + private static final int TO_SOUTH_TWO_BLOCK = -2; + private static final int SAME_FILE = 0; + + @Override + protected boolean isMovablePath(int rankDifference, int fileDifference) { + return (rankDifference == TO_SOUTH_TWO_BLOCK && fileDifference == SAME_FILE); + } + + @Override + protected boolean isSatisfyStart(Position start) { + return start.isSameRank(BLACK_PAWN_INIT_RANK); + } + + @Override + protected List findPath(Position start, Position end) { + Position middle = start.moveToSouth(); + return List.of(middle, end); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/PawnDiagonalMovement.java b/src/main/java/chess/domain/movement/pawn/PawnDiagonalMovement.java new file mode 100644 index 00000000000..164e4815632 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/PawnDiagonalMovement.java @@ -0,0 +1,29 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class PawnDiagonalMovement implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean isAttack) { + if (!isAttack) { + return false; + } + + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + return isMovable(rankDifference, fileDifference); + } + + @Override + public final List findPath(Position start, Position end, boolean isAttack) { + if (!isMovable(start, end, isAttack)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + return List.of(end); + } + + protected abstract boolean isMovable(int rankDifference, int fileDifference); +} diff --git a/src/main/java/chess/domain/movement/pawn/PawnForwardMovement.java b/src/main/java/chess/domain/movement/pawn/PawnForwardMovement.java new file mode 100644 index 00000000000..df9f11fe7a2 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/PawnForwardMovement.java @@ -0,0 +1,33 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class PawnForwardMovement implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean isAttack) { + if (isAttack) { + return false; + } + + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + return isMovablePath(rankDifference, fileDifference) && isSatisfyStart(start); + } + + @Override + public final List findPath(Position start, Position end, boolean isAttack) { + if (!isMovable(start, end, isAttack)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + return findPath(start, end); + } + + protected abstract boolean isMovablePath(int rankDifference, int fileDifference); + + protected abstract boolean isSatisfyStart(Position start); + + protected abstract List findPath(Position start, Position end); +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java new file mode 100644 index 00000000000..400909e69ee --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.position.Position; +import java.util.List; + +public final class WhitePawnDefaultMovement extends PawnForwardMovement { + + private static final int TO_NORTH_ONE_BLOCK = 1; + private static final int SAME_FILE = 0; + + @Override + protected boolean isMovablePath(int rankDifference, int fileDifference) { + return (rankDifference == TO_NORTH_ONE_BLOCK && fileDifference == SAME_FILE); + } + + @Override + protected boolean isSatisfyStart(Position start) { + return true; + } + + @Override + protected List findPath(Position start, Position end) { + return List.of(end); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java new file mode 100644 index 00000000000..ed1811b208e --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java @@ -0,0 +1,12 @@ +package chess.domain.movement.pawn; + +public final class WhitePawnDiagonalMovement extends PawnDiagonalMovement { + + private static final int TO_NORTH_ONE_BLOCK = 1; + private static final int ONE_BLOCK = 1; + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return (rankDifference == TO_NORTH_ONE_BLOCK && Math.abs(fileDifference) == ONE_BLOCK); + } +} diff --git a/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java new file mode 100644 index 00000000000..202a6ad0865 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java @@ -0,0 +1,28 @@ +package chess.domain.movement.pawn; + +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class WhitePawnFirstMovement extends PawnForwardMovement { + + private static final Rank WHITE_PAWN_INIT_RANK = Rank.TWO; + private static final int TO_NORTH_TWO_BLOCK = 2; + private static final int SAME_FILE = 0; + + @Override + protected boolean isMovablePath(int rankDifference, int fileDifference) { + return (rankDifference == TO_NORTH_TWO_BLOCK && fileDifference == SAME_FILE); + } + + @Override + protected boolean isSatisfyStart(Position start) { + return start.isSameRank(WHITE_PAWN_INIT_RANK); + } + + @Override + protected List findPath(Position start, Position end) { + Position middle = start.moveToNorth(); + return List.of(middle, end); + } +} diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 12a9c016bbf..a468dfb7811 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,5 +1,6 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.movement.continuous.NorthEastMovement; @@ -10,10 +11,17 @@ public final class Bishop extends Piece { + private static final Point PIECE_POINT = new Point(3.0); private static final List MOVEMENT_RULES = List.of( new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); public Bishop(Team team) { super(team, MOVEMENT_RULES); } + + + @Override + public Point getPoint(boolean isPawnOverlappedInFile) { + return PIECE_POINT; + } } diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 769a5e69cb5..de38d1235ec 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -1,5 +1,6 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.movement.discrete.KingMovement; @@ -8,8 +9,14 @@ public final class King extends Piece { private static final List MOVEMENT_RULES = List.of(new KingMovement()); + private static final Point PIECE_POINT = Point.ZERO; public King(Team team) { super(team, MOVEMENT_RULES); } + + @Override + public Point getPoint(boolean isPawnOverlappedInFile) { + return PIECE_POINT; + } } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index bfcefe0eb64..35a3f427e9d 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,5 +1,6 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.movement.discrete.KnightMovement; @@ -8,8 +9,14 @@ public final class Knight extends Piece { private static final List MOVEMENT_RULES = List.of(new KnightMovement()); + private static final Point PIECE_POINT = new Point(2.5); public Knight(Team team) { super(team, MOVEMENT_RULES); } + + @Override + public Point getPoint(boolean isPawnOverlappedIn) { + return PIECE_POINT; + } } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index 4a1214a2da4..054c9f442de 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,13 +1,14 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; -import chess.domain.movement.BlackPawnDefaultMovement; -import chess.domain.movement.BlackPawnDiagonalMovement; -import chess.domain.movement.BlackPawnFirstMovement; import chess.domain.movement.MovementRule; -import chess.domain.movement.WhitePawnDefaultMovement; -import chess.domain.movement.WhitePawnDiagonalMovement; -import chess.domain.movement.WhitePawnFirstMovement; +import chess.domain.movement.pawn.BlackPawnDefaultMovement; +import chess.domain.movement.pawn.BlackPawnDiagonalMovement; +import chess.domain.movement.pawn.BlackPawnFirstMovement; +import chess.domain.movement.pawn.WhitePawnDefaultMovement; +import chess.domain.movement.pawn.WhitePawnDiagonalMovement; +import chess.domain.movement.pawn.WhitePawnFirstMovement; import java.util.List; public final class Pawn extends Piece { @@ -16,6 +17,9 @@ public final class Pawn extends Piece { new BlackPawnFirstMovement(), new BlackPawnDefaultMovement(), new BlackPawnDiagonalMovement()); private static final List WHITE_MOVEMENT_RULES = List.of( new WhitePawnFirstMovement(), new WhitePawnDefaultMovement(), new WhitePawnDiagonalMovement()); + private static final Point PIECE_BASIC_POINT = new Point(1.0); + private static final Point PIECE_OVERLAPPED_POINT = new Point(0.5); // TODO 변수명 변경 + public Pawn(Team team) { super(team, findMovementRule(team)); @@ -27,4 +31,12 @@ private static List findMovementRule(Team team) { } return WHITE_MOVEMENT_RULES; } + + @Override + public Point getPoint(boolean isPawnOverlappedInFile) { + if (isPawnOverlappedInFile) { + return PIECE_OVERLAPPED_POINT; + } + return PIECE_BASIC_POINT; + } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 3728507395b..d2b00c7f462 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -1,11 +1,12 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.position.Position; import java.util.List; -public abstract class Piece { +public abstract sealed class Piece permits King, Queen, Rook, Bishop, Knight, Pawn { private final Team team; private final List movementRules; @@ -15,10 +16,6 @@ protected Piece(Team team, List movementRules) { this.movementRules = movementRules; } - public final boolean isBlackTeam() { - return team.isBlack(); - } - public final List findPath(Position start, Position end, boolean isAttack) { return movementRules.stream() .filter(movementRule -> movementRule.isMovable(start, end, isAttack)) @@ -34,4 +31,18 @@ public final boolean isSameTeam(Piece other) { public final boolean isSameTeam(Team team) { return this.team == team; } + + public final boolean isKing() { + return this.getClass() == King.class; + } + + public final boolean isPawn() { + return this.getClass() == Pawn.class; + } + + public Team getTeam() { + return team; + } + + public abstract Point getPoint(boolean isPawnOverlappedInFile); } diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 8c0c21c252d..4120e8d2efd 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -1,5 +1,6 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.movement.continuous.EastMovement; @@ -14,6 +15,8 @@ public final class Queen extends Piece { + private static final Point PIECE_POINT = new Point(9.0); + private static final List MOVEMENT_RULES = List.of( new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement(), new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); @@ -21,4 +24,9 @@ public final class Queen extends Piece { public Queen(Team team) { super(team, MOVEMENT_RULES); } + + @Override + public Point getPoint(boolean isPawnOverlappedInFile) { + return PIECE_POINT; + } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index dfc4e85024a..532c367a26e 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,5 +1,6 @@ package chess.domain.piece; +import chess.domain.Point; import chess.domain.Team; import chess.domain.movement.MovementRule; import chess.domain.movement.continuous.EastMovement; @@ -12,8 +13,14 @@ public final class Rook extends Piece { private static final List MOVEMENT_RULES = List.of( new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement()); + private static final Point PIECE_POINT = new Point(5.0); public Rook(Team team) { super(team, MOVEMENT_RULES); } + + @Override + public Point getPoint(boolean isPawnOverlappedInFile) { + return PIECE_POINT; + } } diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java index 8e6b99e1643..5dfebbfeb5d 100644 --- a/src/main/java/chess/domain/position/Position.java +++ b/src/main/java/chess/domain/position/Position.java @@ -47,6 +47,14 @@ public Position moveToSouth() { return new Position(file, rank.toSouth()); } + public File getFile() { + return file; + } + + public Rank getRank() { + return rank; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java index 24d76a8a4bb..a3a48fc3b5c 100644 --- a/src/main/java/chess/dto/PieceDto.java +++ b/src/main/java/chess/dto/PieceDto.java @@ -2,12 +2,20 @@ import chess.domain.piece.Piece; -public record PieceDto(PieceType type, boolean isBlack) { +public record PieceDto(PieceType pieceType, TeamType teamType) { public static PieceDto from(Piece piece) { - PieceType type = PieceType.from(piece); - boolean isBlackTeam = piece.isBlackTeam(); + PieceType pieceType = PieceType.from(piece); + TeamType teamType = TeamType.from(piece.getTeam()); - return new PieceDto(type, isBlackTeam); + return new PieceDto(pieceType, teamType); + } + + public static PieceDto createEmptyPiece() { + return new PieceDto(PieceType.getEmptyType(), TeamType.EMPTY); + } + + public boolean isBlack() { + return teamType.isBlackTeam(); } } diff --git a/src/main/java/chess/dto/PieceType.java b/src/main/java/chess/dto/PieceType.java index 9427ec01a70..1133ca76021 100644 --- a/src/main/java/chess/dto/PieceType.java +++ b/src/main/java/chess/dto/PieceType.java @@ -1,5 +1,6 @@ package chess.dto; +import chess.domain.Team; import chess.domain.piece.Bishop; import chess.domain.piece.King; import chess.domain.piece.Knight; @@ -8,20 +9,25 @@ import chess.domain.piece.Queen; import chess.domain.piece.Rook; import java.util.Arrays; +import java.util.function.Function; public enum PieceType { - KING(King.class), - QUEEN(Queen.class), - ROOK(Rook.class), - BISHOP(Bishop.class), - KNIGHT(Knight.class), - PAWN(Pawn.class); + KING(King.class, King::new), + QUEEN(Queen.class, Queen::new), + ROOK(Rook.class, Rook::new), + BISHOP(Bishop.class, Bishop::new), + KNIGHT(Knight.class, King::new), + PAWN(Pawn.class, Pawn::new), + EMPTY(null, null), + ; private final Class category; + private final Function pieceFunction; - PieceType(Class category) { + PieceType(Class category, Function pieceFunction) { this.category = category; + this.pieceFunction = pieceFunction; } public static PieceType from(Piece piece) { @@ -30,4 +36,19 @@ public static PieceType from(Piece piece) { .findAny() .orElseThrow(() -> new IllegalArgumentException("해당 기물이 존재하지 않습니다.")); } + + public static PieceType getEmptyType() { + return EMPTY; + } + + public Piece createPiece(Team team) { + if (isEmpty()) { + throw new IllegalStateException("빈 객체는 말을 만들 수 없습니다."); + } + return pieceFunction.apply(team); + } + + public boolean isEmpty() { + return this == EMPTY; + } } diff --git a/src/main/java/chess/dto/ProgressStatus.java b/src/main/java/chess/dto/ProgressStatus.java new file mode 100644 index 00000000000..154c21761c5 --- /dev/null +++ b/src/main/java/chess/dto/ProgressStatus.java @@ -0,0 +1,24 @@ +package chess.dto; + +public enum ProgressStatus { + + WHITE_WIN(false), + BLACK_WIN(false), + PROGRESS(true), + END_GAME(false), + ; + + private final boolean isContinue; + + ProgressStatus(boolean isContinue) { + this.isContinue = isContinue; + } + + public boolean isContinue() { + return isContinue; + } + + public boolean isInputEndCommand() { + return this == END_GAME; + } +} diff --git a/src/main/java/chess/dto/TeamType.java b/src/main/java/chess/dto/TeamType.java new file mode 100644 index 00000000000..890c80f490c --- /dev/null +++ b/src/main/java/chess/dto/TeamType.java @@ -0,0 +1,37 @@ +package chess.dto; + +import chess.domain.Team; +import java.util.Arrays; + +public enum TeamType { + + BLACK(Team.BLACK), + WHITE(Team.WHITE), + EMPTY(null), + ; + + private final Team team; + + TeamType(Team team) { + this.team = team; + } + + public static TeamType from(Team team) { + return Arrays.stream(TeamType.values()) + .filter(type -> type.team == team) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 타입이 없습니다.")); + } + + public static TeamType getEmptyType() { + return EMPTY; + } + + public Team getTeam() { + return team; + } + + public boolean isBlackTeam() { + return this == BLACK; + } +} diff --git a/src/main/java/chess/dto/TurnType.java b/src/main/java/chess/dto/TurnType.java new file mode 100644 index 00000000000..9b8d4e53638 --- /dev/null +++ b/src/main/java/chess/dto/TurnType.java @@ -0,0 +1,28 @@ +package chess.dto; + +import chess.domain.Team; +import java.util.Arrays; + +public enum TurnType { + + BLACK(Team.BLACK), + WHITE(Team.WHITE), + ; + + private final Team team; + + TurnType(Team team) { + this.team = team; + } + + public static TurnType from(Team team) { + return Arrays.stream(TurnType.values()) + .filter(teamType -> teamType.team == team) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 턴 타입이 없습니다.")); + } + + public Team getTeam() { + return team; + } +} diff --git a/src/main/java/chess/view/GameCommand.java b/src/main/java/chess/view/GameCommand.java index b099877116e..00826b02239 100644 --- a/src/main/java/chess/view/GameCommand.java +++ b/src/main/java/chess/view/GameCommand.java @@ -6,7 +6,9 @@ public enum GameCommand { START("start"), END("end"), - MOVE("move"); + MOVE("move"), + STATUS("status"), + ; private final String command; @@ -32,4 +34,8 @@ public boolean isEnd() { public boolean isMove() { return this == MOVE; } + + public boolean isStatus() { + return this == STATUS; + } } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 6ccd8dce259..44c9c0b77a4 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,13 +1,14 @@ package chess.view; +import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; import chess.domain.position.Rank; import chess.dto.PieceDto; import chess.dto.PieceType; +import chess.dto.ProgressStatus; import java.util.List; import java.util.Map; -import java.util.Optional; public class OutputView { @@ -15,9 +16,10 @@ public class OutputView { Rank.EIGHT, Rank.SEVEN, Rank.SIX, Rank.FIVE, Rank.FOUR, Rank.THREE, Rank.TWO, Rank.ONE); private static final List FILE_ORDER = List.of( File.A, File.B, File.C, File.D, File.E, File.F, File.G, File.H); + private static final List TEAM_ORDER = List.of(Team.WHITE, Team.BLACK); private static final Map PIECE_DISPLAY = Map.of( - PieceType.KING, "K", PieceType.QUEEN, "Q", PieceType.KNIGHT, "N", - PieceType.BISHOP, "B", PieceType.ROOK, "R", PieceType.PAWN, "P"); + PieceType.KING, "K", PieceType.QUEEN, "Q", PieceType.KNIGHT, "N", PieceType.BISHOP, "B", + PieceType.ROOK, "R", PieceType.PAWN, "P", PieceType.EMPTY, "."); private static final String EMPTY_SPACE = "."; private static final String ERROR_PREFIX = "[ERROR] "; @@ -26,31 +28,40 @@ public void printStartGame() { > 체스 게임을 시작합니다. > 게임 시작 : start > 게임 종료 : end - > 게임 이동 : move source위치 target위치 - 예. move b2 b3"""); + > 게임 이동 : move source위치 target위치 - 예. move b2 b3 + > 팀 별 기물 점수 조회 : status + """); + } + + public void printCurrentTurn(Team turn) { + if (turn.isBlack()) { + System.out.println("현재 검은 팀 차례입니다."); + return; + } + System.out.println("현재 흰 팀 차례입니다."); } public void printBoard(Map board) { + System.out.println(); for (Rank rank : RANK_ORDER) { printBoardOneLine(board, rank); } - System.out.println(); } private void printBoardOneLine(Map board, Rank rank) { for (File file : FILE_ORDER) { PieceDto piece = board.get(new Position(file, rank)); - Optional optional = Optional.ofNullable(piece); - optional.ifPresentOrElse(this::printPiece, () -> System.out.print(EMPTY_SPACE)); + printPiece(piece); } System.out.println(); } private void printPiece(PieceDto piece) { if (piece.isBlack()) { - printBlackPiece(piece.type()); + printBlackPiece(piece.pieceType()); return; } - printWhitePiece(piece.type()); + printWhitePiece(piece.pieceType()); } private void printBlackPiece(PieceType type) { @@ -63,6 +74,32 @@ private void printWhitePiece(PieceType type) { System.out.print(display.toLowerCase()); } + public void printStatus(Map status) { + System.out.println(); + TEAM_ORDER.stream() + .forEach(team -> printStatus(team, status.get(team))); + } + + private void printStatus(Team team, double score) { + if (team.isBlack()) { + System.out.printf("검정 팀 : %.1f%n", score); + return; + } + System.out.printf("하양 팀 : %.1f%n", score); + } + + public void printWinnerMessage(ProgressStatus status) { + if (status.isContinue()) { + throw new IllegalArgumentException("우승자가 결정되지 않았습니다."); + } + + if (status == ProgressStatus.BLACK_WIN) { + System.out.println("검정 팀이 승리했습니다."); + return; + } + System.out.println("하양 팀이 승리했습니다."); + } + public void printExceptionMessage(Exception exception) { System.out.println(ERROR_PREFIX + exception.getMessage()); } diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java index c239fe9a2b3..7a0be3ac880 100644 --- a/src/test/java/chess/domain/BoardTest.java +++ b/src/test/java/chess/domain/BoardTest.java @@ -5,12 +5,14 @@ import static org.junit.jupiter.api.Assertions.assertAll; import chess.domain.piece.King; +import chess.domain.piece.Pawn; import chess.domain.piece.Piece; import chess.domain.piece.Queen; import chess.domain.piece.Rook; import chess.domain.position.File; import chess.domain.position.Position; import chess.domain.position.Rank; +import chess.dto.ProgressStatus; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -40,17 +42,19 @@ void findTest_whenPieceNotExist() { Position notExistPosition = new Position(File.D, Rank.TWO); Board board = new Board(map); - assertThat(board.find(notExistPosition)).isEqualTo(Optional.empty()); + assertThat(board.find(notExistPosition)).isEmpty(); } /* - * .R...... - * ........ - * ........ - * .q.k.... - * ........ - * ........ - * ........ + * ........ 8 + * ........ 7 + * ........ 6 + * ........ 5 + * ....k... 4 + * ........ 3 + * K...q..R 2 + * ........ 1 + * abcdefgh * */ @Nested @DisplayName("말 이동 테스트") @@ -62,8 +66,10 @@ class MovingTest { private static final Queen QUEEN = new Queen(Team.WHITE); private static final Rook ENEMY_ROOK = new Rook(Team.BLACK); private static final Position START_ENEMY_ROOK = new Position(File.H, Rank.TWO); + private static final King ENEMY_KING = new King(Team.BLACK); + private static final Position START_ENEMY_KING = new Position(File.A, Rank.TWO); private static final Map MAP = Map.of( - START_KING, KING, START_QUEEN, QUEEN, START_ENEMY_ROOK, ENEMY_ROOK); + START_KING, KING, START_QUEEN, QUEEN, START_ENEMY_ROOK, ENEMY_ROOK, START_ENEMY_KING, ENEMY_KING); private Board board; @BeforeEach @@ -80,7 +86,7 @@ void moveTest() { assertAll( () -> assertThat(board.find(possibleEnd)).isEqualTo(Optional.of(KING)), - () -> assertThat(board.find(START_KING)).isEqualTo(Optional.empty()) + () -> assertThat(board.find(START_KING)).isEmpty() ); } @@ -129,7 +135,7 @@ void moveTest_whenExistSameTeamAtTheEnd() { board.move(START_QUEEN, START_ENEMY_ROOK); assertAll( - () -> assertThat(board.find(START_QUEEN)).isEqualTo(Optional.empty()), + () -> assertThat(board.find(START_QUEEN)).isEmpty(), () -> assertThat(board.find(START_ENEMY_ROOK)).isEqualTo(Optional.of(QUEEN)) ); } @@ -143,5 +149,96 @@ void moveTest_whenNotInTurn_throwException() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("해당 팀의 차례가 아닙니다."); } + + @Test + @DisplayName("상대편 왕을 잡을 경우, 해당 팀이 이긴다.") + void moveTest_whenCaptureKing_winGame() { + assertThat(board.move(START_QUEEN, START_ENEMY_KING)).isEqualTo(ProgressStatus.WHITE_WIN); + } + + @Test + @DisplayName("상대편 왕을 잡지 않은 경우, 게임이 진행된다.") + void moveTest_whenNotCaptureKing_progressGame() { + Position possiblePosition = new Position(File.E, Rank.THREE); + + assertThat(board.move(START_QUEEN, possiblePosition)).isEqualTo(ProgressStatus.PROGRESS); + } + } + + @Nested + @DisplayName("보드판에 있는 기물 점수를 계산할 수 있다.") + class CalculatingPointTest { + + /* + * ........ 8 + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ........ 2 + * r..q.... 1 + * abcdefgh + * */ + @Test + @DisplayName("팀 별로 각 기물의 점수를 더하여 계산한다.") + void calculatePointTest_whenNotExistPawn_addAll() { + Board board = new Board(Map.of( + new Position(File.A, Rank.ONE), new Rook(Team.WHITE), + new Position(File.D, Rank.ONE), new Queen(Team.WHITE))); + + assertThat(board.calculateTotalPoints()).containsOnly( + Map.entry(Team.WHITE, new Point(5.0 + 9.0)), + Map.entry(Team.BLACK, Point.ZERO)); + } + + /* + * ........ 8 + * ....P... 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ....pp.. 2 + * ........ 1 + * abcdefgh + * */ + @Test + @DisplayName("각 폰은 한 파일에 같이 없을 경우, 높은 점수로 계산한다.") + void calculatePointTest_whenExistPawnSameTeamAndDifferentFile_addHighPoint() { + Board board = new Board(Map.of( + new Position(File.E, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.SEVEN), new Pawn(Team.BLACK))); + + assertThat(board.calculateTotalPoints()).containsOnly( + Map.entry(Team.WHITE, new Point(1.0 + 1.0)), + Map.entry(Team.BLACK, new Point(1.0))); + } + + /* + * ........ 8 + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ....p... 3 + * ....p... 2 + * ........ 1 + * abcdefgh + * */ + @Test + @DisplayName("각 폰은 한 줄에 같이 있을 경우, 낮은 점수로 계산한다.") + void calculatePointTest_whenExistPawnSameTeamAndFile_addLowPoint() { + Board board = new Board(Map.of( + new Position(File.E, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.THREE), new Pawn(Team.WHITE))); + + assertThat(board.calculateTotalPoints()).containsOnly( + Map.entry(Team.WHITE, new Point(0.5 + 0.5)), + Map.entry(Team.BLACK, Point.ZERO)); + } + + } } diff --git a/src/test/java/chess/domain/PointTest.java b/src/test/java/chess/domain/PointTest.java new file mode 100644 index 00000000000..37eab36ed27 --- /dev/null +++ b/src/test/java/chess/domain/PointTest.java @@ -0,0 +1,49 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.offset; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class PointTest { + + @Test + @DisplayName("점수로 음수를 가질 경우, 예외를 던진다.") + void validateTest_whenPointsNegative_throwException() { + double negativeValue = -0.001; + + assertThatThrownBy(() -> new Point(negativeValue)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("점수로 음수를 가질 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"1.5, 0, 1.5", "1, 1, 2", "0.5, 0.5, 1"}) + @DisplayName("두 점수를 더할 수 있다.") + void addTest(double value1, double value2, double expected) { + Point point1 = new Point(value1); + Point point2 = new Point(value2); + + Point actual = point1.add(point2); + + assertThat(actual.toDouble()).isEqualTo(expected, offset(0.001)); + } + + @Test + @DisplayName("점수를 여러번 더할 수 있다.") + void addTest_whenAddRepeat() { + Point added = new Point(0.5); + int count = 100; + + Point actual = Point.ZERO; + for (int i = 0; i < count; i++) { + actual = actual.add(added); + } + + assertThat(actual.toDouble()).isEqualTo(0.5 * 100, offset(0.001)); + } +} diff --git a/src/test/java/chess/domain/movement/BlackPawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/BlackPawnDefaultMovementTest.java rename to src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java index 95275b96553..9e289613b80 100644 --- a/src/test/java/chess/domain/movement/BlackPawnDefaultMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/movement/BlackPawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/BlackPawnDiagonalMovementTest.java rename to src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java index 5c28d2af576..8b68cd4c821 100644 --- a/src/test/java/chess/domain/movement/BlackPawnDiagonalMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/movement/BlackPawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/BlackPawnFirstMovementTest.java rename to src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java index 6bbf3f6345a..97560f9e63e 100644 --- a/src/test/java/chess/domain/movement/BlackPawnFirstMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/movement/WhitePawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/WhitePawnDefaultMovementTest.java rename to src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java index 2fb4fc4d7e6..e694ec2f7a8 100644 --- a/src/test/java/chess/domain/movement/WhitePawnDefaultMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/movement/WhitePawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/WhitePawnDiagonalMovementTest.java rename to src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java index de9f459bd5e..4086ccf7539 100644 --- a/src/test/java/chess/domain/movement/WhitePawnDiagonalMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/movement/WhitePawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java similarity index 98% rename from src/test/java/chess/domain/movement/WhitePawnFirstMovementTest.java rename to src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java index 2b39acfb211..0a91d36d2c5 100644 --- a/src/test/java/chess/domain/movement/WhitePawnFirstMovementTest.java +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java @@ -1,4 +1,4 @@ -package chess.domain.movement; +package chess.domain.movement.pawn; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index e43f7a34140..428b539f896 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -12,6 +13,7 @@ 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.ValueSource; class BishopTest { @@ -55,4 +57,13 @@ void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("비숍의 기물 점수는 3점이다.") + void getPointTest(boolean isPawnOverlappedInFile) { + Bishop bishop = new Bishop(Team.WHITE); + + assertThat(bishop.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(3.0)); + } } diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java index 4d392f2cc0a..bb477b39dd3 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -10,6 +11,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class KingTest { @@ -38,4 +40,13 @@ void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("킹의 기물 점수는 0점이다.") + void getPointTest(boolean isPawnOverlappedInFile) { + King king = new King(Team.WHITE); + + assertThat(king.getPoint(isPawnOverlappedInFile)).isEqualTo(Point.ZERO); + } } diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java index e6677b57a6c..82f24b94ae3 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -10,6 +11,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; class KnightTest { @@ -38,4 +40,13 @@ void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("나이트의 기물 점수는 2.5점이다.") + void getPointTest(boolean isPawnOverlappedInFile) { + Knight knight = new Knight(Team.WHITE); + + assertThat(knight.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(2.5)); + } } diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java index 574b49a3683..adc0fc5783a 100644 --- a/src/test/java/chess/domain/piece/PawnTest.java +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -116,4 +117,22 @@ void findPathTest_whenOutOfDiagonalMovement_throwException(File file, Rank rank) .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @Test + @DisplayName("폰의 기물 점수는 1점이다.") + void getPointTest() { + Pawn pawn = new Pawn(Team.WHITE); + boolean isPawnOverlappedInFile = false; + + assertThat(pawn.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(1.0)); + } + + @Test + @DisplayName("같은 줄에 폰이 있을 경우, 폰의 기물 점수는 0.5점이다.") + void getPointTest_whenPawnisPawnOverlappedInFileInSameFile() { + Pawn pawn = new Pawn(Team.WHITE); + boolean isPawnOverlappedInFile = true; + + assertThat(pawn.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(0.5)); + } } diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java index a1b15e05297..4807bc78b4d 100644 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -3,34 +3,17 @@ import static org.assertj.core.api.Assertions.assertThat; import chess.domain.Team; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class PieceTest { - class ExamplePiece extends Piece { - - protected ExamplePiece(Team team) { - super(team, List.of()); - } - } - - @ParameterizedTest - @CsvSource({"BLACK, true", "WHITE, false"}) - @DisplayName("해당 팀이 검정 팀인지 확인한다.") - void isBlackTeamTest(Team team, boolean expected) { - ExamplePiece piece = new ExamplePiece(team); - - assertThat(piece.isBlackTeam()).isEqualTo(expected); - } - @ParameterizedTest @CsvSource({"BLACK, true", "WHITE, false"}) @DisplayName("기물이 해당 팀인지 확인한다.") void isSameTeamTest_whenOnePiece(Team pieceTeam, boolean expected) { - ExamplePiece piece = new ExamplePiece(pieceTeam); + Piece piece = new Pawn(pieceTeam); Team team = Team.BLACK; assertThat(piece.isSameTeam(team)).isEqualTo(expected); @@ -40,8 +23,8 @@ void isSameTeamTest_whenOnePiece(Team pieceTeam, boolean expected) { @CsvSource({"BLACK, true", "WHITE, false"}) @DisplayName("두 기물이 같은 팀인지 확인한다.") void isSameTeamTest_whenTwoPieces(Team team, boolean expected) { - ExamplePiece one = new ExamplePiece(Team.BLACK); - ExamplePiece other = new ExamplePiece(team); + Piece one = new Pawn(Team.BLACK); + Piece other = new Pawn(team); assertThat(one.isSameTeam(other)).isEqualTo(expected); } diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java index 5d939d92f38..ed7eb6a9c52 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -12,6 +13,7 @@ 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.ValueSource; class QueenTest { @@ -55,4 +57,13 @@ void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("퀸의 기물 점수는 9점이다.") + void getPointTest(boolean isPawnOverlappedInFile) { + Queen queen = new Queen(Team.WHITE); + + assertThat(queen.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(9.0)); + } } diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java index 28ee9b769a2..8bae4be6398 100644 --- a/src/test/java/chess/domain/piece/RookTest.java +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import chess.domain.Point; import chess.domain.Team; import chess.domain.position.File; import chess.domain.position.Position; @@ -12,6 +13,7 @@ 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.ValueSource; class RookTest { @@ -55,4 +57,13 @@ void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { .isInstanceOf(IllegalArgumentException.class) .hasMessage("불가능한 경로입니다."); } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("룩의 기물 점수는 5점이다.") + void getPointTest(boolean isPawnOverlappedInFile) { + Rook rook = new Rook(Team.WHITE); + + assertThat(rook.getPoint(isPawnOverlappedInFile)).isEqualTo(new Point(5.0)); + } }