diff --git a/.gitignore b/.gitignore index 6c018781387..68f20bee5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ out/ ### VS Code ### .vscode/ + +db/mysql/data/ diff --git a/README.md b/README.md index 8102f91c870..c1738367015 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,76 @@ # java-chess -체스 미션 저장소 +## 실행 방법 +`java-chess`에서 `docker-compose -p chess up -d` 명령어 입력 -## 우아한테크코스 코드리뷰 +## 페어와 지킬 컨벤션 +1. 클래스 정의 다음 줄은 공백으로 한다. +2. test code에 사용하는 메서드는 `static import`한다. +3. `this`는 같은 클래스의 객체가 파라미터로 넘어왔을 때, 파라미터 변수 명이 필드의 변수 명과 겹칠 때 사용한다. -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) +## 기능 요구 사항 + +### 체스 말 +- [x] 무슨 팀인지 알려준다. +- [x] 킹인지 확인한다. +- [x] 폰인지 확인한다. + +### 체스 말 타입 +- [x] 무슨 타입인지 알려준다. +- [x] 타입별 점수를 알려준다. + +### 움직임 +- [x] 이동 가능한지 판단한다. +- [x] 해당 경로를 구한다. + +### 팀 +- [x] 흰색, 검은색을 구분한다. +- [x] 다음 팀을 알려준다. + +### 체스 보드 +- [x] 체스 말 위치 초기화를 한다. +- [x] 해당 위치에 어떤 말이 있는지 알려준다. +- [x] 시작 위치의 말을 도착 위치로 옮긴다. + - [x] 시작 위치에 말이 없을 경우 예외 + - [x] 말의 이동 범위를 넘어갈 경우 예외 + - [x] 이동 경로에 다른 말이 있을 경우 예외 +- [x] 마지막 위치에 적 말이 있을 경우 잡는다. + - [x] 폰의 경우 대각선이 아닌 앞으로 이동할 때에는 잡지 못한다. +- [x] 흰색부터 번갈아가며 플레이한다. +- [x] 각 팀의 점수를 계산한다. +- [x] 왕이 잡히면 게임이 끝난다. + +### 위치 +- [x] 가로 위치(왼쪽부터 a~h)를 저장한다. +- [x] 세로 위치(아래부터 1~8)를 저장한다. +- [x] 서로 같은 위치인지 판단한다. +- [x] 다음 동, 서, 남, 북쪽 위치를 알려준다. +- [x] 서로 같은 랭크인지 판단한다. +- [x] 서로 같은 파일인지 판단한다. + +### 출력 +- [x] 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. + +### 게임 진행 +- [x] start 커맨드를 입력하면 게임에 사용할 보드를 생성한다. + - [x] 이미 보드 정보가 존재할 경우, 그 보드 정보를 불러온다. + - [x] 보드가 존재하지 않을 경우 새 보드를 생성한다. +- [x] end 커맨드를 입력하면 보드의 정보를 저장한다. +- [x] 왕이 잡혀 게임이 종료되면 저장된 보드의 정보를 삭제한다. + +----------- +## 왕이 잡혀 게임이 종료되는 예시 +### 흑 승리 +move f2 f3 +move e7 e5 +move g2 g4 +move d8 h4 +move h2 h3 +move h4 e1 + +### 백 승리 +move e2 e3 +move f7 f6 +move d1 h5 +move g8 h6 +move h5 e8 diff --git a/build.gradle b/build.gradle index 3697236c6fb..8c605072e94 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + runtimeOnly("com.mysql:mysql-connector-j:8.3.0") } java { diff --git a/db/mysql/init/init.sql b/db/mysql/init/init.sql new file mode 100644 index 00000000000..b9fb428cceb --- /dev/null +++ b/db/mysql/init/init.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS game ( + gameId INT PRIMARY KEY, + turn VARCHAR(5) NOT NULL +); + +CREATE TABLE IF NOT EXISTS board ( + gameId INT, + file CHAR(5) NOT NULL, + `rank` CHAR(5) NOT NULL, + type VARCHAR(10) NOT NULL, + team VARCHAR(5) NOT NULL +); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..558a1d5a53f --- /dev/null +++ b/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/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/chess/BoardDao.java b/src/main/java/chess/BoardDao.java new file mode 100644 index 00000000000..49379d3b92a --- /dev/null +++ b/src/main/java/chess/BoardDao.java @@ -0,0 +1,198 @@ +package chess; + +import chess.domain.Board; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.BoardDto; +import chess.dto.PieceDto; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class BoardDao { + + private static final String SERVER = "localhost:13306"; // MySQL 서버 주소 + private static final String DATABASE = "chess"; // MySQL DATABASE 이름 + private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; + private static final String USERNAME = "root"; // MySQL 서버 아이디 + private static final String PASSWORD = "root"; // MySQL 서버 비밀번호 + + public Connection getConnection() { + try { + return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD); + } catch (final SQLException e) { + throw new RuntimeException("DB 연결 실패", e); + } + } + + public void add(final int gameId, final BoardDto boardDto) { + addGame(gameId, boardDto); + addBoard(gameId, boardDto); + } + + private void addGame(final int gameId, final BoardDto boardDto) { + final var query = "INSERT INTO game VALUES(?, ?)"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.setString(2, boardDto.getTurn()); + preparedStatement.executeUpdate(); + } catch (final SQLException e) { + throw new RuntimeException("게임 정보 저장 실패", e); + } + } + + private void addBoard(final int gameId, final BoardDto boardDto) { + Map board = boardDto.getBoardDto(); + + final var query = "INSERT INTO board VALUES(?, ?, ?, ?, ?)"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + addPiece(gameId, board, preparedStatement); + } catch (final SQLException e) { + throw new RuntimeException("보드 정보 저장 실패", e); + } + } + + private void addPiece(int gameId, Map board, PreparedStatement preparedStatement) + throws SQLException { + for (Entry pieceEntry : board.entrySet()) { + preparedStatement.setInt(1, gameId); + preparedStatement.setString(2, pieceEntry.getKey().getFile()); + preparedStatement.setString(3, pieceEntry.getKey().getRank()); + preparedStatement.setString(4, pieceEntry.getValue().getType()); + preparedStatement.setString(5, pieceEntry.getValue().getTeam()); + preparedStatement.executeUpdate(); + } + } + + public void delete(int gameId) { + deleteGame(gameId); + deleteBoard(gameId); + } + + private void deleteGame(int gameId) { + final var query = "DELETE FROM game WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("게임 정보 삭제 실패", e); + } + } + + private void deleteBoard(int gameId) { + final var query = "DELETE FROM board WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + preparedStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException("보드 정보 삭제 실패", e); + } + } + + public Board loadBoard(int gameId) { + Team turn = getGameTurn(gameId); + Map pieces = getBoardPieces(gameId); + return new Board(pieces, turn); + } + + private Team getGameTurn(int gameId) { + final String query = "SELECT turn FROM game WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + Team turn = getTeam(preparedStatement); + validateExistTeam(turn); + return turn; + } catch (SQLException e) { + throw new RuntimeException("게임 턴 정보 조회 실패", e); + } + } + + private Team getTeam(PreparedStatement preparedStatement) throws SQLException { + try (ResultSet resultSet = preparedStatement.executeQuery()) { + return getTeam(resultSet); + } + } + + private Team getTeam(ResultSet resultSet) throws SQLException { + if (resultSet.next()) { + String turn = resultSet.getString("turn"); + return Team.valueOf(turn.toUpperCase()); + } + return null; + } + + private void validateExistTeam(Team turn) { + if (turn == null) { + throw new IllegalArgumentException("해당 게임 ID를 찾을 수 없습니다."); + } + } + + private Map getBoardPieces(int gameId) { + Map pieces = new HashMap<>(); + final String query = "SELECT file, `rank`, type, team FROM board WHERE gameId = ?"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + getOnePiece(preparedStatement, pieces); + } catch (SQLException e) { + throw new RuntimeException("보드 정보 조회 실패", e); + } + return pieces; + } + + private void getOnePiece(PreparedStatement preparedStatement, Map pieces) throws SQLException { + try (ResultSet resultSet = preparedStatement.executeQuery()) { + getOnePiece(pieces, resultSet); + } + } + + private void getOnePiece(Map pieces, ResultSet resultSet) throws SQLException { + while (resultSet.next()) { + File file = File.valueOf(resultSet.getString("file").toUpperCase()); + Rank rank = Rank.valueOf(resultSet.getString("rank").toUpperCase()); + PieceType type = PieceType.valueOf(resultSet.getString("type").toUpperCase()); + Team team = Team.valueOf(resultSet.getString("team").toUpperCase()); + Position position = new Position(file, rank); + Piece piece = type.createPiece(team); + pieces.put(position, piece); + } + } + + public boolean isExistBoard(int gameId) { + final String query = "SELECT EXISTS(SELECT 1 FROM game WHERE gameId = ?) AS Exist"; + try (final var connection = getConnection(); + final var preparedStatement = connection.prepareStatement(query)) { + preparedStatement.setInt(1, gameId); + return getExist(preparedStatement); + } catch (SQLException e) { + throw new RuntimeException("보드 존재 여부 확인 실패", e); + } + } + + private boolean getExist(PreparedStatement preparedStatement) throws SQLException { + try (ResultSet rs = preparedStatement.executeQuery()) { + return getExist(rs); + } + } + + private boolean getExist(ResultSet rs) throws SQLException { + if (rs.next()) { + return rs.getBoolean("Exist"); + } + return false; + } +} diff --git a/src/main/java/chess/ChessApplication.java b/src/main/java/chess/ChessApplication.java new file mode 100644 index 00000000000..0fed82c4e21 --- /dev/null +++ b/src/main/java/chess/ChessApplication.java @@ -0,0 +1,15 @@ +package chess; + +import chess.view.InputView; +import chess.view.OutputView; + +public class ChessApplication { + + public static void main(String[] args) { + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + ChessGame chessGame = new ChessGame(inputView, outputView); + + chessGame.tryStart(); + } +} diff --git a/src/main/java/chess/ChessGame.java b/src/main/java/chess/ChessGame.java new file mode 100644 index 00000000000..36942ccdcaf --- /dev/null +++ b/src/main/java/chess/ChessGame.java @@ -0,0 +1,125 @@ +package chess; + +import chess.domain.Board; +import chess.domain.BoardFactory; +import chess.domain.ScoreCalculator; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import chess.dto.BoardDto; +import chess.view.GameCommand; +import chess.view.InputView; +import chess.view.OutputView; + +public class ChessGame { + + private static final int ONE_GAME = 1; + + private final InputView inputView; + private final OutputView outputView; + + public ChessGame(InputView inputView, OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void tryStart() { + try { + outputView.printStartGame(); + start(); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception); + tryStart(); + } + } + + private void start() { + GameCommand command = inputView.readCommand(); + if (command == GameCommand.START) { + Board board = getBoard(); + showBoard(board); + play(board); + return; + } + if (GameCommand.isImpossibleBeforeStartGame(command)) { + throw new IllegalArgumentException("아직 게임을 시작하지 않았습니다."); + } + } + + private Board getBoard() { + BoardDao boardDao = new BoardDao(); + if (boardDao.isExistBoard(ONE_GAME)) { + return boardDao.loadBoard(ONE_GAME); + } + return BoardFactory.createInitialBoard(); + } + + private void play(Board board) { + GameStatus gameStatus = GameStatus.PLAYING; + while (gameStatus.isPlaying()) { + gameStatus = tryProcessTurn(board); + } + } + + private GameStatus tryProcessTurn(Board board) { + try { + GameCommand command = inputView.readCommand(); + return processTurn(command, board); + } catch (IllegalArgumentException exception) { + outputView.printExceptionMessage(exception); + tryProcessTurn(board); + } + return GameStatus.PLAYING; + } + + private GameStatus processTurn(GameCommand command, Board board) { + if (command == GameCommand.START) { + throw new IllegalArgumentException("이미 게임을 시작했습니다."); + } + if (command == GameCommand.END) { + return endGame(board); + } + if (command == GameCommand.STATUS) { + return showStatus(board); + } + return executeMove(board); + } + + private GameStatus endGame(Board board) { + BoardDao boardDao = new BoardDao(); + boardDao.delete(ONE_GAME); + + BoardDto boardDto = BoardDto.of(board); + boardDao.add(ONE_GAME, boardDto); + + return GameStatus.END; + } + + private GameStatus showStatus(Board board) { + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + double blackScore = scoreCalculator.getBlackScore(); + double whiteScore = scoreCalculator.getWhiteScore(); + Team winner = scoreCalculator.chooseWinner(); + + outputView.printStatus(blackScore, whiteScore, winner); + return GameStatus.PLAYING; + } + + private GameStatus executeMove(Board board) { + Position start = inputView.readPosition(); + Position end = inputView.readPosition(); + GameStatus gameStatus = board.tryMove(start, end); + showBoard(board); + if (!gameStatus.isPlaying()) { + outputView.printWinner(gameStatus); + BoardDao boardDao = new BoardDao(); + boardDao.delete(ONE_GAME); + } + return gameStatus; + } + + private void showBoard(Board board) { + BoardDto boardDto = BoardDto.of(board); + outputView.printBoard(boardDto); + } +} diff --git a/src/main/java/chess/GameStatus.java b/src/main/java/chess/GameStatus.java new file mode 100644 index 00000000000..aaf2f3b1fe7 --- /dev/null +++ b/src/main/java/chess/GameStatus.java @@ -0,0 +1,22 @@ +package chess; + +import chess.domain.piece.Team; + +public enum GameStatus { + + PLAYING, + END, + BLACK_WIN, + WHITE_WIN; + + public static GameStatus winBy(Team team) { + if (team.isBlack()) { + return BLACK_WIN; + } + return WHITE_WIN; + } + + public boolean isPlaying() { + return this == GameStatus.PLAYING; + } +} diff --git a/src/main/java/chess/domain/Board.java b/src/main/java/chess/domain/Board.java new file mode 100644 index 00000000000..88487715148 --- /dev/null +++ b/src/main/java/chess/domain/Board.java @@ -0,0 +1,112 @@ +package chess.domain; + +import chess.GameStatus; +import chess.domain.piece.Piece; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Stream; + +public class Board { + + private final Map board; + private Team turn; + + public Board(Map board) { + this(board, Team.WHITE); + } + + public Board(Map board, Team turn) { + this.board = new HashMap<>(board); + this.turn = turn; + } + + public Optional find(Position position) { + Piece piece = board.get(position); + return Optional.ofNullable(piece); + } + + public GameStatus tryMove(Position start, Position end) { + Piece movingPiece = findMovingPiece(start); + validateTeamRule(movingPiece, end); + + boolean hasEnemy = hasEnemy(end); + List path = movingPiece.findPath(start, end, hasEnemy); + validatePath(path); + + return move(start, end, movingPiece); + } + + private Piece findMovingPiece(Position start) { + return find(start) + .orElseThrow(() -> new IllegalArgumentException("해당 위치에 말이 없습니다.")); + } + + private void validateTeamRule(Piece movingPiece, Position end) { + if (!movingPiece.isSameTeam(turn)) { + throw new IllegalArgumentException("상대 팀의 차례입니다."); + } + if (isSameTeamAtDestination(end)) { + throw new IllegalArgumentException("같은 팀의 말을 잡을 수 없습니다."); + } + } + + private boolean isSameTeamAtDestination(Position end) { + return find(end).map(piece -> piece.isSameTeam(turn)) + .orElse(false); + } + + private boolean hasEnemy(Position end) { + return find(end).map(piece -> !piece.isSameTeam(turn)) + .orElse(false); + } + + private void validatePath(List path) { + if (isBlocked(path)) { + throw new IllegalArgumentException("다른 말이 있어 이동 불가능합니다."); + } + } + + private boolean isBlocked(List path) { + return path.stream() + .anyMatch(board::containsKey); + } + + private GameStatus move(Position start, Position end, Piece movingPiece) { + board.remove(start); + if (isOtherTeamKing(end)) { + return GameStatus.winBy(turn); + } + board.put(end, movingPiece); + turn = turn.next(); + return GameStatus.PLAYING; + } + + private boolean isOtherTeamKing(Position end) { + return find(end).map(this::isOtherTeamKing).orElse(false); + } + + private boolean isOtherTeamKing(Piece piece) { + return !piece.isSameTeam(turn) && piece.isKing(); + } + + public Stream getPiecesOf(Team team) { + return board.values().stream() + .filter(piece -> piece.isSameTeam(team)); + } + + public Stream getPawnPositionsOf(Team team) { + return board.entrySet().stream() + .filter(entry -> entry.getValue().isSameTeam(team)) + .filter(entry -> entry.getValue().isPawn()) + .map(Entry::getKey); + } + + public Team getTurn() { + return turn; + } +} diff --git a/src/main/java/chess/domain/BoardFactory.java b/src/main/java/chess/domain/BoardFactory.java new file mode 100644 index 00000000000..a5686459154 --- /dev/null +++ b/src/main/java/chess/domain/BoardFactory.java @@ -0,0 +1,73 @@ +package chess.domain; + +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.HashMap; +import java.util.Map; + +public class BoardFactory { + + private BoardFactory() { + } + + public static Board createInitialBoard() { + Map map = new HashMap<>(); + initializePawn(map); + initializeKnight(map); + initializeBishop(map); + initializeRook(map); + initializeQueen(map); + initializeKing(map); + + return new Board(map); + } + + private static void initializePawn(Map map) { + for (File file : File.values()) { + map.put(new Position(file, Rank.TWO), new Pawn(Team.WHITE)); + } + for (File file : File.values()) { + map.put(new Position(file, Rank.SEVEN), new Pawn(Team.BLACK)); + } + } + + private static void initializeKnight(Map map) { + map.put(new Position(File.B, Rank.ONE), new Knight(Team.WHITE)); + map.put(new Position(File.G, Rank.ONE), new Knight(Team.WHITE)); + map.put(new Position(File.B, Rank.EIGHT), new Knight(Team.BLACK)); + map.put(new Position(File.G, Rank.EIGHT), new Knight(Team.BLACK)); + } + + private static void initializeBishop(Map map) { + map.put(new Position(File.C, Rank.ONE), new Bishop(Team.WHITE)); + map.put(new Position(File.F, Rank.ONE), new Bishop(Team.WHITE)); + map.put(new Position(File.C, Rank.EIGHT), new Bishop(Team.BLACK)); + map.put(new Position(File.F, Rank.EIGHT), new Bishop(Team.BLACK)); + } + + private static void initializeRook(Map map) { + map.put(new Position(File.A, Rank.ONE), new Rook(Team.WHITE)); + map.put(new Position(File.H, Rank.ONE), new Rook(Team.WHITE)); + map.put(new Position(File.A, Rank.EIGHT), new Rook(Team.BLACK)); + map.put(new Position(File.H, Rank.EIGHT), new Rook(Team.BLACK)); + } + + private static void initializeQueen(Map map) { + map.put(new Position(File.D, Rank.ONE), new Queen(Team.WHITE)); + map.put(new Position(File.D, Rank.EIGHT), new Queen(Team.BLACK)); + } + + private static void initializeKing(Map map) { + map.put(new Position(File.E, Rank.ONE), new King(Team.WHITE)); + map.put(new Position(File.E, Rank.EIGHT), new King(Team.BLACK)); + } +} diff --git a/src/main/java/chess/domain/ScoreCalculator.java b/src/main/java/chess/domain/ScoreCalculator.java new file mode 100644 index 00000000000..0077a6dc30b --- /dev/null +++ b/src/main/java/chess/domain/ScoreCalculator.java @@ -0,0 +1,61 @@ +package chess.domain; + +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import java.util.function.Predicate; + +public class ScoreCalculator { + + private static final double PAWN_PENALTY_SCORE = 0.5; + + private final double blackScore; + private final double whiteScore; + + public ScoreCalculator(Board board) { + blackScore = calculateScoreOf(board, Team.BLACK); + whiteScore = calculateScoreOf(board, Team.WHITE); + } + + private double calculateScoreOf(Board board, Team team) { + double basicScore = calculateBasicScoreOf(board, team); + double minusScore = calculateMinusScoreOf(board, team); + + return basicScore - minusScore; + } + + private double calculateBasicScoreOf(Board board, Team team) { + return board.getPiecesOf(team) + .mapToDouble(PieceType::scoreOf) + .sum(); + } + + private double calculateMinusScoreOf(Board board, Team team) { + Predicate isOnSameFile = position -> board.getPawnPositionsOf(team) + .anyMatch(other -> !other.equals(position) && other.isOnSameFile(position)); + + int count = (int) board.getPawnPositionsOf(team) + .filter(isOnSameFile) + .count(); + + return count * PAWN_PENALTY_SCORE; + } + + public Team chooseWinner() { + if (blackScore > whiteScore) { + return Team.BLACK; + } + if (whiteScore > blackScore) { + return Team.WHITE; + } + return null; + } + + public double getBlackScore() { + return blackScore; + } + + public double getWhiteScore() { + return whiteScore; + } +} diff --git a/src/main/java/chess/domain/movement/MovementRule.java b/src/main/java/chess/domain/movement/MovementRule.java new file mode 100644 index 00000000000..9c16ec8f3a5 --- /dev/null +++ b/src/main/java/chess/domain/movement/MovementRule.java @@ -0,0 +1,11 @@ +package chess.domain.movement; + +import chess.domain.position.Position; +import java.util.List; + +public interface MovementRule { + + boolean isMovable(Position start, Position end, boolean hasEnemy); + + List findPath(Position start, Position end, boolean hasEnemy); +} diff --git a/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java b/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java new file mode 100644 index 00000000000..d8483efcf74 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/ContinuousMovementRule.java @@ -0,0 +1,42 @@ +package chess.domain.movement.continuous; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; +import java.util.stream.Stream; + +public abstract class ContinuousMovementRule implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return isMovable(rankDifference, fileDifference); + } + + @Override + public final List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + int amount = calculate(start, end); + + return Stream.iterate(next(start), this::next) + .limit(amount) + .toList(); + } + + private int calculate(Position start, Position end) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return Math.max(rankDifferenceSize, fileDifferenceSize) - 1; + } + + protected abstract boolean isMovable(int rankDifference, int fileDifference); + + protected abstract Position next(Position position); +} diff --git a/src/main/java/chess/domain/movement/continuous/EastMovement.java b/src/main/java/chess/domain/movement/continuous/EastMovement.java new file mode 100644 index 00000000000..fdad60b825c --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/EastMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class EastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference == 0 && fileDifference > 0; + } + + @Override + protected Position next(Position position) { + return position.moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java b/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java new file mode 100644 index 00000000000..058c133e3c3 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthEastMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthEastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference > 0 && rankDifference == fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth() + .moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthMovement.java b/src/main/java/chess/domain/movement/continuous/NorthMovement.java new file mode 100644 index 00000000000..c6135f7772e --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return fileDifference == 0 && rankDifference > 0; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java b/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java new file mode 100644 index 00000000000..9d2dd2b63e1 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/NorthWestMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class NorthWestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference > 0 && rankDifference == -fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToNorth() + .moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java b/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java new file mode 100644 index 00000000000..d0f0da32dda --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthEastMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthEastMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference < 0 && rankDifference == -fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth() + .moveToEast(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthMovement.java b/src/main/java/chess/domain/movement/continuous/SouthMovement.java new file mode 100644 index 00000000000..bcd766531a6 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return fileDifference == 0 && rankDifference < 0; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java b/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java new file mode 100644 index 00000000000..d4a6bcf65f4 --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/SouthWestMovement.java @@ -0,0 +1,17 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class SouthWestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference < 0 && rankDifference == fileDifference; + } + + @Override + protected Position next(Position position) { + return position.moveToSouth() + .moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/continuous/WestMovement.java b/src/main/java/chess/domain/movement/continuous/WestMovement.java new file mode 100644 index 00000000000..f9e4a9e1c8a --- /dev/null +++ b/src/main/java/chess/domain/movement/continuous/WestMovement.java @@ -0,0 +1,16 @@ +package chess.domain.movement.continuous; + +import chess.domain.position.Position; + +public final class WestMovement extends ContinuousMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + return rankDifference == 0 && fileDifference < 0; + } + + @Override + protected Position next(Position position) { + return position.moveToWest(); + } +} diff --git a/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java b/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java new file mode 100644 index 00000000000..ced0ac5cb29 --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/DiscreteMovementRule.java @@ -0,0 +1,27 @@ +package chess.domain.movement.discrete; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class DiscreteMovementRule implements MovementRule { + + @Override + public final boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return isMovable(rankDifference, fileDifference); + } + + @Override + public final List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } + + protected abstract boolean isMovable(int rankDifference, int fileDifference); +} diff --git a/src/main/java/chess/domain/movement/discrete/KingMovement.java b/src/main/java/chess/domain/movement/discrete/KingMovement.java new file mode 100644 index 00000000000..44351e744ad --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/KingMovement.java @@ -0,0 +1,12 @@ +package chess.domain.movement.discrete; + +public final class KingMovement extends DiscreteMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return rankDifferenceSize <= 1 && fileDifferenceSize <= 1; + } +} diff --git a/src/main/java/chess/domain/movement/discrete/KnightMovement.java b/src/main/java/chess/domain/movement/discrete/KnightMovement.java new file mode 100644 index 00000000000..71a7aab77c2 --- /dev/null +++ b/src/main/java/chess/domain/movement/discrete/KnightMovement.java @@ -0,0 +1,13 @@ +package chess.domain.movement.discrete; + +public final class KnightMovement extends DiscreteMovementRule { + + @Override + protected boolean isMovable(int rankDifference, int fileDifference) { + int rankDifferenceSize = Math.abs(rankDifference); + int fileDifferenceSize = Math.abs(fileDifference); + + return (rankDifferenceSize == 2 && fileDifferenceSize == 1) + || (fileDifferenceSize == 2 && rankDifferenceSize == 1); + } +} 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..325584a818b --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDefaultMovement.java @@ -0,0 +1,23 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public final class BlackPawnDefaultMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && rankDifference == -1 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } +} 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..364431ce834 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnDiagonalMovement.java @@ -0,0 +1,20 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public class BlackPawnDiagonalMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int fileDifferenceSize = Math.abs(fileDifference); + + return hasEnemy && rankDifference == -1 && fileDifferenceSize == 1; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + return List.of(); + } +} 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..2e788c22cd8 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/BlackPawnFirstMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class BlackPawnFirstMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && start.isOnSameRank(Rank.SEVEN) && rankDifference == -2 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + Position middle = start.moveToSouth(); + return List.of(middle); + } +} 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..e222c3ba045 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDefaultMovement.java @@ -0,0 +1,23 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public final class WhitePawnDefaultMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && rankDifference == 1 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + return List.of(); + } +} 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..77a44292944 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnDiagonalMovement.java @@ -0,0 +1,20 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public class WhitePawnDiagonalMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + int fileDifferenceSize = Math.abs(fileDifference); + + return hasEnemy && rankDifference == 1 && fileDifferenceSize == 1; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + return List.of(); + } +} 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..5abd4dd5340 --- /dev/null +++ b/src/main/java/chess/domain/movement/pawn/WhitePawnFirstMovement.java @@ -0,0 +1,25 @@ +package chess.domain.movement.pawn; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.List; + +public final class WhitePawnFirstMovement implements MovementRule { + + public boolean isMovable(Position start, Position end, boolean hasEnemy) { + int rankDifference = start.calculateRankDifference(end); + int fileDifference = start.calculateFileDifference(end); + + return !hasEnemy && start.isOnSameRank(Rank.TWO) && rankDifference == 2 && fileDifference == 0; + } + + public List findPath(Position start, Position end, boolean hasEnemy) { + if (!isMovable(start, end, hasEnemy)) { + throw new IllegalArgumentException("경로가 존재하지 않습니다."); + } + + Position middle = start.moveToNorth(); + return List.of(middle); + } +} diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java new file mode 100644 index 00000000000..ddef1bee55a --- /dev/null +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -0,0 +1,18 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.NorthEastMovement; +import chess.domain.movement.continuous.NorthWestMovement; +import chess.domain.movement.continuous.SouthEastMovement; +import chess.domain.movement.continuous.SouthWestMovement; +import java.util.List; + +public final class Bishop extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); + + public Bishop(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java new file mode 100644 index 00000000000..512dd282846 --- /dev/null +++ b/src/main/java/chess/domain/piece/King.java @@ -0,0 +1,14 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.discrete.KingMovement; +import java.util.List; + +public final class King extends Piece { + + private static final List MOVEMENT_RULES = List.of(new KingMovement()); + + public King(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java new file mode 100644 index 00000000000..b9532ba6169 --- /dev/null +++ b/src/main/java/chess/domain/piece/Knight.java @@ -0,0 +1,14 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.discrete.KnightMovement; +import java.util.List; + +public final class Knight extends Piece { + + private static final List MOVEMENT_RULES = List.of(new KnightMovement()); + + public Knight(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java new file mode 100644 index 00000000000..8262fe13c60 --- /dev/null +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -0,0 +1,29 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +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 { + + private static final List BLACK_MOVEMENT_RULES = List.of( + new BlackPawnFirstMovement(), new BlackPawnDefaultMovement(), new BlackPawnDiagonalMovement()); + private static final List WHITE_MOVEMENT_RULES = List.of( + new WhitePawnFirstMovement(), new WhitePawnDefaultMovement(), new WhitePawnDiagonalMovement()); + + public Pawn(Team team) { + super(team, findMovementRule(team)); + } + + private static List findMovementRule(Team team) { + if (team.isBlack()) { + return BLACK_MOVEMENT_RULES; + } + return WHITE_MOVEMENT_RULES; + } +} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java new file mode 100644 index 00000000000..2e3b4147a69 --- /dev/null +++ b/src/main/java/chess/domain/piece/Piece.java @@ -0,0 +1,40 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.position.Position; +import java.util.List; + +public abstract class Piece { + + private final Team team; + private final List movementRules; + + protected Piece(Team team, List movementRules) { + this.team = team; + this.movementRules = movementRules; + } + + public final boolean isBlackTeam() { + return team.isBlack(); + } + + public final List findPath(Position start, Position end, boolean hasEnemy) { + return movementRules.stream() + .filter(movementRule -> movementRule.isMovable(start, end, hasEnemy)) + .findAny() + .map(movementRule -> movementRule.findPath(start, end, hasEnemy)) + .orElseThrow(() -> new IllegalArgumentException("불가능한 경로입니다.")); + } + + public boolean isSameTeam(Team team) { + return this.team == team; + } + + public boolean isPawn() { + return PieceType.from(this) == PieceType.PAWN; + } + + public boolean isKing() { + return PieceType.from(this) == PieceType.KING; + } +} diff --git a/src/main/java/chess/domain/piece/PieceType.java b/src/main/java/chess/domain/piece/PieceType.java new file mode 100644 index 00000000000..f5bec7320b4 --- /dev/null +++ b/src/main/java/chess/domain/piece/PieceType.java @@ -0,0 +1,39 @@ +package chess.domain.piece; + +import java.util.Arrays; +import java.util.function.Function; + +public enum PieceType { + + KING(King.class, 0, King::new), + QUEEN(Queen.class, 9, Queen::new), + ROOK(Rook.class, 5, Rook::new), + BISHOP(Bishop.class, 3, Bishop::new), + KNIGHT(Knight.class, 2.5, Knight::new), + PAWN(Pawn.class, 1, Pawn::new); + + private final Class category; + private final double score; + private final Function pieceFactory; + + PieceType(Class category, double score, Function pieceFactory) { + this.category = category; + this.score = score; + this.pieceFactory = pieceFactory; + } + + public static PieceType from(Piece piece) { + return Arrays.stream(PieceType.values()) + .filter(pieceType -> pieceType.category == piece.getClass()) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 기물이 존재하지 않습니다.")); + } + + public static double scoreOf(Piece piece) { + return from(piece).score; + } + + public Piece createPiece(Team team) { + return pieceFactory.apply(team); + } +} diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java new file mode 100644 index 00000000000..f9c9c19e378 --- /dev/null +++ b/src/main/java/chess/domain/piece/Queen.java @@ -0,0 +1,23 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.EastMovement; +import chess.domain.movement.continuous.NorthEastMovement; +import chess.domain.movement.continuous.NorthMovement; +import chess.domain.movement.continuous.NorthWestMovement; +import chess.domain.movement.continuous.SouthEastMovement; +import chess.domain.movement.continuous.SouthMovement; +import chess.domain.movement.continuous.SouthWestMovement; +import chess.domain.movement.continuous.WestMovement; +import java.util.List; + +public final class Queen extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement(), + new NorthEastMovement(), new SouthEastMovement(), new NorthWestMovement(), new SouthWestMovement()); + + public Queen(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java new file mode 100644 index 00000000000..c7ffb684c51 --- /dev/null +++ b/src/main/java/chess/domain/piece/Rook.java @@ -0,0 +1,18 @@ +package chess.domain.piece; + +import chess.domain.movement.MovementRule; +import chess.domain.movement.continuous.EastMovement; +import chess.domain.movement.continuous.NorthMovement; +import chess.domain.movement.continuous.SouthMovement; +import chess.domain.movement.continuous.WestMovement; +import java.util.List; + +public final class Rook extends Piece { + + private static final List MOVEMENT_RULES = List.of( + new EastMovement(), new WestMovement(), new SouthMovement(), new NorthMovement()); + + public Rook(Team team) { + super(team, MOVEMENT_RULES); + } +} diff --git a/src/main/java/chess/domain/piece/Team.java b/src/main/java/chess/domain/piece/Team.java new file mode 100644 index 00000000000..4891d246ae9 --- /dev/null +++ b/src/main/java/chess/domain/piece/Team.java @@ -0,0 +1,17 @@ +package chess.domain.piece; + +public enum Team { + + BLACK, WHITE; + + public boolean isBlack() { + return this == BLACK; + } + + public Team next() { + if (this == BLACK) { + return WHITE; + } + return BLACK; + } +} diff --git a/src/main/java/chess/domain/position/File.java b/src/main/java/chess/domain/position/File.java new file mode 100644 index 00000000000..027dc6a959b --- /dev/null +++ b/src/main/java/chess/domain/position/File.java @@ -0,0 +1,53 @@ +package chess.domain.position; + +import java.util.Arrays; + +public enum File { + + A(1), + B(2), + C(3), + D(4), + E(5), + F(6), + G(7), + H(8); + + private static final int MIN_WEST_TO_EAST = 1; + private static final int MAX_WEST_TO_EAST = 8; + private static final int TO_EAST = 1; + private static final int TO_WEST = -1; + + private final int westToEast; + + File(int westToEast) { + this.westToEast = westToEast; + } + + public File toEast() { + if (westToEast >= MAX_WEST_TO_EAST) { + throw new IllegalStateException("동쪽으로 이동할 수 없습니다."); + } + + return find(westToEast + TO_EAST); + } + + public File toWest() { + if (westToEast <= MIN_WEST_TO_EAST) { + throw new IllegalStateException("서쪽으로 이동할 수 없습니다."); + } + + return find(westToEast + TO_WEST); + } + + private File find(int westToEast) { + return Arrays.stream(File.values()) + .filter(column -> column.westToEast == westToEast) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당하는 가로 위치가 없습니다.")); + } + + public int calculateDifference(File other) { + return other.westToEast - this.westToEast; + } +} diff --git a/src/main/java/chess/domain/position/Position.java b/src/main/java/chess/domain/position/Position.java new file mode 100644 index 00000000000..ce2676fc35d --- /dev/null +++ b/src/main/java/chess/domain/position/Position.java @@ -0,0 +1,78 @@ +package chess.domain.position; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class Position { + + public static final List ALL_POSITIONS = Arrays.stream(Rank.values()) + .flatMap(row -> Arrays.stream(File.values()) + .map(column -> new Position(column, row))) + .toList(); + + private final File file; + private final Rank rank; + + public Position(File file, Rank rank) { + this.file = Objects.requireNonNull(file); + this.rank = Objects.requireNonNull(rank); + } + + public boolean isOnSameRank(Rank rank) { + return this.rank == rank; + } + + public boolean isOnSameFile(Position other) { + return this.file == other.file; + } + + public int calculateFileDifference(Position other) { + return this.file.calculateDifference(other.file); + } + + public int calculateRankDifference(Position other) { + return this.rank.calculateDifference(other.rank); + } + + public Position moveToEast() { + return new Position(file.toEast(), rank); + } + + public Position moveToWest() { + return new Position(file.toWest(), rank); + } + + public Position moveToNorth() { + return new Position(file, rank.toNorth()); + } + + public Position moveToSouth() { + return new Position(file, rank.toSouth()); + } + + public String getFile() { + return file.toString(); + } + + public String getRank() { + return rank.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Position position = (Position) o; + return rank == position.rank && file == position.file; + } + + @Override + public int hashCode() { + return Objects.hash(rank, file); + } +} diff --git a/src/main/java/chess/domain/position/Rank.java b/src/main/java/chess/domain/position/Rank.java new file mode 100644 index 00000000000..97fd1485b25 --- /dev/null +++ b/src/main/java/chess/domain/position/Rank.java @@ -0,0 +1,53 @@ +package chess.domain.position; + +import java.util.Arrays; + +public enum Rank { + + ONE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8); + + private static final int MIN_SOUTH_TO_NORTH = 1; + private static final int MAX_SOUTH_TO_NORTH = 8; + private static final int TO_NORTH = 1; + private static final int TO_SOUTH = -1; + + private final int southToNorth; + + Rank(int southToNorth) { + this.southToNorth = southToNorth; + } + + public Rank toNorth() { + if (southToNorth >= MAX_SOUTH_TO_NORTH) { + throw new IllegalStateException("북쪽으로 이동할 수 없습니다."); + } + + return find(southToNorth + TO_NORTH); + } + + public Rank toSouth() { + if (southToNorth <= MIN_SOUTH_TO_NORTH) { + throw new IllegalStateException("남쪽으로 이동할 수 없습니다."); + } + + return find(southToNorth + TO_SOUTH); + } + + private Rank find(int southToNorth) { + return Arrays.stream(Rank.values()) + .filter(row -> row.southToNorth == southToNorth) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당하는 가로 위치가 없습니다.")); + } + + public int calculateDifference(Rank other) { + return other.southToNorth - this.southToNorth; + } +} diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java new file mode 100644 index 00000000000..bc9dea44388 --- /dev/null +++ b/src/main/java/chess/dto/BoardDto.java @@ -0,0 +1,53 @@ +package chess.dto; + +import chess.domain.Board; +import chess.domain.piece.Piece; +import chess.domain.piece.Team; +import chess.domain.position.Position; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BoardDto { + + private final Map boardDto; + private final Team turn; + + private BoardDto(Map boardDto, Team turn) { + this.boardDto = boardDto; + this.turn = turn; + } + + public static BoardDto of(Board board) { + List positions = Position.ALL_POSITIONS; + Map boardDto = new HashMap<>(); + positions.forEach(position -> addPiece(board, position, boardDto)); + Team turn = board.getTurn(); + return new BoardDto(boardDto, turn); + } + + private static void addPiece(Board board, Position position, Map boardDto) { + Optional optionalPiece = board.find(position); + + if (optionalPiece.isEmpty()) { + return; + } + + Piece piece = optionalPiece.get(); + PieceDto pieceDto = PieceDto.from(piece); + boardDto.put(position, pieceDto); + } + + public Optional find(Position position) { + return Optional.ofNullable(boardDto.get(position)); + } + + public Map getBoardDto() { + return boardDto; + } + + public String getTurn() { + return turn.toString(); + } +} diff --git a/src/main/java/chess/dto/PieceDto.java b/src/main/java/chess/dto/PieceDto.java new file mode 100644 index 00000000000..f563ab90e0b --- /dev/null +++ b/src/main/java/chess/dto/PieceDto.java @@ -0,0 +1,42 @@ +package chess.dto; + +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; + +public class PieceDto { + + private final PieceType type; + private final boolean isBlack; + + private PieceDto(PieceType type, boolean isBlack) { + this.type = type; + this.isBlack = isBlack; + } + + static PieceDto from(Piece piece) { + PieceType type = PieceType.from(piece); + boolean isBlackTeam = piece.isBlackTeam(); + + return new PieceDto(type, isBlackTeam); + } + + public PieceType type() { + return type; + } + + public boolean isBlack() { + return isBlack; + } + + public String getType() { + return type.toString(); + } + + public String getTeam() { + if (isBlack) { + return Team.BLACK.toString(); + } + return Team.WHITE.toString(); + } +} diff --git a/src/main/java/chess/view/GameCommand.java b/src/main/java/chess/view/GameCommand.java new file mode 100644 index 00000000000..4f641937aca --- /dev/null +++ b/src/main/java/chess/view/GameCommand.java @@ -0,0 +1,28 @@ +package chess.view; + +import java.util.Arrays; + +public enum GameCommand { + + START("start"), + END("end"), + MOVE("move"), + STATUS("status"); + + private final String command; + + GameCommand(String command) { + this.command = command; + } + + public static GameCommand from(String input) { + return Arrays.stream(GameCommand.values()) + .filter(gameCommand -> gameCommand.command.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 커멘드입니다.")); + } + + public static boolean isImpossibleBeforeStartGame(GameCommand command) { + return command == MOVE || command == STATUS; + } +} diff --git a/src/main/java/chess/view/InputView.java b/src/main/java/chess/view/InputView.java new file mode 100644 index 00000000000..836613a97cf --- /dev/null +++ b/src/main/java/chess/view/InputView.java @@ -0,0 +1,62 @@ +package chess.view; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.Map; +import java.util.Scanner; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + private static final Map FILE_INPUT = 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_INPUT = 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 int POSITION_INPUT_LENGTH = 2; + private static final int FILE_INDEX = 0; + private static final int RANK_INDEX = 1; + + public GameCommand readCommand() { + String input = SCANNER.next(); + return GameCommand.from(input); + } + + public Position readPosition() { + String input = SCANNER.next(); + validatePositionInputLength(input); + + File file = convertToFile(input); + Rank rank = convertToRank(input); + + return new Position(file, rank); + } + + private void validatePositionInputLength(String input) { + if (input.length() != POSITION_INPUT_LENGTH) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + } + + private File convertToFile(String input) { + char fileInput = input.charAt(FILE_INDEX); + File file = FILE_INPUT.get(fileInput); + if (file == null) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + + return file; + } + + private Rank convertToRank(String input) { + char rankInput = input.charAt(RANK_INDEX); + Rank rank = RANK_INPUT.get(rankInput); + if (rank == null) { + throw new IllegalArgumentException("위치 형식이 올바르지 않습니다."); + } + + return rank; + } +} diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java new file mode 100644 index 00000000000..27f26e46d43 --- /dev/null +++ b/src/main/java/chess/view/OutputView.java @@ -0,0 +1,97 @@ +package chess.view; + +import chess.GameStatus; +import chess.domain.piece.PieceType; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import chess.dto.BoardDto; +import chess.dto.PieceDto; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class OutputView { + + private static final List RANK_ORDER = List.of( + 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 Map PIECE_DISPLAY = Map.of( + PieceType.KING, "K", PieceType.QUEEN, "Q", PieceType.KNIGHT, "N", + PieceType.BISHOP, "B", PieceType.ROOK, "R", PieceType.PAWN, "P"); + private static final Map WINNER_DISPLAY = Map.of( + Team.BLACK, "검은색 승리", Team.WHITE, "흰색 승리"); + private static final String EMPTY_SPACE = "."; + private static final String ERROR_PREFIX = "[ERROR] "; + + public void printStartGame() { + System.out.println(""" + > 체스 게임을 시작합니다. + > 게임 시작 : start + > 게임 종료 : end + > 게임 이동 : move source위치 target위치 - 예. move b2 b3"""); + } + + public void printBoard(BoardDto boardDto) { + for (Rank rank : RANK_ORDER) { + printBoardOneLine(boardDto, rank); + } + System.out.println(); + } + + private void printBoardOneLine(BoardDto boardDto, Rank rank) { + for (File file : FILE_ORDER) { + Optional piece = boardDto.find(new Position(file, rank)); + piece.ifPresentOrElse(this::printPiece, this::printEmptySpace); + } + System.out.println(); + } + + private void printPiece(PieceDto pieceDto) { + if (pieceDto.isBlack()) { + printBlackPiece(pieceDto.type()); + return; + } + printWhitePiece(pieceDto.type()); + } + + private void printBlackPiece(PieceType type) { + String display = PIECE_DISPLAY.get(type); + System.out.print(display.toUpperCase()); + } + + private void printWhitePiece(PieceType type) { + String display = PIECE_DISPLAY.get(type); + System.out.print(display.toLowerCase()); + } + + private void printEmptySpace() { + System.out.print(EMPTY_SPACE); + } + + public void printStatus(double blackScore, double whiteScore, Team winner) { + System.out.println("검은색: " + blackScore + ", 흰색: " + whiteScore); + System.out.println(getWinnerDisplay(winner)); + } + + public void printExceptionMessage(Exception exception) { + System.out.println(ERROR_PREFIX + exception.getMessage()); + } + + public void printWinner(GameStatus gameStatus) { + if (gameStatus == GameStatus.BLACK_WIN) { + System.out.println(getWinnerDisplay(Team.BLACK)); + return; + } + System.out.println(getWinnerDisplay(Team.WHITE)); + } + + private static String getWinnerDisplay(Team winner) { + if (winner == null) { + return "무승부"; + } + return WINNER_DISPLAY.get(winner); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/test/java/chess/GameStatusTest.java b/src/test/java/chess/GameStatusTest.java new file mode 100644 index 00000000000..8db51bf6b16 --- /dev/null +++ b/src/test/java/chess/GameStatusTest.java @@ -0,0 +1,31 @@ +package chess; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.piece.Team; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameStatusTest { + + @Test + @DisplayName("이긴 팀으로 해당 상태를 판단한다.") + void winByWhichTeamTest() { + assertAll( + () -> assertThat(GameStatus.winBy(Team.BLACK)).isEqualTo(GameStatus.BLACK_WIN), + () -> assertThat(GameStatus.winBy(Team.WHITE)).isEqualTo(GameStatus.WHITE_WIN) + ); + } + + @Test + @DisplayName("플레이 중인 상태인지 확인한다.") + void isPlayingTest() { + assertAll( + () -> assertThat(GameStatus.PLAYING.isPlaying()).isTrue(), + () -> assertThat(GameStatus.END.isPlaying()).isFalse(), + () -> assertThat(GameStatus.BLACK_WIN.isPlaying()).isFalse(), + () -> assertThat(GameStatus.WHITE_WIN.isPlaying()).isFalse() + ); + } +} diff --git a/src/test/java/chess/domain/BoardFactoryTest.java b/src/test/java/chess/domain/BoardFactoryTest.java new file mode 100644 index 00000000000..2de34a81974 --- /dev/null +++ b/src/test/java/chess/domain/BoardFactoryTest.java @@ -0,0 +1,89 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +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 org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class BoardFactoryTest { + + private static final Board BOARD = BoardFactory.createInitialBoard(); + + @ParameterizedTest + @CsvSource({ + "TWO, A", "TWO, B", "TWO, C", "TWO, D", "TWO, E", "TWO, F", "TWO, G", "TWO, H", + "SEVEN, A", "SEVEN, B", "SEVEN, C", "SEVEN, D", "SEVEN, E", "SEVEN, F", "SEVEN, G", "SEVEN, H" + }) + @DisplayName("폰이 초기 위치에 있다.") + void pawnTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Pawn.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, B", "ONE, G", "EIGHT, B", "EIGHT, G" + }) + @DisplayName("나이트가 초기 위치에 있다.") + void knightTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Knight.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, C", "ONE, F", "EIGHT, C", "EIGHT, F" + }) + @DisplayName("비숍이 초기 위치에 있다.") + void BishopTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Bishop.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, A", "ONE, H", "EIGHT, A", "EIGHT, H" + }) + @DisplayName("륙이 초기 위치에 있다.") + void rookTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Rook.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, D", "EIGHT, D" + }) + @DisplayName("퀸이 초기 위치에 있다.") + void queenTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(Queen.class); + } + + @ParameterizedTest + @CsvSource({ + "ONE, E", "EIGHT, E" + }) + @DisplayName("킹이 초기 위치에 있다.") + void kingTest(Rank rank, File file) { + Piece piece = BOARD.find(new Position(file, rank)).get(); + + assertThat(piece).isInstanceOf(King.class); + } +} diff --git a/src/test/java/chess/domain/BoardTest.java b/src/test/java/chess/domain/BoardTest.java new file mode 100644 index 00000000000..4173814a468 --- /dev/null +++ b/src/test/java/chess/domain/BoardTest.java @@ -0,0 +1,177 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class BoardTest { + + @Test + @DisplayName("특정 위치에 어떤 말이 있는지 알려준다.") + void findTest_whenPieceExist() { + Position position = new Position(File.D, Rank.TWO); + King king = new King(Team.WHITE); + Map map = Map.of(position, king); + Board board = new Board(map); + + assertThat(board.find(position)).isEqualTo(Optional.of(king)); + } + + @Test + @DisplayName("특정 위치에 어떤 말이 있는지 알려준다.") + void findTest_whenPieceNotExist() { + Position position = new Position(File.E, Rank.TWO); + King king = new King(Team.WHITE); + Map map = Map.of(position, king); + Position notExistPosition = new Position(File.D, Rank.TWO); + Board board = new Board(map); + + assertThat(board.find(notExistPosition)).isEqualTo(Optional.empty()); + } + + @Nested + @DisplayName("말 이동 테스트") + class MovingTest { + + private static final Position START_KING = new Position(File.E, Rank.FOUR); + private static final King KING = new King(Team.WHITE); + private static final Position START_QUEEN = new Position(File.E, Rank.TWO); + private static final Queen QUEEN = new Queen(Team.WHITE); + private static final Map MAP = Map.of(START_KING, KING, START_QUEEN, QUEEN); + private Board board; + + @BeforeEach + void beforeEach() { + board = new Board(MAP); + } + + @Test + @DisplayName("시작 위치에 있는 말을 도착 위치로 움직인다.") + void moveTest() { + Position possibleEnd = new Position(File.E, Rank.THREE); + + board.tryMove(START_KING, possibleEnd); + + assertAll( + () -> assertThat(board.find(possibleEnd)).isEqualTo(Optional.of(KING)), + () -> assertThat(board.find(START_KING)).isEqualTo(Optional.empty()) + ); + } + + @Test + @DisplayName("시작 위치에 말이 없을 경우, 예외가 발생한다.") + void moveTest_whenPieceNotExist_throwException() { + Position emptyPosition = new Position(File.F, Rank.EIGHT); + Position end = new Position(File.F, Rank.TWO); + + assertThatThrownBy(() -> board.tryMove(emptyPosition, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 위치에 말이 없습니다."); + } + + @Test + @DisplayName("말의 이동 반경을 벗어날 경우, 예외가 발생한다.") + void moveTest_whenOutOfMovement_throwException() { + Position impossibleEnd = new Position(File.F, Rank.EIGHT); + + assertThatThrownBy(() -> board.tryMove(START_KING, impossibleEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("이동 경로 위에 다른 말이 존재할 경우, 예외가 발생한다.") + void moveTest_whenBlocked_throwException() { + Position impossibleEnd = new Position(File.E, Rank.FIVE); + + assertThatThrownBy(() -> board.tryMove(START_QUEEN, impossibleEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("다른 말이 있어 이동 불가능합니다."); + } + } + + @Test + @DisplayName("특정 팀의 말들을 알 수 있다.") + void getPiecesOfTest() { + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.WHITE); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.BLACK); + Pawn pawn = new Pawn(Team.BLACK); + King king = new King(Team.BLACK); + Map boardMap = Map.of( + new Position(File.B, Rank.TWO), queen, + new Position(File.F, Rank.TWO), rook, + new Position(File.F, Rank.SIX), bishop, + new Position(File.D, Rank.THREE), knight, + new Position(File.E, Rank.SIX), pawn, + new Position(File.A, Rank.FOUR), king); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.getPiecesOf(Team.WHITE).toList()) + .containsExactlyInAnyOrder(queen, rook, bishop), + () -> assertThat(board.getPiecesOf(Team.BLACK).toList()) + .containsExactlyInAnyOrder(knight, pawn, king) + ); + } + + @Test + @DisplayName("특정 팀의 폰의 위치들을 알 수 있다.") + void getPawnPositionsOfTest() { + Map boardMap = new java.util.HashMap<>(Map.of( + new Position(File.B, Rank.EIGHT), new King(Team.BLACK), + new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), + new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), + new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.E, Rank.SIX), new Queen(Team.BLACK) + )); + Map addedBoard = Map.of( + new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), + new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), + new Position(File.F, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.ONE), new Rook(Team.WHITE), + new Position(File.F, Rank.ONE), new King(Team.WHITE) + ); + boardMap.putAll(addedBoard); + Board board = new Board(boardMap); + + assertAll( + () -> assertThat(board.getPawnPositionsOf(Team.BLACK).toList()) + .containsExactlyInAnyOrder( + new Position(File.A, Rank.SEVEN), + new Position(File.C, Rank.SEVEN), + new Position(File.B, Rank.SIX)), + () -> assertThat(board.getPawnPositionsOf(Team.WHITE).toList()) + .containsExactlyInAnyOrder( + new Position(File.F, Rank.THREE), + new Position(File.H, Rank.THREE), + new Position(File.F, Rank.TWO), + new Position(File.G, Rank.TWO) + ) + ); + } +} diff --git a/src/test/java/chess/domain/ScoreCalculatorTest.java b/src/test/java/chess/domain/ScoreCalculatorTest.java new file mode 100644 index 00000000000..73832087fe5 --- /dev/null +++ b/src/test/java/chess/domain/ScoreCalculatorTest.java @@ -0,0 +1,73 @@ +package chess.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.piece.Bishop; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; +import chess.domain.piece.Team; +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ScoreCalculatorTest { + + @Test + @DisplayName("점수를 계산할 수 있다.") + void calculateScoreTest() { + Map boardMap = Map.of( + new Position(File.B, Rank.TWO), new Queen(Team.WHITE), + new Position(File.F, Rank.TWO), new Rook(Team.WHITE), + new Position(File.F, Rank.SIX), new Bishop(Team.WHITE), + new Position(File.D, Rank.THREE), new Knight(Team.BLACK), + new Position(File.E, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.A, Rank.FOUR), new King(Team.BLACK)); + Board board = new Board(boardMap); + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + assertAll( + () -> assertThat(scoreCalculator.getWhiteScore()).isEqualTo(17), + () -> assertThat(scoreCalculator.getBlackScore()).isEqualTo(3.5) + ); + } + + @Test + @DisplayName("같은 팀의 폰이 같은 파일에 있을 때 점수를 계산할 수 있다.") + void calculateScoreTest_whenPawnInSameFile() { + Map boardMap = new java.util.HashMap<>(Map.of( + new Position(File.B, Rank.EIGHT), new King(Team.BLACK), + new Position(File.C, Rank.EIGHT), new Rook(Team.BLACK), + new Position(File.A, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.C, Rank.SEVEN), new Pawn(Team.BLACK), + new Position(File.D, Rank.SEVEN), new Bishop(Team.BLACK), + new Position(File.B, Rank.SIX), new Pawn(Team.BLACK), + new Position(File.E, Rank.SIX), new Queen(Team.BLACK), + new Position(File.F, Rank.FOUR), new Knight(Team.WHITE), + new Position(File.G, Rank.FOUR), new Queen(Team.WHITE), + new Position(File.F, Rank.THREE), new Pawn(Team.WHITE) + )); + Map addedBoard = Map.of( + new Position(File.H, Rank.THREE), new Pawn(Team.WHITE), + new Position(File.F, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.G, Rank.TWO), new Pawn(Team.WHITE), + new Position(File.E, Rank.ONE), new Rook(Team.WHITE), + new Position(File.F, Rank.ONE), new King(Team.WHITE) + ); + boardMap.putAll(addedBoard); + Board board = new Board(boardMap); + ScoreCalculator scoreCalculator = new ScoreCalculator(board); + + assertAll( + () -> assertThat(scoreCalculator.getWhiteScore()).isEqualTo(19.5), + () -> assertThat(scoreCalculator.getBlackScore()).isEqualTo(20) + ); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/EastMovementTest.java b/src/test/java/chess/domain/movement/continuous/EastMovementTest.java new file mode 100644 index 00000000000..b2de9a86dcd --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/EastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.FOUR); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.THREE); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.FOUR); + EastMovement eastMovement = new EastMovement(); + + assertThat(eastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.FOUR), new Position(File.E, Rank.FOUR)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java new file mode 100644 index 00000000000..57ab7ec1fd4 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthEastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthEastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.SIX); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.D, Rank.SIX); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.SEVEN); + NorthEastMovement northEastMovement = new NorthEastMovement(); + + assertThat(northEastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.FIVE), new Position(File.E, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java new file mode 100644 index 00000000000..176e5319470 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.SIX); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.SIX); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.SEVEN); + NorthMovement northMovement = new NorthMovement(); + + assertThat(northMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FIVE), new Position(File.C, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java b/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java new file mode 100644 index 00000000000..6c0fb7a2732 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/NorthWestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NorthWestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.SIX); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.SIX); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.SEVEN); + NorthWestMovement northWestMovement = new NorthWestMovement(); + + assertThat(northWestMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FIVE), new Position(File.B, Rank.SIX)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java new file mode 100644 index 00000000000..fca6518d92a --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthEastMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthEastMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.E, Rank.TWO); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.D, Rank.TWO); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.F, Rank.ONE); + SouthEastMovement southEastMovement = new SouthEastMovement(); + + assertThat(southEastMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.D, Rank.THREE), new Position(File.E, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java new file mode 100644 index 00000000000..aee841095b1 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.TWO); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.TWO); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.C, Rank.ONE); + SouthMovement southMovement = new SouthMovement(); + + assertThat(southMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.THREE), new Position(File.C, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java b/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java new file mode 100644 index 00000000000..d4f75a55ce1 --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/SouthWestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SouthWestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.TWO); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.B, Rank.TWO); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + SouthWestMovement southWestMovement = new SouthWestMovement(); + + assertThat(southWestMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.THREE), new Position(File.B, Rank.TWO)); + } +} diff --git a/src/test/java/chess/domain/movement/continuous/WestMovementTest.java b/src/test/java/chess/domain/movement/continuous/WestMovementTest.java new file mode 100644 index 00000000000..d5e44a27f7d --- /dev/null +++ b/src/test/java/chess/domain/movement/continuous/WestMovementTest.java @@ -0,0 +1,45 @@ +package chess.domain.movement.continuous; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class WestMovementTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @Test + @DisplayName("이동 불가능한지 확인한다.") + void isMovableTest_false() { + Position start = new Position(File.C, Rank.FOUR); + Position end = new Position(File.A, Rank.THREE); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + WestMovement westMovement = new WestMovement(); + + assertThat(westMovement.findPath(start, end, HAS_ENEMY)) + .containsExactly(new Position(File.C, Rank.FOUR), new Position(File.B, Rank.FOUR)); + } +} diff --git a/src/test/java/chess/domain/movement/discrete/KingMovementTest.java b/src/test/java/chess/domain/movement/discrete/KingMovementTest.java new file mode 100644 index 00000000000..dd964c94f4f --- /dev/null +++ b/src/test/java/chess/domain/movement/discrete/KingMovementTest.java @@ -0,0 +1,49 @@ +package chess.domain.movement.discrete; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 KingMovementTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"D, FIVE", "E, FIVE", "E, FOUR", "E, THREE", "D, THREE", "C, THREE", "C, FOUR", "C, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "B, TWO", "F, TWO"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + KingMovement kingMovement = new KingMovement(); + + assertThat(kingMovement.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java b/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java new file mode 100644 index 00000000000..b02ebb0ea02 --- /dev/null +++ b/src/test/java/chess/domain/movement/discrete/KnightMovementTest.java @@ -0,0 +1,49 @@ +package chess.domain.movement.discrete; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 KnightMovementTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"B, FIVE", "C, SIX", "E, SIX", "F, FIVE", "B, THREE", "C, TWO", "E, TWO", "F, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.isMovable(start, end, HAS_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "B, SIX", "F, SIX", "E, FIVE", "C, THREE", "B, TWO", "F, TWO", "E, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.isMovable(start, end, HAS_ENEMY)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.B, Rank.FIVE); + KnightMovement knightMovement = new KnightMovement(); + + assertThat(knightMovement.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java new file mode 100644 index 00000000000..e6b7b3c1848 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDefaultMovementTest.java @@ -0,0 +1,59 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 BlackPawnDefaultMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.THREE); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, TWO", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, Rank.FOUR); + Position end = start.moveToSouth(); + boolean hasEnemy = true; + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.THREE); + BlackPawnDefaultMovement blackPawnDefaultMovement = new BlackPawnDefaultMovement(); + + assertThat(blackPawnDefaultMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java new file mode 100644 index 00000000000..1999fb81f99 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnDiagonalMovementTest.java @@ -0,0 +1,61 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 BlackPawnDiagonalMovementTest { + + private static final boolean EXIST_ENEMY = true; + + @ParameterizedTest + @CsvSource({"C, THREE", "E, THREE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, TWO", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, THREE", "E, THREE"}) + @DisplayName("도착 위치에 적이 없을 경우, 이동 불가능하다.") + void isMovableTest_notExistEnemy(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.E, Rank.THREE); + BlackPawnDiagonalMovement blackPawnDiagonalMovement = new BlackPawnDiagonalMovement(); + + assertThat(blackPawnDiagonalMovement.findPath(start, end, EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java new file mode 100644 index 00000000000..c8a6dc047b0 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/BlackPawnFirstMovementTest.java @@ -0,0 +1,72 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 BlackPawnFirstMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + private static final Rank AVAILABLE_START_RANK = Rank.SEVEN; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(File.D, Rank.FIVE); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, SEVEN", "D, SIX", "D, FOUR", "D, EIGHT"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(file, rank); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "D, SIX", "D, EIGHT"}) + @DisplayName("초기 위치가 아닐 경우, 이동 불가능하다.") + void isMovableTest_notMatchInitialPosition(File file, Rank rank) { + Position start = new Position(file, rank); + Position end = start.moveToSouth().moveToSouth(); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, AVAILABLE_START_RANK); + Position end = start.moveToSouth().moveToSouth(); + boolean hasEnemy = true; + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position middle = new Position(File.D, Rank.SIX); + Position end = new Position(File.D, Rank.FIVE); + BlackPawnFirstMovement blackPawnFirstMovement = new BlackPawnFirstMovement(); + + assertThat(blackPawnFirstMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .containsExactly(middle); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java new file mode 100644 index 00000000000..47fc7abcd0f --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDefaultMovementTest.java @@ -0,0 +1,59 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 WhitePawnDefaultMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, THREE", "D, SIX"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, Rank.FOUR); + Position end = start.moveToNorth(); + boolean hasEnemy = true; + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDefaultMovement whitePawnDefaultMovement = new WhitePawnDefaultMovement(); + + assertThat(whitePawnDefaultMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java new file mode 100644 index 00000000000..f42b32d87fd --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnDiagonalMovementTest.java @@ -0,0 +1,61 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 WhitePawnDiagonalMovementTest { + + private static final boolean EXIST_ENEMY = true; + + @ParameterizedTest + @CsvSource({"C, FIVE", "E, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"B, SIX", "F, SIX", "D, THREE", "D, SIX"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, FIVE", "E, FIVE"}) + @DisplayName("도착 위치에 적이 없을 경우, 이동 불가능하다.") + void isMovableTest_notExistEnemy(File file, Rank rank) { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.D, Rank.FIVE); + WhitePawnDiagonalMovement whitePawnDiagonalMovement = new WhitePawnDiagonalMovement(); + + assertThat(whitePawnDiagonalMovement.findPath(start, end, EXIST_ENEMY)) + .isEmpty(); + } +} diff --git a/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java new file mode 100644 index 00000000000..dc87f503593 --- /dev/null +++ b/src/test/java/chess/domain/movement/pawn/WhitePawnFirstMovementTest.java @@ -0,0 +1,72 @@ +package chess.domain.movement.pawn; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 WhitePawnFirstMovementTest { + + private static final boolean NOT_EXIST_ENEMY = false; + private static final Rank AVAILABLE_START_RANK = Rank.TWO; + + @Test + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest() { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(File.D, Rank.FOUR); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"C, SEVEN", "D, ONE", "D, THREE", "D, FIVE"}) + @DisplayName("이동 가능한지 확인한다.") + void isMovableTest_false(File file, Rank rank) { + Position start = new Position(File.D, AVAILABLE_START_RANK); + Position end = new Position(file, rank); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @ParameterizedTest + @CsvSource({"C, ONE", "D, THREE", "D, FOUR"}) + @DisplayName("초기 위치가 아닐 경우, 이동 불가능하다.") + void isMovableTest_notMatchInitialPosition(File file, Rank rank) { + Position start = new Position(file, rank); + Position end = start.moveToNorth().moveToNorth(); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, NOT_EXIST_ENEMY)).isFalse(); + } + + @Test + @DisplayName("도착 위치에 적이 있을 경우, 이동 불가능하다.") + void isMovableTest_existEnemy() { + Position start = new Position(File.B, AVAILABLE_START_RANK); + Position end = start.moveToNorth().moveToNorth(); + boolean hasEnemy = true; + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.isMovable(start, end, hasEnemy)).isFalse(); + } + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Position start = new Position(File.D, Rank.TWO); + Position middle = new Position(File.D, Rank.THREE); + Position end = new Position(File.D, Rank.FOUR); + WhitePawnFirstMovement whitePawnFirstMovement = new WhitePawnFirstMovement(); + + assertThat(whitePawnFirstMovement.findPath(start, end, NOT_EXIST_ENEMY)) + .containsExactly(middle); + } +} diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java new file mode 100644 index 00000000000..f90da85c424 --- /dev/null +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 BishopTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + + assertThat(bishop.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.THREE), + new Position(File.B, Rank.TWO)); + } + + @ParameterizedTest + @CsvSource({"E, FIVE", "F, SIX", "G, ONE", "B, TWO", "E, THREE", "G, SEVEN", "A, ONE", "C, THREE"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> bishop.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"D, TWO", "F, FOUR", "C, TWO", "E, SIX"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Bishop bishop = new Bishop(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> bishop.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java new file mode 100644 index 00000000000..80d8ac174b3 --- /dev/null +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -0,0 +1,41 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KingTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"F, THREE", "F, FIVE", "E, THREE", "E, FOUR", "E, FIVE", "G, THREE", "G, FOUR", "G, FIVE"}) + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest(File file, Rank rank) { + King king = new King(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThat(king.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } + + @ParameterizedTest + @CsvSource({"F, TWO", "F, SIX", "D, THREE", "H, FOUR"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + King king = new King(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> king.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java new file mode 100644 index 00000000000..62fe8de4fcb --- /dev/null +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -0,0 +1,41 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class KnightTest { + + private static final boolean HAS_ENEMY = false; + + @ParameterizedTest + @CsvSource({"G, SIX", "H, FIVE", "H, THREE", "G, TWO", "E, TWO", "D, THREE", "D, FIVE", "E, SIX"}) + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest(File file, Rank rank) { + Knight knight = new Knight(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThat(knight.findPath(start, end, HAS_ENEMY)) + .isEmpty(); + } + + @ParameterizedTest + @CsvSource({"G, FIVE", "H, FOUR", "D, TWO", "E, ONE"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Knight knight = new Knight(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> knight.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java new file mode 100644 index 00000000000..22ce3a0669c --- /dev/null +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -0,0 +1,170 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 PawnTest { + + @Test + @DisplayName("검정 폰이 초기 위치에 있고 목적지에 적이 없을 경우, 남쪽으로 한 칸 또는 두 칸 이동한다.") + void findPathTest_whenBlackPawnInitPosition() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.SEVEN); + boolean hasEnemy = false; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.SIX), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .containsExactly(new Position(File.F, Rank.SIX)) + ); + } + + @Test + @DisplayName("검정 폰이 초기 위치가 아닌 곳에 있고 목적지에 적이 없을 경우, 남쪽으로 한 칸 이동한다.") + void findPathTest_whenBlackPawnNotInitPosition() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.SIX); + boolean hasEnemy = false; + + assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .isEmpty(); + } + + @Test + @DisplayName("검정 폰의 목적지에 적이 있을 경우, 남쪽으로 이동할 수 없다.") + void findPathTest_whenMoveToSouthExistEnemy_throwException() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertThatThrownBy(() -> pawn.findPath(start, start.moveToSouth(), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("검정 폰이 목적지에 적이 있을 경우, 남동쪽 혹은 남서쪽으로 한 칸 이동한다.") + void findPathTest_whenBlackPawnHaveEnemy() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.E, Rank.THREE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.G, Rank.THREE), hasEnemy)) + .isEmpty() + ); + } + + @Test + @DisplayName("검정 폰이 목적지에 적이 없을 경우, 남동쪽 혹은 남서쪽으로 한 칸 이동할 수 없다.") + void findPathTest_whenBlackPawnMoveDiagonalNotExistEnemy() { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertAll( + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.E, Rank.THREE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."), + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.G, Rank.THREE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다.") + ); + } + + @Test + @DisplayName("흰 폰이 초기 위치에 있고 목적지에 적이 없을 경우, 북쪽으로 한 칸 또는 두 칸 이동한다.") + void findPathTest_whenWhitePawnInitPosition() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.TWO); + boolean hasEnemy = false; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.THREE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.F, Rank.FOUR), hasEnemy)) + .containsExactly(new Position(File.F, Rank.THREE)) + ); + } + + @Test + @DisplayName("흰 폰이 초기 위치가 아닌 곳에 있고 목적지에 적이 없을 경우, 북쪽으로 한 칸 이동한다.") + void findPathTest_whenWhitePawnNotInitPosition() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertThat(pawn.findPath(start, new Position(File.F, Rank.FIVE), hasEnemy)) + .isEmpty(); + } + + @Test + @DisplayName("흰 폰의 목적지에 적이 있을 경우, 북쪽으로 이동할 수 없다.") + void findPathTest_whenWhitePawnMoveToSouthExistEnemy_throwException() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertThatThrownBy(() -> pawn.findPath(start, start.moveToNorth(), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } + + @Test + @DisplayName("흰 폰이 목적지에 적이 있을 경우, 북동쪽 혹은 북서쪽으로 한 칸 이동한다.") + void findPathTest_whenWhitePawnHaveEnemy() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = true; + + assertAll( + () -> assertThat(pawn.findPath(start, new Position(File.E, Rank.FIVE), hasEnemy)) + .isEmpty(), + () -> assertThat(pawn.findPath(start, new Position(File.G, Rank.FIVE), hasEnemy)) + .isEmpty() + ); + } + + @Test + @DisplayName("흰 폰이 목적지에 적이 없을 경우, 북동쪽 혹은 북서쪽으로 한 칸 이동할 수 없다.") + void findPathTest_whenWhitePawnMoveDiagonalNotExistEnemy() { + Pawn pawn = new Pawn(Team.WHITE); + Position start = new Position(File.F, Rank.FOUR); + boolean hasEnemy = false; + + assertAll( + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.E, Rank.FIVE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."), + () -> assertThatThrownBy(() -> pawn.findPath(start, new Position(File.G, Rank.FIVE), hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다.") + ); + } + + @ParameterizedTest + @CsvSource({"G, FOUR", "F, FIVE", "F, TWO"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Pawn pawn = new Pawn(Team.BLACK); + Position start = new Position(File.F, Rank.FOUR); + Position end = new Position(file, rank); + boolean hasEnemy = false; + + assertThatThrownBy(() -> pawn.findPath(start, end, hasEnemy)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java new file mode 100644 index 00000000000..adaab379813 --- /dev/null +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -0,0 +1,78 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +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 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 examplePiece = new ExamplePiece(team); + + assertThat(examplePiece.isBlackTeam()).isEqualTo(expected); + } + + @Test + @DisplayName("해당 기물이 킹인지 확인한다.") + void isKingTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + ExamplePiece examplePiece = new ExamplePiece(Team.WHITE); + + assertAll( + () -> assertThat(king.isKing()).isTrue(), + () -> assertThat(queen.isKing()).isFalse(), + () -> assertThat(rook.isKing()).isFalse(), + () -> assertThat(bishop.isKing()).isFalse(), + () -> assertThat(knight.isKing()).isFalse(), + () -> assertThat(pawn.isKing()).isFalse(), + () -> assertThatThrownBy(examplePiece::isKing) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 기물이 존재하지 않습니다.") + ); + } + + @Test + @DisplayName("해당 기물이 폰인지 확인한다.") + void isPawnTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + ExamplePiece examplePiece = new ExamplePiece(Team.WHITE); + + assertAll( + () -> assertThat(king.isPawn()).isFalse(), + () -> assertThat(queen.isPawn()).isFalse(), + () -> assertThat(rook.isPawn()).isFalse(), + () -> assertThat(bishop.isPawn()).isFalse(), + () -> assertThat(knight.isPawn()).isFalse(), + () -> assertThat(pawn.isPawn()).isTrue(), + () -> assertThatThrownBy(examplePiece::isPawn) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("해당 기물이 존재하지 않습니다.") + ); + } +} diff --git a/src/test/java/chess/domain/piece/PieceTypeTest.java b/src/test/java/chess/domain/piece/PieceTypeTest.java new file mode 100644 index 00000000000..649766a1073 --- /dev/null +++ b/src/test/java/chess/domain/piece/PieceTypeTest.java @@ -0,0 +1,50 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PieceTypeTest { + + @Test + @DisplayName("기물의 타입을 알 수 있다.") + void findTypeTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + + assertAll( + () -> assertThat(PieceType.from(king)).isEqualTo(PieceType.KING), + () -> assertThat(PieceType.from(queen)).isEqualTo(PieceType.QUEEN), + () -> assertThat(PieceType.from(rook)).isEqualTo(PieceType.ROOK), + () -> assertThat(PieceType.from(bishop)).isEqualTo(PieceType.BISHOP), + () -> assertThat(PieceType.from(knight)).isEqualTo(PieceType.KNIGHT), + () -> assertThat(PieceType.from(pawn)).isEqualTo(PieceType.PAWN) + ); + } + + @Test + @DisplayName("기물의 점수를 알 수 있다.") + void findScoreTest() { + King king = new King(Team.BLACK); + Queen queen = new Queen(Team.WHITE); + Rook rook = new Rook(Team.BLACK); + Bishop bishop = new Bishop(Team.WHITE); + Knight knight = new Knight(Team.WHITE); + Pawn pawn = new Pawn(Team.BLACK); + + assertAll( + () -> assertThat(PieceType.scoreOf(king)).isEqualTo(0), + () -> assertThat(PieceType.scoreOf(queen)).isEqualTo(9), + () -> assertThat(PieceType.scoreOf(rook)).isEqualTo(5), + () -> assertThat(PieceType.scoreOf(bishop)).isEqualTo(3), + () -> assertThat(PieceType.scoreOf(knight)).isEqualTo(2.5), + () -> assertThat(PieceType.scoreOf(pawn)).isEqualTo(1) + ); + } +} diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java new file mode 100644 index 00000000000..a2844a5ddb0 --- /dev/null +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 QueenTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.ONE); + + assertThat(queen.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.THREE), + new Position(File.B, Rank.TWO)); + } + + @ParameterizedTest + @CsvSource({"F, SIX", "G, FOUR", "F, TWO", "D, TWO", "B, TWO", "B, FOUR", "B, SIX", "D, SIX"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> queen.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"B, FIVE", "B, ONE", "C, TWO", "E, SIX"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Queen queen = new Queen(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> queen.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java new file mode 100644 index 00000000000..a2fee045b9a --- /dev/null +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.position.File; +import chess.domain.position.Position; +import chess.domain.position.Rank; +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 RookTest { + + private static final boolean HAS_ENEMY = false; + + @Test + @DisplayName("이동 경로를 알 수 있다.") + void findPathTest() { + Rook rook = new Rook(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(File.A, Rank.FOUR); + + assertThat(rook.findPath(start, end, HAS_ENEMY)) + .containsExactly( + new Position(File.C, Rank.FOUR), + new Position(File.B, Rank.FOUR)); + } + + @ParameterizedTest + @CsvSource({"D, FIVE", "D, SIX", "D, ONE", "D, TWO", "E, FOUR", "G, FOUR", "A, FOUR", "C, FOUR"}) + @DisplayName("이동이 가능한 경우, 예외를 발생하지 않는다.") + void findPathTest_whenCanMove(File file, Rank rank) { + Rook rook = new Rook(Team.BLACK); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatCode(() -> rook.findPath(start, end, HAS_ENEMY)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @CsvSource({"F, TWO", "F, SIX", "C, THREE", "E, FIVE"}) + @DisplayName("이동할 수 없는 곳인 경우, 예외가 발생한다.") + void findPathTest_whenOutOfMovement_throwException(File file, Rank rank) { + Rook rook = new Rook(Team.WHITE); + Position start = new Position(File.D, Rank.FOUR); + Position end = new Position(file, rank); + + assertThatThrownBy(() -> rook.findPath(start, end, HAS_ENEMY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("불가능한 경로입니다."); + } +} diff --git a/src/test/java/chess/domain/piece/TeamTest.java b/src/test/java/chess/domain/piece/TeamTest.java new file mode 100644 index 00000000000..9f99e3d36eb --- /dev/null +++ b/src/test/java/chess/domain/piece/TeamTest.java @@ -0,0 +1,19 @@ +package chess.domain.piece; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class TeamTest { + + @Test + @DisplayName("다음 팀을 찾을 수 있다.") + void findNextTeamTest() { + assertAll( + () -> assertThat(Team.BLACK.next()).isEqualTo(Team.WHITE), + () -> assertThat(Team.WHITE.next()).isEqualTo(Team.BLACK) + ); + } +} diff --git a/src/test/java/chess/domain/position/FileTest.java b/src/test/java/chess/domain/position/FileTest.java new file mode 100644 index 00000000000..adfed56ee0f --- /dev/null +++ b/src/test/java/chess/domain/position/FileTest.java @@ -0,0 +1,49 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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 FileTest { + + @ParameterizedTest + @CsvSource({"G, H", "A, B"}) + @DisplayName("다음 동쪽 위치를 알 수 있다.") + void toEastTest(File before, File after) { + assertThat(before.toEast()).isEqualTo(after); + } + + @Test + @DisplayName("동쪽으로 갈 수 없으면, 예외가 발생한다.") + void toEastTest_whenCant() { + assertThatThrownBy(File.H::toEast) + .isInstanceOf(IllegalStateException.class) + .hasMessage("동쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"H, G", "B, A"}) + @DisplayName("다음 서쪽 위치를 알 수 있다.") + void toWestTest(File before, File after) { + assertThat(before.toWest()).isEqualTo(after); + } + + @Test + @DisplayName("서쪽으로 갈 수 없으면, 예외가 발생한다.") + void toWestTest_whenCant() { + assertThatThrownBy(File.A::toWest) + .isInstanceOf(IllegalStateException.class) + .hasMessage("서쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"A, H, 7", "H, A, -7", "C, D, 1", "D, C, -1"}) + @DisplayName("두 파일의 차이를 알 수 있다.") + void calculateDifferenceTest(File before, File after, int expected) { + assertThat(before.calculateDifference(after)).isEqualTo(expected); + } +} diff --git a/src/test/java/chess/domain/position/PositionTest.java b/src/test/java/chess/domain/position/PositionTest.java new file mode 100644 index 00000000000..0d43b436d01 --- /dev/null +++ b/src/test/java/chess/domain/position/PositionTest.java @@ -0,0 +1,62 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PositionTest { + + @Test + @DisplayName("같은 행, 같은 열을 가르키면 같은 객체라고 판단한다.") + void equalsTest() { + Position one = new Position(File.A, Rank.SIX); + Position another = new Position(File.A, Rank.SIX); + Position different = new Position(File.C, Rank.SIX); + + assertThat(one) + .isEqualTo(another) + .isNotEqualTo(different) + .hasSameHashCodeAs(another); + } + + @Test + @DisplayName("동, 서, 남, 북으로 이동한 위치를 알 수 있다.") + void moveTest() { + Position position = new Position(File.D, Rank.FOUR); + + assertAll( + () -> assertThat(position.moveToEast()).isEqualTo(new Position(File.E, Rank.FOUR)), + () -> assertThat(position.moveToWest()).isEqualTo(new Position(File.C, Rank.FOUR)), + () -> assertThat(position.moveToSouth()).isEqualTo(new Position(File.D, Rank.THREE)), + () -> assertThat(position.moveToNorth()).isEqualTo(new Position(File.D, Rank.FIVE)) + ); + } + + @Test + @DisplayName("서로 같은 랭크인지 판단한다.") + void isSameRankTest() { + Position sample = new Position(File.F, Rank.FOUR); + Rank sameRank = Rank.FOUR; + Rank differentRank = Rank.SIX; + + assertAll( + () -> assertThat(sample.isOnSameRank(sameRank)).isTrue(), + () -> assertThat(sample.isOnSameRank(differentRank)).isFalse() + ); + } + + @Test + @DisplayName("서로 같은 파일인지 판단한다.") + void isSameFileTest() { + Position sample = new Position(File.F, Rank.FOUR); + Position sameFile = new Position(File.F, Rank.SIX); + Position differentFile = new Position(File.A, Rank.FOUR); + + assertAll( + () -> assertThat(sample.isOnSameFile(sameFile)).isTrue(), + () -> assertThat(sample.isOnSameFile(differentFile)).isFalse() + ); + } +} diff --git a/src/test/java/chess/domain/position/RankTest.java b/src/test/java/chess/domain/position/RankTest.java new file mode 100644 index 00000000000..d8b959fb2c3 --- /dev/null +++ b/src/test/java/chess/domain/position/RankTest.java @@ -0,0 +1,49 @@ +package chess.domain.position; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +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 RankTest { + + @ParameterizedTest + @CsvSource({"SEVEN, EIGHT", "ONE, TWO"}) + @DisplayName("다음 북쪽 위치를 알 수 있다.") + void toNorthTest(Rank before, Rank after) { + assertThat(before.toNorth()).isEqualTo(after); + } + + @Test + @DisplayName("북쪽으로 갈 수 없으면, 예외가 발생한다.") + void toNorthTest_whenCant() { + assertThatThrownBy(Rank.EIGHT::toNorth) + .isInstanceOf(IllegalStateException.class) + .hasMessage("북쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"EIGHT, SEVEN", "TWO, ONE"}) + @DisplayName("다음 남쪽 위치를 알 수 있다.") + void toSouthTest(Rank before, Rank after) { + assertThat(before.toSouth()).isEqualTo(after); + } + + @Test + @DisplayName("남쪽으로 갈 수 없으면, 예외가 발생한다.") + void toSouthTest_whenCant() { + assertThatThrownBy(Rank.ONE::toSouth) + .isInstanceOf(IllegalStateException.class) + .hasMessage("남쪽으로 이동할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"ONE, EIGHT, 7", "EIGHT, ONE, -7", "THREE, FOUR, 1", "FOUR, THREE, -1"}) + @DisplayName("두 랭크의 차이를 알 수 있다.") + void calculateDifferenceTest(Rank before, Rank after, int expected) { + assertThat(before.calculateDifference(after)).isEqualTo(expected); + } +} diff --git a/src/test/java/chess/view/GameCommandTest.java b/src/test/java/chess/view/GameCommandTest.java new file mode 100644 index 00000000000..4ffb94b5fb1 --- /dev/null +++ b/src/test/java/chess/view/GameCommandTest.java @@ -0,0 +1,41 @@ +package chess.view; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameCommandTest { + + @Test + @DisplayName("해당하는 커맨드를 찾는다.") + void findCommandFromStringTest() { + assertAll( + () -> assertThat(GameCommand.from("start")).isEqualTo(GameCommand.START), + () -> assertThat(GameCommand.from("end")).isEqualTo(GameCommand.END), + () -> assertThat(GameCommand.from("move")).isEqualTo(GameCommand.MOVE), + () -> assertThat(GameCommand.from("status")).isEqualTo(GameCommand.STATUS) + ); + } + + @Test + @DisplayName("해당하는 커맨드가 없으면 예외를 발생시킨다.") + void findCommandFromStringTest_whenNotExist() { + assertThatThrownBy(() -> GameCommand.from("notExistCommand")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("존재하지 않는 커멘드입니다."); + } + + @Test + @DisplayName("게임 시작 전 불가능한 커맨드인지 확인한다.") + void isImpossibleCommandBeforeStartGameTest() { + assertAll( + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.START)).isFalse(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.END)).isFalse(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.MOVE)).isTrue(), + () -> assertThat(GameCommand.isImpossibleBeforeStartGame(GameCommand.STATUS)).isTrue() + ); + } +}