diff --git a/README.md b/README.md index 8102f91c870..6b7e68795d0 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,56 @@ ## 우아한테크코스 코드리뷰 - [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) + + +# 기능 구현 목록 + +## 체스 게임 +- [X] : 체스 게임 시작 문구를 출력한다. +- [X] : 사용자는 체스 게임 방을 생성할 수 있다. +- [X] : 체스 게임 방 목록중 원하는 방을 선택해서 게임을 시작할 수 있다. +- [X] : 시작 명령어를 입력 받는다. + - [X] : 명령어 형식이 올바르지 않으면 예외를 터트리고 재입력 받는다. + - [X] : start 입력 시 게임을 시작한다. + - [X] : end 입력 시 게임을 종료한다. + - [X] : status 입력 시 각 팀의 점수와 어느 팀이 우세한지 출력한다. + - [X] : move 명령어를 통해 기물을 움직일 수 있다. +- [X] : 보드판을 초기화한다. + - [X] : 소문자, 대문자로 팀을 구분한다. + - [X] : 각 팀은 총 16개의 말을 갖는다. + - [X] : 폰 8개, 룩 2개, 나이트 2개, 비숍 2개, 퀸 1개, 킹 1개 +- [X] : 왕이 잡히면 게임이 종료되고 게임 결과를 출력한다. + + +## 체스 기물 +### 방향 + - [X] : 북, 북동, 동, 동남, 남, 남서, 서, 북서쪽의 총 8방향을 갖는다. + - [X] : 방향에 따라 방향 가중치 (1, -1, 0)을 갖는다. + +### 위치 + - [X] : 행, 열 enum을 갖는다. + - [X] : 현재 위치를 기준으로 방향에 따른 최대 가중치를 계산한다. + - [X] : 기물이 고유로 갖는 이동 방향과 가중치를 이용하여 이동할 수 있는 위치들을 반환한다. + +### 기물 + - [X] : 기물은 기물 타입을 갖는다. + - [X] : 기물은 팀 색깔을 갖는다. + +### 기물 타입 + - [X] : 기물 타입은 이동 전략을 갖는다. + - [X] : 각 기물의 이동 전략 구현체는 기물의 현재 위치, 방향, 가중치를 가지고 이동할 수 있는 후보 위치들을 계산한다. + - [X] : 룩은 기본적으로 북, 동, 남, 서 방향으로 이동할 수 있다. + - [X] : 비숍은 기본적으로 북동, 동남, 남서, 북서 방향으로 이동할 수 있다. + - [X] : 퀸은 기본적으로 모든 방향으로 이동할 수 있다. + - [X] : 킹은 기본적으로 모든 방향으로 이동할 수 있다. + - [X] : 폰은 기본적으로 팀 색깔에 따라 북 또는 남 방향으로만 이동할 수 있다. + - [X] : 나이트는 기본적으로 북북동, 동북동, 동남동, 남남동, 남남서, 서남서, 서북서, 북북서 방향으로 이동할 수 있다. + +## 기물 이동 + - [X] : 각 기물이 생성한 후보 위치를 이용하여 실제 이동 가능한 위치인지 판단한다. + - [X] : 위치에 기물이 존재하지 않으면 이동 가능한 위치이므로 포함한다. + - [X] : 위치에 우리팀 기물이 존재할 경우 이동할 수 없는 위치이므로 포함하지 않고 다음 방향으로 넘어간다. + - [X] : 위치에 상대팀 기물이 존재할 경우 이동가능한 위치이므로 포함하고 다음 방향으로 넘어간다. + - [X] : 명령어 입력으로 들어온 목적지 위치가 실제 이동 가능한 위치인지 판단한다. + - [X] : 실제 이동 가능한 위치에 포함되어 있는 경우 이동한다. + - [X] : 실제 이동 가능한 위치에 포함되어 있지 않은 경우 에러를 발생시킨다. diff --git a/build.gradle b/build.gradle index 3697236c6fb..20ad08a5a5e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,8 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + + runtimeOnly("com.mysql:mysql-connector-j:8.3.0") } java { diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/chess/Application.java b/src/main/java/chess/Application.java new file mode 100644 index 00000000000..547cde9e180 --- /dev/null +++ b/src/main/java/chess/Application.java @@ -0,0 +1,21 @@ +package chess; + +import chess.controller.ChessGameController; +import chess.repository.BoardRepository; +import chess.repository.BoardRepositoryImpl; +import chess.repository.RoomRepository; +import chess.repository.RoomRepositoryImpl; +import chess.service.BoardService; +import chess.service.GameService; + +public class Application { + public static void main(String[] args) { + RoomRepository roomRepository = new RoomRepositoryImpl(); + BoardRepository boardRepository = new BoardRepositoryImpl(); + + GameService gameService = new GameService(roomRepository, boardRepository); + BoardService boardService = new BoardService(roomRepository, boardRepository); + ChessGameController chessGameController = new ChessGameController(gameService, boardService); + chessGameController.run(); + } +} diff --git a/src/main/java/chess/controller/ChessGameController.java b/src/main/java/chess/controller/ChessGameController.java new file mode 100644 index 00000000000..94deab6a7e0 --- /dev/null +++ b/src/main/java/chess/controller/ChessGameController.java @@ -0,0 +1,54 @@ +package chess.controller; + +import chess.controller.command.Command; +import chess.controller.command.CommandRouter; +import chess.domain.game.Room; +import chess.service.BoardService; +import chess.service.GameService; +import chess.view.InputView; +import chess.view.OutputView; + +public class ChessGameController { + + private final GameService gameService; + private final BoardService boardService; + + public ChessGameController(GameService gameService, BoardService boardService) { + this.gameService = gameService; + this.boardService = boardService; + } + + public void run() { + OutputView.printRoomNames(gameService.findAllRoomNames()); + Room room = createRoom(); + OutputView.printStartMessage(room.getName()); + process(gameService, boardService, room.getId()); + } + + private Room createRoom() { + try { + String input = InputView.readRoomName(); + return gameService.loadRoom(input); + } catch (RuntimeException error) { + OutputView.printError(error); + return createRoom(); + } + } + + private void process(GameService gameService, BoardService boardService, Long roomId) { + State state = State.RUNNING; + do { + state = executeCommand(gameService, boardService, state, roomId); + } while (state != State.END); + } + + private State executeCommand(GameService gameService, BoardService boardService, State state, Long roomId) { + try { + Command command = CommandRouter.findCommendByInput(InputView.readCommend()); + return command.execute(gameService, boardService, roomId); + } catch (RuntimeException error) { + OutputView.printError(error); + return state; + } + } +} diff --git a/src/main/java/chess/controller/State.java b/src/main/java/chess/controller/State.java new file mode 100644 index 00000000000..c510850380a --- /dev/null +++ b/src/main/java/chess/controller/State.java @@ -0,0 +1,7 @@ +package chess.controller; + +public enum State { + + RUNNING, + END +} diff --git a/src/main/java/chess/controller/command/Command.java b/src/main/java/chess/controller/command/Command.java new file mode 100644 index 00000000000..361d66a6aea --- /dev/null +++ b/src/main/java/chess/controller/command/Command.java @@ -0,0 +1,10 @@ +package chess.controller.command; + +import chess.controller.State; +import chess.service.BoardService; +import chess.service.GameService; + +public interface Command { + + State execute(GameService gameService, BoardService boardService, Long roomId); +} diff --git a/src/main/java/chess/controller/command/CommandRouter.java b/src/main/java/chess/controller/command/CommandRouter.java new file mode 100644 index 00000000000..c05e8297058 --- /dev/null +++ b/src/main/java/chess/controller/command/CommandRouter.java @@ -0,0 +1,34 @@ +package chess.controller.command; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public enum CommandRouter { + START("start", Start::new), + END("end", End::new), + MOVE("move", Move::new), + STATUS("status", Status::new); + + private static final int COMMAND_INDEX = 0; + + private final String value; + private final Function, Command> command; + + CommandRouter(String value, + Function, Command> command) { + this.value = value; + this.command = command; + } + + public static Command findCommendByInput(List commandInput) { + if (commandInput == null || commandInput.size() == 0) { + throw new IllegalArgumentException("빈 값 입력을 허용하지 않습니다."); + } + return Arrays.stream(values()) + .filter(commandRouter -> commandRouter.value.equals(commandInput.get(COMMAND_INDEX))) + .map(commandRouter -> commandRouter.command.apply(commandInput)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("올바르지 않은 명령어 입력입니다.")); + } +} diff --git a/src/main/java/chess/controller/command/End.java b/src/main/java/chess/controller/command/End.java new file mode 100644 index 00000000000..860b745f4fc --- /dev/null +++ b/src/main/java/chess/controller/command/End.java @@ -0,0 +1,22 @@ +package chess.controller.command; + +import chess.controller.State; +import chess.service.BoardService; +import chess.service.GameService; +import java.util.List; + +public class End implements Command { + + private static final int END_COMMAND_SIZE = 1; + + public End(List commandInput) { + if (commandInput.size() != END_COMMAND_SIZE) { + throw new IllegalArgumentException("게임 종료 명령어 입력 형식이 올바르지 않습니다."); + } + } + + @Override + public State execute(GameService gameService, BoardService boardService, Long roomId) { + return State.END; + } +} diff --git a/src/main/java/chess/controller/command/Move.java b/src/main/java/chess/controller/command/Move.java new file mode 100644 index 00000000000..aa1b012ab49 --- /dev/null +++ b/src/main/java/chess/controller/command/Move.java @@ -0,0 +1,61 @@ +package chess.controller.command; + +import chess.controller.State; +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.service.BoardService; +import chess.service.GameService; +import chess.service.dto.ChessGameResult; +import chess.view.OutputView; +import chess.view.mapper.ColumnMapper; +import chess.view.mapper.RowMapper; +import java.util.List; +import java.util.regex.Pattern; + +public class Move implements Command { + + private static final Pattern POSITION_REGEX = Pattern.compile("" + + "move\\s+([a-h][1-8])\\s+([a-h][1-8])"); + + private final Position from; + private final Position to; + + public Move(List commandInput) { + validateMoveCommandPattern(commandInput); + String fromPosition = commandInput.get(1); + String toPosition = commandInput.get(2); + this.from = createPosition(fromPosition.substring(0, 1), fromPosition.substring(1, 2)); + this.to = createPosition(toPosition.substring(0, 1), toPosition.substring(1, 2)); + } + + private void validateMoveCommandPattern(List commandInput) { + String moveCommand = String.join(" ", commandInput); + if (!POSITION_REGEX.matcher(moveCommand).matches()) { + throw new IllegalArgumentException("게임 이동 명령어 입력 형식이 올바르지 않습니다."); + } + } + + @Override + public State execute(GameService gameService, BoardService boardService, Long roomId) { + if (boardService.isCheckmate(to, roomId)) { + moveAndPrintBoard(gameService, boardService, roomId); + ChessGameResult chessGameResult = gameService.generateGameResult(roomId); + OutputView.printChessGameResult(chessGameResult); + return State.END; + } + moveAndPrintBoard(gameService, boardService, roomId); + return State.RUNNING; + } + + private void moveAndPrintBoard(GameService gameService, BoardService boardService, Long roomId) { + boardService.movePiece(from, to, roomId); + OutputView.printBoard(boardService.getAllPieces(roomId)); + } + + private Position createPosition(String requestColumn, String requestRow) { + Column column = ColumnMapper.findByInputValue(requestColumn); + Row row = RowMapper.findByInputValue(requestRow); + return new Position(row, column); + } +} diff --git a/src/main/java/chess/controller/command/Start.java b/src/main/java/chess/controller/command/Start.java new file mode 100644 index 00000000000..5bfe5de70de --- /dev/null +++ b/src/main/java/chess/controller/command/Start.java @@ -0,0 +1,24 @@ +package chess.controller.command; + +import chess.controller.State; +import chess.service.BoardService; +import chess.service.GameService; +import chess.view.OutputView; +import java.util.List; + +public class Start implements Command { + + private static final int END_COMMAND_SIZE = 1; + + public Start(List commandInput) { + if (commandInput.size() != END_COMMAND_SIZE) { + throw new IllegalArgumentException("게임 시작 명령어 입력 형식이 올바르지 않습니다."); + } + } + + @Override + public State execute(GameService gameService, BoardService boardService, Long roomId) { + OutputView.printBoard(boardService.getAllPieces(roomId)); + return State.RUNNING; + } +} diff --git a/src/main/java/chess/controller/command/Status.java b/src/main/java/chess/controller/command/Status.java new file mode 100644 index 00000000000..668b5b8941f --- /dev/null +++ b/src/main/java/chess/controller/command/Status.java @@ -0,0 +1,24 @@ +package chess.controller.command; + +import chess.controller.State; +import chess.service.BoardService; +import chess.service.GameService; +import chess.view.OutputView; +import java.util.List; + +public class Status implements Command { + + private static final int STATUS_COMMAND_SIZE = 1; + + public Status(List commandInput) { + if (commandInput.size() != STATUS_COMMAND_SIZE) { + throw new IllegalArgumentException("게임 점수 명령어 입력 형식이 올바르지 않습니다."); + } + } + + @Override + public State execute(GameService gameService, BoardService boardService, Long roomId) { + OutputView.printTeamScore(gameService.generateGameResult(roomId)); + return State.RUNNING; + } +} diff --git a/src/main/java/chess/domain/board/BoardFactory.java b/src/main/java/chess/domain/board/BoardFactory.java new file mode 100644 index 00000000000..3b0414f40dc --- /dev/null +++ b/src/main/java/chess/domain/board/BoardFactory.java @@ -0,0 +1,56 @@ +package chess.domain.board; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import java.util.HashMap; +import java.util.Map; + +public class BoardFactory { + + public Map initialize() { + Map map = new HashMap<>(); + initializeBlackTeam(map); + initializeWhiteTeam(map); + return map; + } + + private void initializeBlackTeam(Map map) { + initializePawn(map, Row.SEVEN, Color.BLACK); + initializeHighValuePiece(map, Row.EIGHT, Color.BLACK); + } + + private void initializeWhiteTeam(Map map) { + initializePawn(map, Row.TWO, Color.WHITE); + initializeHighValuePiece(map, Row.ONE, Color.WHITE); + } + + private void initializePawn(Map map, Row row, Color color) { + for (Column column : Column.values()) { + Position position = new Position(row, column); + if (color == Color.WHITE) { + map.put(position, new Piece(PieceType.WHITE_PAWN, color)); + } + if (color == Color.BLACK) { + map.put(position, new Piece(PieceType.BLACK_PAWN, color)); + } + } + } + + private void initializeHighValuePiece(Map map, Row row, Color color) { + map.put(new Position(row, Column.A), new Piece(PieceType.ROOK, color)); + map.put(new Position(row, Column.H), new Piece(PieceType.ROOK, color)); + + map.put(new Position(row, Column.B), new Piece(PieceType.KNIGHT, color)); + map.put(new Position(row, Column.G), new Piece(PieceType.KNIGHT, color)); + + map.put(new Position(row, Column.C), new Piece(PieceType.BISHOP, color)); + map.put(new Position(row, Column.F), new Piece(PieceType.BISHOP, color)); + + map.put(new Position(row, Column.D), new Piece(PieceType.QUEEN, color)); + map.put(new Position(row, Column.E), new Piece(PieceType.KING, color)); + } +} diff --git a/src/main/java/chess/domain/board/position/Column.java b/src/main/java/chess/domain/board/position/Column.java new file mode 100644 index 00000000000..9d81050071f --- /dev/null +++ b/src/main/java/chess/domain/board/position/Column.java @@ -0,0 +1,36 @@ +package chess.domain.board.position; + +import java.util.Arrays; + +public enum Column { + A(0), + B(1), + C(2), + D(3), + E(4), + F(5), + G(6), + H(7); + + private final int index; + + Column(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + + public Column calculateNextColumn(int distance) { + return Arrays.stream(values()) + .filter(column -> column.index == this.index + distance) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("일치하는 Column 가 없습니다.")); + } + + public boolean isNextInRange(int distance) { + int nextIndex = index + distance; + return A.index <= nextIndex && nextIndex <= H.index; + } +} diff --git a/src/main/java/chess/domain/board/position/Direction.java b/src/main/java/chess/domain/board/position/Direction.java new file mode 100644 index 00000000000..ca84ab9d3f0 --- /dev/null +++ b/src/main/java/chess/domain/board/position/Direction.java @@ -0,0 +1,42 @@ +package chess.domain.board.position; + +public enum Direction { + N(1, 0), + E(0, 1), + S(-1, 0), + W(0, -1), + + NE(1, 1), + SE(-1, 1), + SW(-1, -1), + NW(1, -1), + + NNE(2, 1), + ENE(1, 2), + ESE(-1, 2), + SSE(-2, 1), + SSW(-2, -1), + WSW(-1, -2), + WNW(1, -2), + NNW(2, -1); + + private final int rowDirection; + private final int columnDirection; + + Direction(int rowDirection, int columnDirection) { + this.rowDirection = rowDirection; + this.columnDirection = columnDirection; + } + + public boolean isStraight() { + return this == Direction.N || this == Direction.S || this == Direction.E || this == Direction.W; + } + + public int calculateRowDistance(int weight) { + return rowDirection * weight; + } + + public int calculateColumnDistance(int weight) { + return columnDirection * weight; + } +} diff --git a/src/main/java/chess/domain/board/position/Position.java b/src/main/java/chess/domain/board/position/Position.java new file mode 100644 index 00000000000..55a1759d069 --- /dev/null +++ b/src/main/java/chess/domain/board/position/Position.java @@ -0,0 +1,75 @@ +package chess.domain.board.position; + +import java.util.Objects; +import java.util.stream.IntStream; + +public class Position { + + private final Row row; + private final Column column; + + public Position(Row row, Column column) { + this.row = row; + this.column = column; + } + + public Position(int row, String column) { + this(Row.findByIndex(row), Column.valueOf(column)); + } + + public Position calculateNextPosition(Direction direction, int weight) { + int rowDistance = direction.calculateRowDistance(weight); + int columnDistance = direction.calculateColumnDistance(weight); + return new Position(row.calculateNextRow(rowDistance), column.calculateNextColumn(columnDistance)); + } + + public int calculateMaxDistance(Direction direction, int maxMoveDistance) { + return IntStream.rangeClosed(1, maxMoveDistance) + .filter(weight -> isInRange(direction, weight)) + .max() + .orElse(0); + } + + private boolean isInRange(Direction direction, int weight) { + int rowDistance = direction.calculateRowDistance(weight); + int columnDistance = direction.calculateColumnDistance(weight); + return row.isNextInRange(rowDistance) && column.isNextInRange(columnDistance); + } + + public boolean isSameRow(Row row) { + return this.row == row; + } + + public int getColumnIndex() { + return column.getIndex(); + } + + public int getRowIndex() { + return row.getIndex(); + } + + public Row getRow() { + return row; + } + + public Column getColumn() { + return column; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Position position = (Position) o; + return column == position.column && row == position.row; + } + + @Override + public int hashCode() { + return Objects.hash(column, row); + } +} diff --git a/src/main/java/chess/domain/board/position/Row.java b/src/main/java/chess/domain/board/position/Row.java new file mode 100644 index 00000000000..584989db2da --- /dev/null +++ b/src/main/java/chess/domain/board/position/Row.java @@ -0,0 +1,43 @@ +package chess.domain.board.position; + +import java.util.Arrays; + +public enum Row { + EIGHT(8), + SEVEN(7), + SIX(6), + FIVE(5), + FOUR(4), + THREE(3), + TWO(2), + ONE(1); + + private final int index; + + Row(int index) { + this.index = index; + } + + public Row calculateNextRow(int distance) { + return Arrays.stream(values()) + .filter(row -> row.index == this.index + distance) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("다음 위치로 이동할 수 있는 열이 없습니다.")); + } + + public static Row findByIndex(int index) { + return Arrays.stream(values()) + .filter(row -> row.index == index) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("열과 일치하는 인덱스가 없습니다.")); + } + + public boolean isNextInRange(int distance) { + int nextIndex = index + distance; + return EIGHT.index >= nextIndex && nextIndex >= ONE.index; + } + + public int getIndex() { + return index; + } +} diff --git a/src/main/java/chess/domain/game/PositionsFilter.java b/src/main/java/chess/domain/game/PositionsFilter.java new file mode 100644 index 00000000000..a951703220b --- /dev/null +++ b/src/main/java/chess/domain/game/PositionsFilter.java @@ -0,0 +1,58 @@ +package chess.domain.game; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.piece.Piece; +import chess.repository.BoardRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public class PositionsFilter { + + private final BoardRepository boardRepository; + private final Map> candidateAllPositions; + + public PositionsFilter(BoardRepository boardRepository, + Map> candidateAllPositions) { + this.boardRepository = boardRepository; + this.candidateAllPositions = candidateAllPositions; + } + + public List generateValidPositions(Piece piece, Long roomId) { + return candidateAllPositions.keySet().stream() + .map(direction -> filterInvalidPositions(candidateAllPositions.get(direction), direction, piece, roomId)) + .flatMap(List::stream) + .toList(); + } + + private List filterInvalidPositions(Queue expectedPositions, + Direction direction, + Piece piece, + Long roomId) { + List result = new ArrayList<>(); + Position currentPosition = expectedPositions.poll(); + while (isEmptySpace(direction, piece, currentPosition, roomId)) { + result.add(currentPosition); + currentPosition = expectedPositions.poll(); + } + if (isEnemySpace(direction, piece, currentPosition, roomId)) { + result.add(currentPosition); + } + return result; + } + + private boolean isEmptySpace(Direction direction, Piece piece, Position currentPosition, Long roomId) { + return currentPosition != null + && piece.isPawnMovePossible(direction) + && !boardRepository.existsPieceByPosition(currentPosition, roomId); + } + + private boolean isEnemySpace(Direction direction, Piece piece, Position currentPosition, Long roomId) { + return currentPosition != null + && piece.isPawnAttackPossible(direction) + && boardRepository.existsPieceByPosition(currentPosition, roomId) + && piece.isEnemy(boardRepository.findPieceByPosition(currentPosition, roomId)); + } +} diff --git a/src/main/java/chess/domain/game/Room.java b/src/main/java/chess/domain/game/Room.java new file mode 100644 index 00000000000..398d296bbd1 --- /dev/null +++ b/src/main/java/chess/domain/game/Room.java @@ -0,0 +1,20 @@ +package chess.domain.game; + +public class Room { + + private final Long id; + private final RoomName roomName; + + public Room(Long id, String name) { + this.id = id; + this.roomName = new RoomName(name); + } + + public String getName() { + return roomName.getValue(); + } + + public Long getId() { + return id; + } +} diff --git a/src/main/java/chess/domain/game/RoomName.java b/src/main/java/chess/domain/game/RoomName.java new file mode 100644 index 00000000000..d00d66d2bc6 --- /dev/null +++ b/src/main/java/chess/domain/game/RoomName.java @@ -0,0 +1,17 @@ +package chess.domain.game; + +public class RoomName { + + private final String name; + + public RoomName(String name) { + if (name.length() > 16) { + throw new IllegalArgumentException("게임 방 이름 길이는 최대 16글자까지 가능합니다."); + } + this.name = name; + } + + public String getValue() { + return name; + } +} diff --git a/src/main/java/chess/domain/game/Score.java b/src/main/java/chess/domain/game/Score.java new file mode 100644 index 00000000000..91f1da0ef72 --- /dev/null +++ b/src/main/java/chess/domain/game/Score.java @@ -0,0 +1,40 @@ +package chess.domain.game; + +import java.util.Objects; + +public final class Score { + private final Double score; + + public Score(Double score) { + this.score = score; + } + + public boolean isGreaterThan(Score other) { + return score > other.score; + } + + public boolean isLowerThan(Score other) { + return score < other.score; + } + + public Double score() { + return score; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Score score1 = (Score) o; + return Objects.equals(score, score1.score); + } + + @Override + public int hashCode() { + return Objects.hash(score); + } +} diff --git a/src/main/java/chess/domain/game/Winner.java b/src/main/java/chess/domain/game/Winner.java new file mode 100644 index 00000000000..d11ca02c068 --- /dev/null +++ b/src/main/java/chess/domain/game/Winner.java @@ -0,0 +1,33 @@ +package chess.domain.game; + +import chess.domain.piece.Color; +import java.util.Arrays; +import java.util.Map; +import java.util.function.BiPredicate; + +public enum Winner { + + WHITE_WIN(Score::isGreaterThan), + BLACK_WIN(Score::isLowerThan), + DRAW(Score::equals); + + private final BiPredicate selectWinner; + + Winner(BiPredicate selectWinner) { + this.selectWinner = selectWinner; + } + + public static Winner selectWinnerByScore(Map teamScore) { + return Arrays.stream(values()) + .filter(winner -> winner.selectWinner.test(teamScore.get(Color.WHITE), teamScore.get(Color.BLACK))) + .findAny() + .orElseThrow(IllegalStateException::new); + } + + public static Winner selectWinnerByCheckmate(Color aliveKingColor) { + if (aliveKingColor == Color.WHITE) { + return WHITE_WIN; + } + return BLACK_WIN; + } +} diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/piece/Color.java new file mode 100644 index 00000000000..b380cc7e1ea --- /dev/null +++ b/src/main/java/chess/domain/piece/Color.java @@ -0,0 +1,13 @@ +package chess.domain.piece; + +public enum Color { + BLACK, + WHITE; + + public Color change() { + if (this == BLACK) { + return WHITE; + } + return BLACK; + } +} 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..45105ed462b --- /dev/null +++ b/src/main/java/chess/domain/piece/Piece.java @@ -0,0 +1,91 @@ +package chess.domain.piece; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; + +public class Piece { + + private final PieceType pieceType; + private final Color color; + + public Piece(PieceType pieceType, Color color) { + this.pieceType = pieceType; + this.color = color; + } + + public Piece(String pieceType, String color) { + this(PieceType.valueOf(pieceType), Color.valueOf(color)); + } + + public Map> generateAllDirectionPositions(Position currentPosition) { + return pieceType.generateAllDirectionPositions(currentPosition); + } + + public boolean isEnemy(Piece piece) { + return isNotSameColor(piece.color); + } + + public boolean isSameColor(Color color) { + return this.color == color; + } + + public boolean isNotSameColor(Color color) { + return !isSameColor(color); + } + + public boolean isKing() { + return this.pieceType == PieceType.KING; + } + + public double getScore() { + return pieceType.getScore(); + } + + public Color getColor() { + return color; + } + + public boolean isPawnAttackPossible(Direction direction) { + if (pieceType == PieceType.BLACK_PAWN) { + return direction == Direction.SW || direction == Direction.SE; + } + if (pieceType == PieceType.WHITE_PAWN) { + return direction == Direction.NW || direction == Direction.NE; + } + return true; + } + + public boolean isPawnMovePossible(Direction direction) { + if (pieceType == PieceType.BLACK_PAWN) { + return direction == Direction.S; + } + if (pieceType == PieceType.WHITE_PAWN) { + return direction == Direction.N; + } + return true; + } + + public PieceType getPieceType() { + return pieceType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Piece piece = (Piece) o; + return pieceType == piece.pieceType && color == piece.color; + } + + @Override + public int hashCode() { + return Objects.hash(pieceType, color); + } +} 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..ff7becab3f3 --- /dev/null +++ b/src/main/java/chess/domain/piece/PieceType.java @@ -0,0 +1,40 @@ +package chess.domain.piece; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.strategy.BishopMoveStrategy; +import chess.domain.strategy.BlackPawnMoveStrategy; +import chess.domain.strategy.KingMoveStrategy; +import chess.domain.strategy.KnightMoveStrategy; +import chess.domain.strategy.MoveStrategy; +import chess.domain.strategy.QueenMoveStrategy; +import chess.domain.strategy.RookMoveStrategy; +import chess.domain.strategy.WhitePawnMoveStrategy; +import java.util.Map; +import java.util.Queue; + +public enum PieceType { + BLACK_PAWN(new BlackPawnMoveStrategy(), 1), + WHITE_PAWN(new WhitePawnMoveStrategy(), 1), + ROOK(new RookMoveStrategy(), 5), + KNIGHT(new KnightMoveStrategy(), 2.5), + BISHOP(new BishopMoveStrategy(), 3), + QUEEN(new QueenMoveStrategy(), 9), + KING(new KingMoveStrategy(), 0); + + private final MoveStrategy moveStrategy; + private final double score; + + PieceType(MoveStrategy moveStrategy, double score) { + this.moveStrategy = moveStrategy; + this.score = score; + } + + public Map> generateAllDirectionPositions(Position currentPosition) { + return this.moveStrategy.generateMovablePositions(currentPosition); + } + + public double getScore() { + return score; + } +} diff --git a/src/main/java/chess/domain/strategy/BishopMoveStrategy.java b/src/main/java/chess/domain/strategy/BishopMoveStrategy.java new file mode 100644 index 00000000000..0ce1273c404 --- /dev/null +++ b/src/main/java/chess/domain/strategy/BishopMoveStrategy.java @@ -0,0 +1,15 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import java.util.List; + +public class BishopMoveStrategy extends SpecialPieceMoveStrategy{ + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 7; + + private static final List DIRECTIONS = List.of(Direction.NE, Direction.SE, Direction.SW, Direction.NW); + + public BishopMoveStrategy() { + super(DIRECTIONS, DEFAULT_MAX_MOVE_DISTANCE); + } +} diff --git a/src/main/java/chess/domain/strategy/BlackPawnMoveStrategy.java b/src/main/java/chess/domain/strategy/BlackPawnMoveStrategy.java new file mode 100644 index 00000000000..6000c704f18 --- /dev/null +++ b/src/main/java/chess/domain/strategy/BlackPawnMoveStrategy.java @@ -0,0 +1,14 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Row; +import java.util.List; + +public class BlackPawnMoveStrategy extends PawnMoveStrategy{ + + private static final List DIRECTIONS = List.of(Direction.S, Direction.SE, Direction.SW); + + public BlackPawnMoveStrategy() { + super(DIRECTIONS, Row.SEVEN); + } +} diff --git a/src/main/java/chess/domain/strategy/KingMoveStrategy.java b/src/main/java/chess/domain/strategy/KingMoveStrategy.java new file mode 100644 index 00000000000..190a330c4e9 --- /dev/null +++ b/src/main/java/chess/domain/strategy/KingMoveStrategy.java @@ -0,0 +1,18 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import java.util.List; + +public class KingMoveStrategy extends SpecialPieceMoveStrategy{ + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 1; + + private static final List DIRECTIONS = List.of( + Direction.N, Direction.NE, Direction.E, Direction.SE, + Direction.S, Direction.SW, Direction.W, Direction.NW + ); + + public KingMoveStrategy() { + super(DIRECTIONS, DEFAULT_MAX_MOVE_DISTANCE); + } +} diff --git a/src/main/java/chess/domain/strategy/KnightMoveStrategy.java b/src/main/java/chess/domain/strategy/KnightMoveStrategy.java new file mode 100644 index 00000000000..0cba1cd1f9b --- /dev/null +++ b/src/main/java/chess/domain/strategy/KnightMoveStrategy.java @@ -0,0 +1,19 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import java.util.List; + +public class KnightMoveStrategy extends SpecialPieceMoveStrategy{ + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 1; + + private static final List DIRECTIONS = List.of( + Direction.NNE, Direction.ENE, Direction.ESE, Direction.SSE, + Direction.SSW, Direction.WSW, Direction.WNW, Direction.NNW + ); + + + public KnightMoveStrategy() { + super(DIRECTIONS, DEFAULT_MAX_MOVE_DISTANCE); + } +} diff --git a/src/main/java/chess/domain/strategy/MoveStrategy.java b/src/main/java/chess/domain/strategy/MoveStrategy.java new file mode 100644 index 00000000000..2176179470c --- /dev/null +++ b/src/main/java/chess/domain/strategy/MoveStrategy.java @@ -0,0 +1,10 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import java.util.Queue; +import java.util.Map; + +public interface MoveStrategy { + Map> generateMovablePositions(Position position); +} diff --git a/src/main/java/chess/domain/strategy/PawnMoveStrategy.java b/src/main/java/chess/domain/strategy/PawnMoveStrategy.java new file mode 100644 index 00000000000..71db6885c4c --- /dev/null +++ b/src/main/java/chess/domain/strategy/PawnMoveStrategy.java @@ -0,0 +1,43 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public abstract class PawnMoveStrategy implements MoveStrategy { + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 1; + + private final List directions; + private final Row startRow; + + protected PawnMoveStrategy(List directions, Row startRow) { + this.directions = directions; + this.startRow = startRow; + } + + @Override + public Map> generateMovablePositions(Position position) { + return directions.stream() + .collect(Collectors.toMap( + direction -> direction, + direction -> generateMovablePositionsByDirection(position, direction) + )); + } + + private Queue generateMovablePositionsByDirection(Position currentPosition, Direction direction) { + int movableMaxDistance = currentPosition.calculateMaxDistance(direction, DEFAULT_MAX_MOVE_DISTANCE); + if (currentPosition.isSameRow(startRow) && direction.isStraight()) { + movableMaxDistance++; + } + return new ArrayDeque<>(IntStream.rangeClosed(1, movableMaxDistance) + .mapToObj(weight -> currentPosition.calculateNextPosition(direction, weight)) + .toList()); + } +} diff --git a/src/main/java/chess/domain/strategy/QueenMoveStrategy.java b/src/main/java/chess/domain/strategy/QueenMoveStrategy.java new file mode 100644 index 00000000000..4b6b03ca12a --- /dev/null +++ b/src/main/java/chess/domain/strategy/QueenMoveStrategy.java @@ -0,0 +1,18 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import java.util.List; + +public class QueenMoveStrategy extends SpecialPieceMoveStrategy{ + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 7; + + private static final List DIRECTIONS = List.of( + Direction.N, Direction.NE, Direction.E, Direction.SE, + Direction.S, Direction.SW, Direction.W, Direction.NW + ); + + public QueenMoveStrategy() { + super(DIRECTIONS, DEFAULT_MAX_MOVE_DISTANCE); + } +} diff --git a/src/main/java/chess/domain/strategy/RookMoveStrategy.java b/src/main/java/chess/domain/strategy/RookMoveStrategy.java new file mode 100644 index 00000000000..6352979762d --- /dev/null +++ b/src/main/java/chess/domain/strategy/RookMoveStrategy.java @@ -0,0 +1,14 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import java.util.List; + +public class RookMoveStrategy extends SpecialPieceMoveStrategy{ + + private static final int DEFAULT_MAX_MOVE_DISTANCE = 7; + private static final List DIRECTIONS = List.of(Direction.N, Direction.W, Direction.S, Direction.E); + + public RookMoveStrategy() { + super(DIRECTIONS, DEFAULT_MAX_MOVE_DISTANCE); + } +} diff --git a/src/main/java/chess/domain/strategy/SpecialPieceMoveStrategy.java b/src/main/java/chess/domain/strategy/SpecialPieceMoveStrategy.java new file mode 100644 index 00000000000..edcadce6a66 --- /dev/null +++ b/src/main/java/chess/domain/strategy/SpecialPieceMoveStrategy.java @@ -0,0 +1,37 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public abstract class SpecialPieceMoveStrategy implements MoveStrategy { + + private final List directions; + private final int defaultMaxMoveDistance; + + protected SpecialPieceMoveStrategy(List directions, int defaultMaxMoveDistance) { + this.directions = directions; + this.defaultMaxMoveDistance = defaultMaxMoveDistance; + } + + @Override + public Map> generateMovablePositions(Position position) { + return directions.stream() + .collect(Collectors.toMap( + direction -> direction, + direction -> generateMovablePositionsByDirection(position, direction) + )); + } + + private Queue generateMovablePositionsByDirection(Position currentPosition, Direction direction) { + int movableMaxDistance = currentPosition.calculateMaxDistance(direction, defaultMaxMoveDistance); + return new ArrayDeque<>(IntStream.rangeClosed(1, movableMaxDistance) + .mapToObj(weight -> currentPosition.calculateNextPosition(direction, weight)) + .toList()); + } +} diff --git a/src/main/java/chess/domain/strategy/WhitePawnMoveStrategy.java b/src/main/java/chess/domain/strategy/WhitePawnMoveStrategy.java new file mode 100644 index 00000000000..b0ff8a5941a --- /dev/null +++ b/src/main/java/chess/domain/strategy/WhitePawnMoveStrategy.java @@ -0,0 +1,14 @@ +package chess.domain.strategy; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Row; +import java.util.List; + +public class WhitePawnMoveStrategy extends PawnMoveStrategy{ + + private static final List DIRECTIONS = List.of(Direction.N, Direction.NE, Direction.NW); + + public WhitePawnMoveStrategy() { + super(DIRECTIONS, Row.TWO); + } +} diff --git a/src/main/java/chess/repository/BoardRepository.java b/src/main/java/chess/repository/BoardRepository.java new file mode 100644 index 00000000000..b7904430e75 --- /dev/null +++ b/src/main/java/chess/repository/BoardRepository.java @@ -0,0 +1,27 @@ +package chess.repository; + +import chess.domain.board.position.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import java.util.List; +import java.util.Map; + +public interface BoardRepository { + + void savePiece(Piece piece, Position position, Long roomId); + + boolean existsPieceByPosition(Position position, Long roomId); + + void deletePieceByPosition(Position position, Long roomId); + + Piece findPieceByPosition(Position position, Long roomId); + + List findPiecesByColor(Color piece_color, Long roomId); + + List getPieceCountByPieceType(PieceType pieceType, Long roomId); + + Map findAllPieceByRoomId(Long roomId); + + List findPieceByPieceType(PieceType pieceType, Long roomId); +} diff --git a/src/main/java/chess/repository/BoardRepositoryImpl.java b/src/main/java/chess/repository/BoardRepositoryImpl.java new file mode 100644 index 00000000000..34e6b2d6cde --- /dev/null +++ b/src/main/java/chess/repository/BoardRepositoryImpl.java @@ -0,0 +1,164 @@ +package chess.repository; + +import chess.domain.board.position.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BoardRepositoryImpl implements BoardRepository { + + private final DBConnection dbConnection = new DBConnection(); + + @Override + public void savePiece(Piece piece, Position position, Long roomId) { + final String query = "INSERT INTO board(`row`, `column`, piece_type, piece_color, room_id) VALUES(?, ?, ?, ?, ?)"; + processQuery(query, preparedStatement -> { + preparedStatement.setInt(1, position.getRowIndex()); + preparedStatement.setString(2, position.getColumn().name()); + preparedStatement.setString(3, piece.getPieceType().name()); + preparedStatement.setString(4, piece.getColor().name()); + preparedStatement.setLong(5, roomId); + preparedStatement.executeUpdate(); + }); + } + + @Override + public boolean existsPieceByPosition(Position position, Long roomId) { + List existsPiece = new ArrayList<>(); + final String query = "SELECT EXISTS (" + + "SELECT 1 FROM board WHERE `row` = ? AND `column` = ? AND room_id = ?) AS exists_piece"; + processQuery(query, preparedStatement -> { + preparedStatement.setInt(1, position.getRowIndex()); + preparedStatement.setString(2, position.getColumn().name()); + preparedStatement.setLong(3, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addExistPieceResult(existsPiece, rs); + }); + return existsPiece.get(0); + } + + private void addExistPieceResult(List existsPiece, ResultSet rs) throws SQLException { + if (rs.next()) { + existsPiece.add(rs.getBoolean("exists_piece")); + } + } + + @Override + public void deletePieceByPosition(Position position, Long roomId) { + final String query = "DELETE FROM board WHERE `row` = ? AND `column` = ? AND room_id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setInt(1, position.getRowIndex()); + preparedStatement.setString(2, position.getColumn().name()); + preparedStatement.setLong(3, roomId); + preparedStatement.executeUpdate(); + }); + } + + @Override + public Piece findPieceByPosition(Position position, Long roomId) { + List pieces = new ArrayList<>(); + final String query = "SELECT piece_type, piece_color FROM board WHERE `row` = ? AND `column` = ? AND room_id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setInt(1, position.getRowIndex()); + preparedStatement.setString(2, position.getColumn().name()); + preparedStatement.setLong(3, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addPiece(pieces, rs); + }); + return pieces.get(0); + } + + private void addPiece(List pieces, ResultSet rs) throws SQLException { + if (rs.next()) { + pieces.add(new Piece(rs.getString("piece_type"), rs.getString("piece_color"))); + } + } + + @Override + public List findPiecesByColor(Color piece_color, Long roomId) { + List pieces = new ArrayList<>(); + String query = "SELECT piece_type, piece_color FROM board WHERE piece_color = ? AND room_id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, piece_color.name()); + preparedStatement.setLong(2, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addPieces(pieces, rs); + }); + return pieces; + } + + private void addPieces(List pieces, ResultSet rs) throws SQLException { + while (rs.next()) { + pieces.add(new Piece(rs.getString("piece_type"), rs.getString("piece_color"))); + } + } + + @Override + public List getPieceCountByPieceType(PieceType pieceType, Long roomId) { + List pieceCount = new ArrayList<>(); + String query = "SELECT COUNT(*) AS piece_count FROM board WHERE piece_type = ? AND room_id = ? GROUP BY `column`"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, pieceType.name()); + preparedStatement.setLong(2, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addPieceCount(pieceCount, rs); + }); + return pieceCount; + } + + private void addPieceCount(List pieceCount, ResultSet rs) throws SQLException { + while (rs.next()) { + pieceCount.add(rs.getInt("piece_count")); + } + } + + @Override + public Map findAllPieceByRoomId(Long roomId) { + Map allPieces = new HashMap<>(); + String query = "SELECT * FROM board JOIN room ON board.room_id = room.id WHERE board.room_id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setLong(1, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addPositionPieces(allPieces, rs); + }); + return allPieces; + } + + private void addPositionPieces(Map allPieces, ResultSet rs) throws SQLException { + while (rs.next()) { + Position position = new Position(rs.getInt("row"), rs.getString("column")); + Piece piece = new Piece(rs.getString("piece_type"), rs.getString("piece_color")); + allPieces.put(position, piece); + } + } + + @Override + public List findPieceByPieceType(PieceType pieceType, Long roomId) { + List pieces = new ArrayList<>(); + final String query = "SELECT piece_type, piece_color FROM board WHERE piece_type = ? AND room_id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, pieceType.name()); + preparedStatement.setLong(2, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addPieces(pieces, rs); + }); + return pieces; + } + + private void processQuery(String query, QueryProcessor queryProcessor) { + try (final Connection connection = dbConnection.getConnection()) { + final PreparedStatement preparedStatement = connection.prepareStatement(query); + queryProcessor.process(preparedStatement); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/chess/repository/DBConnection.java b/src/main/java/chess/repository/DBConnection.java new file mode 100644 index 00000000000..56679105a55 --- /dev/null +++ b/src/main/java/chess/repository/DBConnection.java @@ -0,0 +1,23 @@ +package chess.repository; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class DBConnection { + + private static final String SERVER = "localhost:3306"; + private static final String DATABASE = "chess"; + private static final String OPTION = "?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; + private static final String USERNAME = "root"; + private static final String PASSWORD = "99113030"; + + public Connection getConnection() { + try { + return DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD); + } catch (SQLException e) { + System.out.println("DB 연결 오류:" + e.getMessage()); + throw new RuntimeException("데이터베이스 연결에 실패했습니다."); + } + } +} diff --git a/src/main/java/chess/repository/QueryProcessor.java b/src/main/java/chess/repository/QueryProcessor.java new file mode 100644 index 00000000000..8375d02c69c --- /dev/null +++ b/src/main/java/chess/repository/QueryProcessor.java @@ -0,0 +1,10 @@ +package chess.repository; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@FunctionalInterface +public interface QueryProcessor { + + void process(PreparedStatement preparedStatement) throws SQLException; +} diff --git a/src/main/java/chess/repository/RoomRepository.java b/src/main/java/chess/repository/RoomRepository.java new file mode 100644 index 00000000000..d473c24d99a --- /dev/null +++ b/src/main/java/chess/repository/RoomRepository.java @@ -0,0 +1,21 @@ +package chess.repository; + +import chess.domain.game.Room; +import chess.domain.game.RoomName; +import chess.domain.piece.Color; +import java.util.List; + +public interface RoomRepository { + + List findAllRoomNames(); + + boolean existsRoomName(RoomName roomName); + + void saveRoom(RoomName roomName); + + void updateRoomTurn(Color color, Long roomId); + + Room findRoomByName(RoomName roomName); + + Color findTurnById(Long roomId); +} diff --git a/src/main/java/chess/repository/RoomRepositoryImpl.java b/src/main/java/chess/repository/RoomRepositoryImpl.java new file mode 100644 index 00000000000..176878c35c7 --- /dev/null +++ b/src/main/java/chess/repository/RoomRepositoryImpl.java @@ -0,0 +1,117 @@ +package chess.repository; + +import chess.domain.game.Room; +import chess.domain.game.RoomName; +import chess.domain.piece.Color; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class RoomRepositoryImpl implements RoomRepository{ + + private final DBConnection dbConnection = new DBConnection(); + + @Override + public List findAllRoomNames() { + List allRoomNames = new ArrayList<>(); + String query = "SELECT name FROM room"; + processQuery(query, preparedStatement -> { + ResultSet rs = preparedStatement.executeQuery(); + addRoomNames(allRoomNames, rs); + }); + return allRoomNames; + } + + private void addRoomNames(List allRoomNames, ResultSet rs) throws SQLException { + while (rs.next()) { + allRoomNames.add(rs.getString("name")); + } + } + + @Override + public boolean existsRoomName(RoomName roomName) { + List existsRoom = new ArrayList<>(); + final String query = "SELECT EXISTS (" + + "SELECT 1 FROM room WHERE name = ?) AS exists_room"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, roomName.getValue()); + ResultSet rs = preparedStatement.executeQuery(); + addExistsRoomResult(existsRoom, rs); + }); + return existsRoom.get(0); + } + + private void addExistsRoomResult(List existsRoom, ResultSet rs) throws SQLException { + if (rs.next()) { + existsRoom.add(rs.getBoolean("exists_room")); + } + } + + @Override + public void saveRoom(RoomName roomName) { + final String query = "INSERT INTO room(name, turn) VALUES(?, ?)"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, roomName.getValue()); + preparedStatement.setString(2, Color.WHITE.name()); + preparedStatement.executeUpdate(); + }); + } + + @Override + public void updateRoomTurn(Color color, Long roomId) { + final String query = "UPDATE room SET turn = ? WHERE id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, color.name()); + preparedStatement.setLong(2, roomId); + preparedStatement.executeUpdate(); + }); + } + + @Override + public Room findRoomByName(RoomName roomName) { + List roomId = new ArrayList<>(); + final String query = "SELECT * FROM room WHERE name = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setString(1, roomName.getValue()); + ResultSet rs = preparedStatement.executeQuery(); + addRoom(roomId, rs); + }); + return roomId.get(0); + } + + private void addRoom(List roomId, ResultSet rs) throws SQLException { + if (rs.next()) { + roomId.add(new Room(rs.getLong("id"), rs.getString("name"))); + } + } + + @Override + public Color findTurnById(Long roomId) { + List color = new ArrayList<>(); + final String query = "SELECT turn FROM room WHERE id = ?"; + processQuery(query, preparedStatement -> { + preparedStatement.setLong(1, roomId); + ResultSet rs = preparedStatement.executeQuery(); + addColor(color, rs); + }); + return color.get(0); + } + + private void addColor(List color, ResultSet rs) throws SQLException { + if (rs.next()) { + color.add(Color.valueOf(rs.getString("turn"))); + } + } + + private void processQuery(String query, QueryProcessor queryProcessor) { + try (final Connection connection = dbConnection.getConnection()) { + final PreparedStatement preparedStatement = connection.prepareStatement(query); + queryProcessor.process(preparedStatement); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/chess/service/BoardService.java b/src/main/java/chess/service/BoardService.java new file mode 100644 index 00000000000..87a9a332590 --- /dev/null +++ b/src/main/java/chess/service/BoardService.java @@ -0,0 +1,70 @@ +package chess.service; + +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +public class BoardService { + + private final RoomRepository roomRepository; + private final BoardRepository boardRepository; + + public BoardService(RoomRepository roomRepository, BoardRepository boardRepository) { + this.roomRepository = roomRepository; + this.boardRepository = boardRepository; + } + + public void movePiece(Position from, Position to, Long roomId) { + Piece piece = findFromPositionPiece(from, roomId); + Color currentTurn = findCurrentTurn(roomId, piece); + List movablePositions = generateMovablePositions(piece, from, roomId); + if (movablePositions.contains(to)) { + movePiece(from, to, piece, roomId); + roomRepository.updateRoomTurn(currentTurn.change(), roomId); + return; + } + throw new IllegalArgumentException("기물을 해당 위치로 이동시킬 수 없습니다."); + } + + private Piece findFromPositionPiece(Position from, Long roomId) { + if (!boardRepository.existsPieceByPosition(from, roomId)) { + throw new IllegalArgumentException("선택한 기물이 존재하지 않습니다."); + } + return boardRepository.findPieceByPosition(from, roomId); + } + + private Color findCurrentTurn(Long roomId, Piece piece) { + Color currentTurn = roomRepository.findTurnById(roomId); + if (piece.isNotSameColor(currentTurn)) { + throw new IllegalArgumentException("상대방의 기물을 움직일 수 없습니다. 현재 턴 : " + currentTurn); + } + return currentTurn; + } + + private List generateMovablePositions(Piece piece, Position position, Long roomId) { + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + return new PositionsFilter(boardRepository, candidateAllPositions).generateValidPositions(piece, roomId); + } + + private void movePiece(Position from, Position to, Piece piece, Long roomId) { + boardRepository.deletePieceByPosition(from, roomId); + boardRepository.deletePieceByPosition(to, roomId); + boardRepository.savePiece(piece, to, roomId); + } + + public boolean isCheckmate(Position position, Long roomId) { + return boardRepository.existsPieceByPosition(position, roomId) && boardRepository.findPieceByPosition(position, roomId) + .isKing(); + } + + public Map getAllPieces(Long roomId) { + return boardRepository.findAllPieceByRoomId(roomId); + } +} diff --git a/src/main/java/chess/service/GameService.java b/src/main/java/chess/service/GameService.java new file mode 100644 index 00000000000..ea525015488 --- /dev/null +++ b/src/main/java/chess/service/GameService.java @@ -0,0 +1,94 @@ +package chess.service; + +import chess.domain.board.BoardFactory; +import chess.domain.board.position.Position; +import chess.domain.game.Room; +import chess.domain.game.RoomName; +import chess.domain.game.Score; +import chess.domain.game.Winner; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.service.dto.ChessGameResult; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GameService { + + private static final int GAME_OVER_KING_COUNT = 1; + private static final int PWAN_DUPLICATE_THRESHOLD = 2; + private static final double PWAN_DEDUCTION_SCORE = 0.5; + + private final RoomRepository roomRepository; + private final BoardRepository boardRepository; + + public GameService(RoomRepository roomRepository, BoardRepository boardRepository) { + this.roomRepository = roomRepository; + this.boardRepository = boardRepository; + } + + public List findAllRoomNames() { + return roomRepository.findAllRoomNames(); + } + + public Room loadRoom(String input) { + RoomName roomName = new RoomName(input); + if (roomRepository.existsRoomName(roomName)) { + return roomRepository.findRoomByName(roomName); + } + return createRoom(roomName); + } + + private Room createRoom(RoomName roomName) { + roomRepository.saveRoom(roomName); + Room room = roomRepository.findRoomByName(roomName); + initializeBoard(room.getId()); + return room; + } + + private void initializeBoard(Long roomId) { + BoardFactory boardFactory = new BoardFactory(); + Map board = boardFactory.initialize(); + board.forEach((position, piece) -> boardRepository.savePiece(piece, position, roomId)); + } + + public ChessGameResult generateGameResult(Long roomId) { + Map teamScore = getScore(roomId); + List kings = boardRepository.findPieceByPieceType(PieceType.KING, roomId); + if (kings.size() == GAME_OVER_KING_COUNT) { + return new ChessGameResult(Winner.selectWinnerByCheckmate(kings.get(0).getColor()), teamScore); + } + return new ChessGameResult(Winner.selectWinnerByScore(teamScore), teamScore); + } + + public Map getScore(Long roomId) { + Map teamScore = new HashMap<>(); + teamScore.put(Color.WHITE, calculateTotalScore(Color.WHITE, PieceType.WHITE_PAWN, roomId)); + teamScore.put(Color.BLACK, calculateTotalScore(Color.BLACK, PieceType.BLACK_PAWN, roomId)); + return teamScore; + } + + private Score calculateTotalScore(Color color, PieceType pieceType, Long roomId) { + double sum = sumTotalScore(color, roomId); + double pawnMinus = calculatePawnScore(pieceType, roomId); + return new Score(sum - pawnMinus); + } + + private double sumTotalScore(Color color, Long roomId) { + List pieces = boardRepository.findPiecesByColor(color, roomId); + return pieces.stream() + .mapToDouble(Piece::getScore) + .sum(); + } + + private double calculatePawnScore(PieceType pieceType, Long roomId) { + List pieceCount = boardRepository.getPieceCountByPieceType(pieceType, roomId); + return pieceCount.stream() + .filter(count -> count >= PWAN_DUPLICATE_THRESHOLD) + .mapToDouble(count -> count * PWAN_DEDUCTION_SCORE) + .sum(); + } +} diff --git a/src/main/java/chess/service/dto/ChessGameResult.java b/src/main/java/chess/service/dto/ChessGameResult.java new file mode 100644 index 00000000000..0d34d33031d --- /dev/null +++ b/src/main/java/chess/service/dto/ChessGameResult.java @@ -0,0 +1,25 @@ +package chess.service.dto; + +import chess.domain.game.Score; +import chess.domain.game.Winner; +import chess.domain.piece.Color; +import java.util.Map; + +public class ChessGameResult { + + private final Winner winner; + private final Map teamScore; + + public ChessGameResult(Winner winner, Map teamScore) { + this.winner = winner; + this.teamScore = teamScore; + } + + public Winner getWinner() { + return winner; + } + + public Map getTeamScore() { + return teamScore; + } +} diff --git a/src/main/java/chess/view/InputView.java b/src/main/java/chess/view/InputView.java new file mode 100644 index 00000000000..aaa34304bd5 --- /dev/null +++ b/src/main/java/chess/view/InputView.java @@ -0,0 +1,18 @@ +package chess.view; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class InputView { + + private static final Scanner scanner = new Scanner(System.in); + + public static String readRoomName() { + return scanner.nextLine(); + } + + public static List readCommend() { + return Arrays.stream(scanner.nextLine().split(" ")).toList(); + } +} diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java new file mode 100644 index 00000000000..e6f4fa45327 --- /dev/null +++ b/src/main/java/chess/view/OutputView.java @@ -0,0 +1,102 @@ +package chess.view; + +import chess.domain.board.position.Position; +import chess.domain.game.Score; +import chess.domain.game.Winner; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.service.dto.ChessGameResult; +import chess.view.mapper.PieceMapper; +import chess.view.mapper.RowMapper; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OutputView { + + private static final String EMPTY_BOARD = ". . . . . . . ."; + + public static void printRoomNames(List roomNames) { + System.out.println("체스 게임을 시작합니다.\n"); + System.out.println("현재 저장된 체스 게임 방 목록입니다."); + + if (roomNames.size() == 0) { + System.out.println("-- 현재 존재하는 체스 게임 방이 없습니다. --"); + } + + for (String name : roomNames) { + System.out.println("- " + name); + } + + System.out.println("\n이어서 시작하려면 위에 보이는 방 이름을 입력해주세요."); + System.out.println("새로 시작하려면 새로운 방 이름을 입력해주세요."); + } + + public static void printStartMessage(String roomName) { + System.out.println(roomName + " 체스 방에 입장하였습니다."); + System.out.println("\n게임 시작 : start"); + System.out.println("게임 종료 : end"); + System.out.println("게임 이동 : move source위치 target위치 - 예. move b2 b3"); + System.out.println("게임 점수 : status"); + } + + public static void printBoard(Map board) { + List result = new ArrayList<>(); + for (int i = 1; i <= 8; i++) { + result.add(new StringBuilder(EMPTY_BOARD + " | " + (9 - i))); + } + result.add(new StringBuilder("ㅡㅡㅡㅡㅡㅡㅡㅡㅡ")); + result.add(new StringBuilder("a b c d e f g h")); + + board.keySet() + .forEach(position -> { + Piece piece = board.get(position); + int rowIndex = RowMapper.findIndexByRow(position.getRow()); + int columnIndex = position.getColumnIndex(); + result.get(rowIndex).replace(columnIndex * 2, columnIndex * 2 + 1, PieceMapper.findByPieceType(piece)); + }); + + result.forEach(System.out::println); + System.out.println(); + } + + public static void printChessGameResult(ChessGameResult chessGameResult) { + System.out.print("왕이 죽어서 게임이 종료되었습니다. \n체스 게임 결과 : "); + Winner winner = chessGameResult.getWinner(); + if (winner == Winner.WHITE_WIN) { + System.out.println("흰색이 검은색 왕을 죽이고 승리하였습니다."); + } + if (winner == Winner.BLACK_WIN) { + System.out.println("검은색이 흰색 왕을 죽이고 승리하였습니다."); + } + + Map teamScore = chessGameResult.getTeamScore(); + printScore(teamScore.get(Color.WHITE), teamScore.get(Color.BLACK)); + } + + public static void printTeamScore(ChessGameResult chessGameResult) { + Winner winner = chessGameResult.getWinner(); + Map teamScore = chessGameResult.getTeamScore(); + printScore(teamScore.get(Color.WHITE), teamScore.get(Color.BLACK)); + + if (winner == Winner.WHITE_WIN) { + System.out.println("현재 대국 상황은 흰색이 우세합니다."); + } + if (winner == Winner.BLACK_WIN) { + System.out.println("현재 대국 상황은 검은색이 우세합니다."); + } + if (winner == Winner.DRAW) { + System.out.println("현재 대국 상황이 호각입니다."); + } + } + + private static void printScore(Score whiteScore, Score blackScore) { + System.out.println("--- 기물 점수 ---"); + System.out.println("흰: " + whiteScore.score()); + System.out.println("검: " + blackScore.score()); + } + + public static void printError(Exception exception) { + System.out.println(exception.getMessage()); + } +} diff --git a/src/main/java/chess/view/mapper/ColumnMapper.java b/src/main/java/chess/view/mapper/ColumnMapper.java new file mode 100644 index 00000000000..dc0d08a264d --- /dev/null +++ b/src/main/java/chess/view/mapper/ColumnMapper.java @@ -0,0 +1,31 @@ +package chess.view.mapper; + +import chess.domain.board.position.Column; +import java.util.Arrays; + +public enum ColumnMapper { + A(Column.A, "a"), + B(Column.B, "b"), + C(Column.C, "c"), + D(Column.D, "d"), + E(Column.E, "e"), + F(Column.F, "f"), + G(Column.G, "g"), + H(Column.H, "h"); + + private final Column column; + private final String value; + + ColumnMapper(Column column, String value) { + this.column = column; + this.value = value; + } + + public static Column findByInputValue(String value) { + return Arrays.stream(values()) + .filter(column -> column.value.equals(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("일치하는 Column 이 없습니다.")) + .column; + } +} diff --git a/src/main/java/chess/view/mapper/PieceMapper.java b/src/main/java/chess/view/mapper/PieceMapper.java new file mode 100644 index 00000000000..c923c3b3baa --- /dev/null +++ b/src/main/java/chess/view/mapper/PieceMapper.java @@ -0,0 +1,37 @@ +package chess.view.mapper; + +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import java.util.Arrays; + +public enum PieceMapper { + + BLACK_PAWN(PieceType.BLACK_PAWN, "P"), + WHITE_PAWN(PieceType.WHITE_PAWN, "p"), + ROOK(PieceType.ROOK, "r"), + KNIGHT(PieceType.KNIGHT, "n"), + BISHOP(PieceType.BISHOP, "b"), + QUEEN(PieceType.QUEEN, "q"), + KING(PieceType.KING, "k"); + + private final PieceType pieceType; + private final String value; + + PieceMapper(PieceType pieceType, String value) { + this.pieceType = pieceType; + this.value = value; + } + + public static String findByPieceType(Piece piece) { + String value = Arrays.stream(values()) + .filter(pieceMapper -> piece.getPieceType() == pieceMapper.pieceType) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("일치하는 기물이 없습니다.")) + .value; + if (piece.isSameColor(Color.BLACK)) { + return value.toUpperCase(); + } + return value; + } +} diff --git a/src/main/java/chess/view/mapper/RowMapper.java b/src/main/java/chess/view/mapper/RowMapper.java new file mode 100644 index 00000000000..99b9a489360 --- /dev/null +++ b/src/main/java/chess/view/mapper/RowMapper.java @@ -0,0 +1,41 @@ +package chess.view.mapper; + +import chess.domain.board.position.Row; +import java.util.Arrays; + +public enum RowMapper { + RANK8(Row.EIGHT, "8", 0), + RANK7(Row.SEVEN, "7", 1), + RANK6(Row.SIX, "6", 2), + RANK5(Row.FIVE, "5", 3), + RANK4(Row.FOUR, "4", 4), + RANK3(Row.THREE, "3", 5), + RANK2(Row.TWO, "2", 6), + RANK1(Row.ONE, "1", 7); + + private final Row row; + private final String value; + private final int arrayIndex; + + RowMapper(Row row, String value, int arrayIndex) { + this.row = row; + this.value = value; + this.arrayIndex = arrayIndex; + } + + public static Row findByInputValue(String value) { + return Arrays.stream(values()) + .filter(row -> row.value.equals(value)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("입력과 일치하는 열이 존재하지 않습니다.")) + .row; + } + + public static int findIndexByRow(Row row) { + return Arrays.stream(values()) + .filter(rowMapper -> rowMapper.row == row) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("열과 일치하는 인덱스가 존재하지 않습니다.")) + .arrayIndex; + } +} 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/controller/command/CommandRouterTest.java b/src/test/java/chess/controller/command/CommandRouterTest.java new file mode 100644 index 00000000000..f049f97cd98 --- /dev/null +++ b/src/test/java/chess/controller/command/CommandRouterTest.java @@ -0,0 +1,59 @@ +package chess.controller.command; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CommandRouterTest { + + @DisplayName("게임 시작 커맨드를 반환한다.") + @Test + void findStartCommendByInputTest() { + Command start = CommandRouter.findCommendByInput(List.of("start")); + + assertThat(start).isInstanceOf(Start.class); + } + + @DisplayName("게임 이동 커맨드를 반환한다.") + @Test + void findMoveCommendByInputTest() { + Command start = CommandRouter.findCommendByInput(List.of("move", "b2", "b4")); + + assertThat(start).isInstanceOf(Move.class); + } + + @DisplayName("게임 종료 커맨드를 반환한다.") + @Test + void findEndCommendByInputTest() { + Command start = CommandRouter.findCommendByInput(List.of("end")); + + assertThat(start).isInstanceOf(End.class); + } + + @DisplayName("게임 점수 커맨드를 반환한다.") + @Test + void findStatusCommendByInputTest() { + Command start = CommandRouter.findCommendByInput(List.of("status")); + + assertThat(start).isInstanceOf(Status.class); + } + + @DisplayName("커맨드 입력 리스트 크기가 0이면 에러를 발생시킨다.") + @Test + void commendInputFormatSizeTest() { + assertThatThrownBy(() -> CommandRouter.findCommendByInput(List.of())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("빈 값 입력을 허용하지 않습니다."); + } + + @DisplayName("입력과 일치하는 커맨드를 찾지 못하면 에러를 발생시킨다.") + @Test + void findCommendByInputFailTest() { + assertThatThrownBy(() -> CommandRouter.findCommendByInput(List.of("jazz"))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바르지 않은 명령어 입력입니다."); + } +} diff --git a/src/test/java/chess/controller/command/EndTest.java b/src/test/java/chess/controller/command/EndTest.java new file mode 100644 index 00000000000..b52ce9733c2 --- /dev/null +++ b/src/test/java/chess/controller/command/EndTest.java @@ -0,0 +1,56 @@ +package chess.controller.command; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.controller.State; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.repository.fake.FakeBoardRepository; +import chess.repository.fake.FakeRoomRepository; +import chess.service.BoardService; +import chess.service.GameService; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EndTest { + + private RoomRepository roomRepository; + private BoardRepository boardRepository; + + @BeforeEach + void setUp() { + roomRepository = new FakeRoomRepository(); + boardRepository = new FakeBoardRepository(); + } + + @DisplayName("명령어 입력이 end로만 이루어져 있으면 정상적으로 생성된다.") + @Test + void validateCommandInputSizeSuccessTest() { + assertThatNoException() + .isThrownBy(() -> new End(List.of("end"))); + } + + @DisplayName("명령어 입력이 end로만 이루어져 있지 않으면 에러를 발생시킨다..") + @Test + void validateCommandInputSizeFailTest() { + assertThatThrownBy(() -> new End(List.of("end", "asd", " "))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("게임 종료 명령어 입력 형식이 올바르지 않습니다."); + } + + @DisplayName("기능을 수행한 후 END 상태를 반환한다.") + @Test + void executeTest() { + End end = new End(List.of("end")); + GameService gameService = new GameService(roomRepository, boardRepository); + BoardService boardService = new BoardService(roomRepository, boardRepository); + + State gameState = end.execute(gameService, boardService, 0L); + + assertThat(gameState).isEqualTo(State.END); + } +} diff --git a/src/test/java/chess/controller/command/MoveTest.java b/src/test/java/chess/controller/command/MoveTest.java new file mode 100644 index 00000000000..23d5060b509 --- /dev/null +++ b/src/test/java/chess/controller/command/MoveTest.java @@ -0,0 +1,26 @@ +package chess.controller.command; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MoveTest { + + @DisplayName("명령어 입력 형식이 올바르면 정상적으로 생성된다.") + @Test + void validateCommandInputSizeSuccessTest() { + assertThatNoException() + .isThrownBy(() -> new Move(List.of("move", "c2", "c3"))); + } + + @DisplayName("명령어 입력이 올바르지 않으면 에러를 발생시킨다..") + @Test + void validateCommandInputSizeFailTest() { + assertThatThrownBy(() -> new Move(List.of("move", "ff", "f0", " "))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("게임 이동 명령어 입력 형식이 올바르지 않습니다."); + } +} diff --git a/src/test/java/chess/controller/command/StartTest.java b/src/test/java/chess/controller/command/StartTest.java new file mode 100644 index 00000000000..ba4188c7312 --- /dev/null +++ b/src/test/java/chess/controller/command/StartTest.java @@ -0,0 +1,56 @@ +package chess.controller.command; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.controller.State; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.repository.fake.FakeBoardRepository; +import chess.repository.fake.FakeRoomRepository; +import chess.service.BoardService; +import chess.service.GameService; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class StartTest { + + private RoomRepository roomRepository; + private BoardRepository boardRepository; + + @BeforeEach + void setUp() { + roomRepository = new FakeRoomRepository(); + boardRepository = new FakeBoardRepository(); + } + + @DisplayName("명령어 입력이 start로만 이루어져 있으면 정상적으로 생성된다.") + @Test + void validateCommandInputSize() { + assertThatNoException() + .isThrownBy(() -> new Start(List.of("start"))); + } + + @DisplayName("명령어 입력이 start로만 이루어져 있지 않으면 에러를 발생시킨다..") + @Test + void validateCommandInputSizeFailTest() { + assertThatThrownBy(() -> new Start(List.of("start", "2", " "))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("게임 시작 명령어 입력 형식이 올바르지 않습니다."); + } + + @DisplayName("기능을 수행한 후 RUNNING 상태를 반환한다.") + @Test + void executeTest() { + Start start = new Start(List.of("start")); + GameService gameService = new GameService(roomRepository, boardRepository); + BoardService boardService = new BoardService(roomRepository, boardRepository); + State gameState = start.execute(gameService, boardService, 0L); + + assertThat(gameState).isEqualTo(State.RUNNING); + } + +} diff --git a/src/test/java/chess/controller/command/StatusTest.java b/src/test/java/chess/controller/command/StatusTest.java new file mode 100644 index 00000000000..d89ec80f2a5 --- /dev/null +++ b/src/test/java/chess/controller/command/StatusTest.java @@ -0,0 +1,55 @@ +package chess.controller.command; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.controller.State; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.repository.fake.FakeBoardRepository; +import chess.repository.fake.FakeRoomRepository; +import chess.service.BoardService; +import chess.service.GameService; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class StatusTest { + + private RoomRepository roomRepository; + private BoardRepository boardRepository; + + @BeforeEach + void setUp() { + roomRepository = new FakeRoomRepository(); + boardRepository = new FakeBoardRepository(); + } + + @DisplayName("명령어 입력이 status로만 이루어져 있으면 정상적으로 생성된다.") + @Test + void validateCommandInputSizeSuccessTest() { + assertThatNoException() + .isThrownBy(() -> new Status(List.of("status"))); + } + + @DisplayName("명령어 입력이 status로만 이루어져 있지 않으면 에러를 발생시킨다..") + @Test + void validateCommandInputSizeFailTest() { + assertThatThrownBy(() -> new Status(List.of("status", " "))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("게임 점수 명령어 입력 형식이 올바르지 않습니다."); + } + + @DisplayName("기능을 수행한 후 RUNNING 상태를 반환한다.") + @Test + void executeTest() { + Status status = new Status(List.of("status")); + GameService gameService = new GameService(roomRepository, boardRepository); + BoardService boardService = new BoardService(roomRepository, boardRepository); + State gameState = status.execute(gameService, boardService, 0L); + + assertThat(gameState).isEqualTo(State.RUNNING); + } +} diff --git a/src/test/java/chess/domain/board/BoardFactoryTest.java b/src/test/java/chess/domain/board/BoardFactoryTest.java new file mode 100644 index 00000000000..0363d33aa10 --- /dev/null +++ b/src/test/java/chess/domain/board/BoardFactoryTest.java @@ -0,0 +1,93 @@ +package chess.domain.board; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class BoardFactoryTest { + + private Map map; + + @BeforeEach + void setUp() { + map = new BoardFactory().initialize(); + } + + @Test + @DisplayName("보드 생성 시 32개의 기물이 초기화된다.") + void mapSizeTest() { + assertThat(map).hasSize(32); + } + + @Nested + @DisplayName("체스 보드 초기화 정상 테스트") + class BoardInitializeTest { + @Test + @DisplayName("검정 폰이 정상적으로 초기화된다.") + void blackPawnInitializeTest() { + assertAll( + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.A)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.H)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.B)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.G)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.C)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.F)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.D)), new Piece(PieceType.BLACK_PAWN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.SEVEN, Column.E)), new Piece(PieceType.BLACK_PAWN, Color.BLACK))); + } + + @Test + @DisplayName("흰색 폰이 정상적으로 초기화된다.") + void whitePawnInitializeTest() { + assertAll( + () -> assertEquals(map.get(new Position(Row.TWO, Column.A)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.H)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.B)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.G)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.C)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.F)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.D)), new Piece(PieceType.WHITE_PAWN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.TWO, Column.E)), new Piece(PieceType.WHITE_PAWN, Color.WHITE))); + } + + @Test + @DisplayName("폰을 제외한 검정 기물들이 정상적으로 초기화된다.") + void blackSpecialPieceInitializeTest() { + assertAll( + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.A)), new Piece(PieceType.ROOK, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.H)), new Piece(PieceType.ROOK, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.B)), new Piece(PieceType.KNIGHT, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.G)), new Piece(PieceType.KNIGHT, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.C)), new Piece(PieceType.BISHOP, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.F)), new Piece(PieceType.BISHOP, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.D)), new Piece(PieceType.QUEEN, Color.BLACK)), + () -> assertEquals(map.get(new Position(Row.EIGHT, Column.E)), new Piece(PieceType.KING, Color.BLACK))); + } + + @Test + @DisplayName("폰을 제외 흰색 기물들이 정상적으로 초기화된다.") + void whiteSpecialPieceInitializeTest() { + assertAll( + () -> assertEquals(map.get(new Position(Row.ONE, Column.A)), new Piece(PieceType.ROOK, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.H)), new Piece(PieceType.ROOK, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.B)), new Piece(PieceType.KNIGHT, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.G)), new Piece(PieceType.KNIGHT, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.C)), new Piece(PieceType.BISHOP, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.F)), new Piece(PieceType.BISHOP, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.D)), new Piece(PieceType.QUEEN, Color.WHITE)), + () -> assertEquals(map.get(new Position(Row.ONE, Column.E)), new Piece(PieceType.KING, Color.WHITE))); + } + } +} diff --git a/src/test/java/chess/domain/board/ColumnTest.java b/src/test/java/chess/domain/board/ColumnTest.java new file mode 100644 index 00000000000..d792a59f8f3 --- /dev/null +++ b/src/test/java/chess/domain/board/ColumnTest.java @@ -0,0 +1,48 @@ +package chess.domain.board; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import chess.domain.board.position.Column; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ColumnTest { + + @Test + @DisplayName("거리 만큼 이동한 Column를 반환한다.") + void calculateNextColumnSuccessTest() { + Column column = Column.E; + + assertAll( + () -> assertEquals(Column.D, column.calculateNextColumn(-1)), + () -> assertEquals(Column.F, column.calculateNextColumn(1)), + () -> assertEquals(Column.E, column.calculateNextColumn(0)) + ); + } + + @Test + @DisplayName("거리 만큼 이동한 값이 보드판을 벗어난 경우 에러를 반환한다.") + void calculateNextColumnFailTest() { + Column column = Column.D; + + assertThatThrownBy(() -> column.calculateNextColumn(-5)) + .isInstanceOf(IllegalArgumentException.class); + } + + + + @Test + @DisplayName("거리 만큼 이동한 값이 보드판을 벗어난 경우 false를 반환한다.") + void isNextInRange() { + assertAll( + () -> assertFalse(Column.A.isNextInRange(-1)), + () -> assertFalse(Column.H.isNextInRange(1)), + () -> assertTrue(Column.A.isNextInRange(1)), + () -> assertTrue(Column.H.isNextInRange(-1)) + ); + } +} diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java new file mode 100644 index 00000000000..d87b8ba5a46 --- /dev/null +++ b/src/test/java/chess/domain/board/PositionTest.java @@ -0,0 +1,19 @@ +package chess.domain.board; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PositionTest { + + @Test + @DisplayName("포지션 생성 테스트") + void constructTest() { + Position position = new Position(Row.THREE, Column.H); + + Assertions.assertThat(position).isNotNull(); + } +} diff --git a/src/test/java/chess/domain/board/RowTest.java b/src/test/java/chess/domain/board/RowTest.java new file mode 100644 index 00000000000..611e41918f1 --- /dev/null +++ b/src/test/java/chess/domain/board/RowTest.java @@ -0,0 +1,67 @@ +package chess.domain.board; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import chess.domain.board.position.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RowTest { + + @Test + @DisplayName("거리 만큼 이동한 Row를 반환한다.") + void calculateNextRowSuccessTest() { + Row row = Row.FOUR; + + assertAll( + () -> assertEquals(Row.FIVE, row.calculateNextRow(1)), + () -> assertEquals(Row.THREE, row.calculateNextRow(-1)), + () -> assertEquals(Row.FOUR, row.calculateNextRow(0)) + ); + } + + @Test + @DisplayName("거리 만큼 이동한 값이 보드판을 벗어난 경우 에러를 반환한다.") + void calculateNextRowFailTest() { + Row row = Row.FOUR; + + assertThatThrownBy(() -> row.calculateNextRow(-5)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("다음 위치로 이동할 수 있는 열이 없습니다."); + } + + @Test + @DisplayName("거리 만큼 이동한 값이 보드판을 벗어난 경우 false를 반환한다.") + void isNextInRange() { + assertAll( + () -> assertFalse(Row.EIGHT.isNextInRange(1)), + () -> assertFalse(Row.ONE.isNextInRange(-1)) + ); + } + + @Test + @DisplayName("인덱스로 일치하는 열을 반환한다.") + void findByIndexTest() { + assertAll( + () -> assertEquals(Row.EIGHT, Row.findByIndex(8)), + () -> assertEquals(Row.SEVEN, Row.findByIndex(7)), + () -> assertEquals(Row.SIX, Row.findByIndex(6)), + () -> assertEquals(Row.FIVE, Row.findByIndex(5)), + () -> assertEquals(Row.FOUR, Row.findByIndex(4)), + () -> assertEquals(Row.THREE, Row.findByIndex(3)), + () -> assertEquals(Row.TWO, Row.findByIndex(2)), + () -> assertEquals(Row.ONE, Row.findByIndex(1)) + ); + } + + @Test + @DisplayName("인덱스로 일치하는 열 반환에 실패하면 에러를 반환한다.") + void findByIndexFailTest() { + assertThatThrownBy(() -> Row.findByIndex(-1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("열과 일치하는 인덱스가 없습니다."); + } +} diff --git a/src/test/java/chess/domain/game/RoomNameTest.java b/src/test/java/chess/domain/game/RoomNameTest.java new file mode 100644 index 00000000000..9968159065d --- /dev/null +++ b/src/test/java/chess/domain/game/RoomNameTest.java @@ -0,0 +1,25 @@ +package chess.domain.game; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RoomNameTest { + + @DisplayName("게임방 이름 길이가 16글자 이하면 정상 생성된다.") + @Test + void validateNameLengthSuccessTest() { + assertThatNoException() + .isThrownBy(() -> new RoomName("----------------")); + } + + @DisplayName("게임방 이름 길이가 16글자를 초과하면 에러를 발생시킨다.") + @Test + void validateNameLengthFailTest() { + assertThatThrownBy(() -> new RoomName("-----------------")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("게임 방 이름 길이는 최대 16글자까지 가능합니다."); + } +} diff --git a/src/test/java/chess/domain/game/WinnerTest.java b/src/test/java/chess/domain/game/WinnerTest.java new file mode 100644 index 00000000000..e290c7f0db6 --- /dev/null +++ b/src/test/java/chess/domain/game/WinnerTest.java @@ -0,0 +1,68 @@ +package chess.domain.game; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.piece.Color; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class WinnerTest { + + @Nested + @DisplayName("점수로 승리를 결정한다.") + class SelectWinnerByScoreTest { + + @DisplayName("흰색 팀의 점수가 검정 팀보다 높으면 흰색 팀이 승리한다.") + @Test + void whiteTeamScoreIsGreaterThanBlackTeam() { + Map map = Map.of(Color.WHITE, new Score(33.0), Color.BLACK, new Score(27.0)); + + Winner winner = Winner.selectWinnerByScore(map); + + assertThat(winner).isEqualTo(Winner.WHITE_WIN); + } + + @DisplayName("검정 팀의 점수가 흰색 팀보다 높으면 검정 팀이 승리한다.") + @Test + void blackTeamScoreIsGreaterThanWhiteTeam() { + Map map = Map.of(Color.WHITE, new Score(26.9), Color.BLACK, new Score(27.0)); + + Winner winner = Winner.selectWinnerByScore(map); + + assertThat(winner).isEqualTo(Winner.BLACK_WIN); + } + + @DisplayName("검정 팀의 점수가 흰색 팀과 같으면 무승부") + @Test + void blackTeamScoreEqualWhiteTeam() { + Map map = Map.of(Color.WHITE, new Score(27.0), Color.BLACK, new Score(27.0)); + + Winner winner = Winner.selectWinnerByScore(map); + + assertThat(winner).isEqualTo(Winner.DRAW); + } + } + + @Nested + @DisplayName("체크메이트로 승리를 결정한다") + class SelectWinnerByCheckmateTest { + + @DisplayName("혼자 살아있는 왕이 흰색이면 흰색이 승리한다.") + @Test + void blackKingCheckmateTest() { + Winner winner = Winner.selectWinnerByCheckmate(Color.WHITE); + + assertThat(winner).isEqualTo(Winner.WHITE_WIN); + } + + @DisplayName("혼자 살아있는 왕이 검은색이면 검은색이 승리한다.") + @Test + void whiteKingCheckmateTest() { + Winner winner = Winner.selectWinnerByCheckmate(Color.BLACK); + + assertThat(winner).isEqualTo(Winner.BLACK_WIN); + } + } +} diff --git a/src/test/java/chess/domain/positionFilter/BishopTest.java b/src/test/java/chess/domain/positionFilter/BishopTest.java new file mode 100644 index 00000000000..39585e641a1 --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/BishopTest.java @@ -0,0 +1,87 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BishopTest { + + @Test + @DisplayName("해당 포지션이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void positionEmptyAllMovablePositionsIncludedTest() { + Piece piece = new Piece(PieceType.BISHOP, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.E), + new Position(Row.SEVEN, Column.F), + new Position(Row.EIGHT, Column.G), + + new Position(Row.FOUR, Column.E), + new Position(Row.THREE, Column.F), + new Position(Row.TWO, Column.G), + new Position(Row.ONE, Column.H), + + new Position(Row.FOUR, Column.C), + new Position(Row.THREE, Column.B), + new Position(Row.TWO, Column.A), + + new Position(Row.SIX, Column.C), + new Position(Row.SEVEN, Column.B), + new Position(Row.EIGHT, Column.A) + ); + } + + @Test + @DisplayName("해당 포지션에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void positionWithOpponentPiecesTest() { + Piece piece = new Piece(PieceType.BISHOP, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.E), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.C), + new Position(Row.SIX, Column.C) + ); + } + + @Test + @DisplayName("해당 포지션이 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void positionWithOwnPiecesTest() { + Piece piece = new Piece(PieceType.BISHOP, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/positionFilter/KingTest.java b/src/test/java/chess/domain/positionFilter/KingTest.java new file mode 100644 index 00000000000..d0ae5345679 --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/KingTest.java @@ -0,0 +1,83 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class KingTest { + + @Test + @DisplayName("해당 포지션이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void positionEmptyAllMovablePositionsIncludedTest() { + Piece piece = new Piece(PieceType.KING, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.D), + new Position(Row.SIX, Column.E), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.D), + new Position(Row.FOUR, Column.C), + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.C) + ); + } + + @Test + @DisplayName("해당 포지션에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void positionWithOpponentPiecesTest() { + Piece piece = new Piece(PieceType.KING, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.D), + new Position(Row.SIX, Column.E), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.D), + new Position(Row.FOUR, Column.C), + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.C) + ); + } + + @Test + @DisplayName("해당 포지션이 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void positionWithOwnPiecesTest() { + Piece piece = new Piece(PieceType.KING, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/positionFilter/KnightTest.java b/src/test/java/chess/domain/positionFilter/KnightTest.java new file mode 100644 index 00000000000..3cfe78c11f3 --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/KnightTest.java @@ -0,0 +1,83 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class KnightTest { + + @Test + @DisplayName("해당 포지션이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void positionEmptyAllMovablePositionsIncludedTest() { + Piece piece = new Piece(PieceType.KNIGHT, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SEVEN, Column.C), + new Position(Row.SEVEN, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.FOUR, Column.F), + new Position(Row.THREE, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.FOUR, Column.B) + ); + } + + @Test + @DisplayName("해당 포지션에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void positionWithOpponentPiecesTest() { + Piece piece = new Piece(PieceType.KNIGHT, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SEVEN, Column.C), + new Position(Row.SEVEN, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.FOUR, Column.F), + new Position(Row.THREE, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.FOUR, Column.B) + ); + } + + @Test + @DisplayName("해당 포지션이 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void positionWithOwnPiecesTest() { + Piece piece = new Piece(PieceType.KNIGHT, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/positionFilter/PawnTest.java b/src/test/java/chess/domain/positionFilter/PawnTest.java new file mode 100644 index 00000000000..f7319731b5d --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/PawnTest.java @@ -0,0 +1,71 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PawnTest { + + @Test + @DisplayName("시작 위치 앞 2칸이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void startWithEmptyFrontTwoPositionsTest() { + Piece piece = new Piece(PieceType.WHITE_PAWN, Color.WHITE); + Position position = new Position(Row.TWO, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.D) + ); + } + + @Test + @DisplayName("대각선에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void diagonalMoveWithOpponentPiecePresentTest() { + Piece piece = new Piece(PieceType.WHITE_PAWN, Color.WHITE); + Position position = new Position(Row.TWO, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.THREE, Column.C), + new Position(Row.THREE, Column.E) + ); + } + + @Test + @DisplayName("앞에 기물이 존재하고 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void cannotMoveIfFriendlyPieceAhead() { + Piece piece = new Piece(PieceType.WHITE_PAWN, Color.WHITE); + Position position = new Position(Row.TWO, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/positionFilter/QueenTest.java b/src/test/java/chess/domain/positionFilter/QueenTest.java new file mode 100644 index 00000000000..f659530cc07 --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/QueenTest.java @@ -0,0 +1,109 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class QueenTest { + + @Test + @DisplayName("해당 포지션이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void positionEmptyAllMovablePositionsIncludedTest() { + Piece piece = new Piece(PieceType.QUEEN, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.E), + new Position(Row.SEVEN, Column.F), + new Position(Row.EIGHT, Column.G), + + new Position(Row.FOUR, Column.E), + new Position(Row.THREE, Column.F), + new Position(Row.TWO, Column.G), + new Position(Row.ONE, Column.H), + + new Position(Row.FOUR, Column.C), + new Position(Row.THREE, Column.B), + new Position(Row.TWO, Column.A), + + new Position(Row.SIX, Column.C), + new Position(Row.SEVEN, Column.B), + new Position(Row.EIGHT, Column.A), + + new Position(Row.SIX, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.EIGHT, Column.D), + + new Position(Row.FIVE, Column.E), + new Position(Row.FIVE, Column.F), + new Position(Row.FIVE, Column.G), + new Position(Row.FIVE, Column.H), + + new Position(Row.FOUR, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.ONE, Column.D), + + new Position(Row.FIVE, Column.C), + new Position(Row.FIVE, Column.B), + new Position(Row.FIVE, Column.A) + ); + } + + @Test + @DisplayName("해당 포지션에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void positionWithOpponentPiecesTest() { + Piece piece = new Piece(PieceType.QUEEN, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.E), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.C), + new Position(Row.SIX, Column.C), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.D), + new Position(Row.FIVE, Column.C) + ); + } + + @Test + @DisplayName("해당 포지션이 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void positionWithOwnPiecesTest() { + Piece piece = new Piece(PieceType.QUEEN, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/positionFilter/RookTest.java b/src/test/java/chess/domain/positionFilter/RookTest.java new file mode 100644 index 00000000000..b366ded744d --- /dev/null +++ b/src/test/java/chess/domain/positionFilter/RookTest.java @@ -0,0 +1,88 @@ +package chess.domain.positionFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.game.PositionsFilter; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.fake.BlackPieceRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RookTest { + + @Test + @DisplayName("해당 포지션이 비어있을 시 이동 가능한 포지션에 모두 포함되어야 한다.") + void positionEmptyAllMovablePositionsIncludedTest() { + Piece piece = new Piece(PieceType.ROOK, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new NotExistsPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.EIGHT, Column.D), + + new Position(Row.FIVE, Column.E), + new Position(Row.FIVE, Column.F), + new Position(Row.FIVE, Column.G), + new Position(Row.FIVE, Column.H), + + new Position(Row.FOUR, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.ONE, Column.D), + + new Position(Row.FIVE, Column.C), + new Position(Row.FIVE, Column.B), + new Position(Row.FIVE, Column.A) + ); + } + + @Test + @DisplayName("해당 포지션에 상대 기물이 존재하면 이동할 수 있는 위치에 포함되어야 한다.") + void positionWithOpponentPiecesTest() { + Piece piece = new Piece(PieceType.ROOK, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new BlackPieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).containsExactlyInAnyOrder( + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.D), + new Position(Row.FIVE, Column.C) + ); + } + + @Test + @DisplayName("해당 포지션이 우리팀 기물일 시 이동가능한 위치에 포함되어서는 안된다.") + void positionWithOwnPiecesTest() { + Piece piece = new Piece(PieceType.ROOK, Color.WHITE); + Position position = new Position(Row.FIVE, Column.D); + Map> candidateAllPositions = piece.generateAllDirectionPositions(position); + PositionsFilter positionsFilter = new PositionsFilter(new WhitePieceRepository(), + candidateAllPositions); + + List movablePositions = positionsFilter.generateValidPositions(piece, 0L); + + assertThat(movablePositions).isEmpty(); + } +} diff --git a/src/test/java/chess/domain/strategy/BishopMoveStrategyTest.java b/src/test/java/chess/domain/strategy/BishopMoveStrategyTest.java new file mode 100644 index 00000000000..28a0cab936e --- /dev/null +++ b/src/test/java/chess/domain/strategy/BishopMoveStrategyTest.java @@ -0,0 +1,88 @@ +package chess.domain.strategy; + +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BishopMoveStrategyTest { + + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * .b...... 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("비숍이 B2 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnEdgePosition() { + Position position = new Position(Row.TWO, Column.B); + MoveStrategy moveStrategy = new BishopMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> Assertions.assertThat(directionListMap.get(Direction.NE)).containsExactly( + new Position(Row.THREE, Column.C), + new Position(Row.FOUR, Column.D), + new Position(Row.FIVE, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.SEVEN, Column.G), + new Position(Row.EIGHT, Column.H) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.SE)).containsExactly( + new Position(Row.ONE, Column.C) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.SW)).containsExactly( + new Position(Row.ONE, Column.A) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.NW)).containsExactly( + new Position(Row.THREE, Column.A) + ) + ); + } + + /** + * ........ 8 (rank 8) ........ 7 ........ 6 ........ 5 ........ 4 ........ 3 ........ 2 ..b..... 1 (rank + * 1) + *

+ * abcdefgh + */ + @Test + @DisplayName("비숍이 C1 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnCenterPosition() { + Position position = new Position(Row.ONE, Column.C); + BishopMoveStrategy bishopMoveStrategy = new BishopMoveStrategy(); + + Map> directionListMap = bishopMoveStrategy.generateMovablePositions(position); + + assertAll( + () -> Assertions.assertThat(directionListMap.get(Direction.NE)).containsExactly( + new Position(Row.TWO, Column.D), + new Position(Row.THREE, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FIVE, Column.G), + new Position(Row.SIX, Column.H) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.SE)).isEmpty(), + () -> Assertions.assertThat(directionListMap.get(Direction.SW)).isEmpty(), + () -> Assertions.assertThat(directionListMap.get(Direction.NW)).containsExactly( + new Position(Row.TWO, Column.B), + new Position(Row.THREE, Column.A) + ) + ); + } +} diff --git a/src/test/java/chess/domain/strategy/BlackPawnMoveStrategyTest.java b/src/test/java/chess/domain/strategy/BlackPawnMoveStrategyTest.java new file mode 100644 index 00000000000..2f84ceeaea5 --- /dev/null +++ b/src/test/java/chess/domain/strategy/BlackPawnMoveStrategyTest.java @@ -0,0 +1,70 @@ +package chess.domain.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.piece.PieceType; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BlackPawnMoveStrategyTest { + + /** + * ........ 8 (rank 8) + * ......P. 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ........ 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("블랙 폰은 시작 위치에서는 아래로 2칸을 움직일 수 있다.") + void canMoveTwoDistanceAtStartPositionTest() { + PieceType blackPawn = PieceType.BLACK_PAWN; + + Map> directionListMap = blackPawn.generateAllDirectionPositions( + new Position(Row.SEVEN, Column.G)); + + assertAll( + () -> assertThat(directionListMap.get(Direction.S)).containsExactly( + new Position(Row.SIX, Column.G), + new Position(Row.FIVE, Column.G)), + () -> assertThat(directionListMap.get(Direction.N)).isNull(), + () -> assertThat(directionListMap.get(Direction.E)).isNull(), + () -> assertThat(directionListMap.get(Direction.W)).isNull(), + () -> assertThat(directionListMap.get(Direction.SE)).containsExactly(new Position(Row.SIX, Column.H)), + () -> assertThat(directionListMap.get(Direction.SW)).containsExactly(new Position(Row.SIX, Column.F)) + ); + } + + /** + * ........ 8 (rank 8) ........ 7 .......P 6 ........ 5 ........ 4 ........ 3 ........ 2 ........ 1 (rank + * 1) + *

+ * abcdefgh + */ + @Test + @DisplayName("블랙 폰은 시작 위치가 아니면 아래로 1칸만 움직일 수 있다.") + void canMoveOneDistancePositionTest() { + PieceType blackPawn = PieceType.BLACK_PAWN; + + Map> directionListMap = blackPawn.generateAllDirectionPositions( + new Position(Row.SIX, Column.H)); + + assertAll( + () -> assertThat(directionListMap.get(Direction.S)).containsExactly( + new Position(Row.FIVE, Column.H) + )) + ; + } +} diff --git a/src/test/java/chess/domain/strategy/KingMoveStrategyTest.java b/src/test/java/chess/domain/strategy/KingMoveStrategyTest.java new file mode 100644 index 00000000000..e97cc2895f9 --- /dev/null +++ b/src/test/java/chess/domain/strategy/KingMoveStrategyTest.java @@ -0,0 +1,63 @@ +package chess.domain.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class KingMoveStrategyTest { + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ...k.... 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("킹이 D2 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnEdgePosition() { + Position position = new Position(Row.TWO, Column.D); + MoveStrategy moveStrategy = new KingMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.THREE, Column.D) + ), + () -> assertThat(directionListMap.get(Direction.NE)).containsExactly( + new Position(Row.THREE, Column.E) + ), + () -> assertThat(directionListMap.get(Direction.E)).containsExactly( + new Position(Row.TWO, Column.E) + ), + () -> assertThat(directionListMap.get(Direction.SE)).containsExactly( + new Position(Row.ONE, Column.E) + ), + () -> assertThat(directionListMap.get(Direction.S)).containsExactly( + new Position(Row.ONE, Column.D) + ), + () -> assertThat(directionListMap.get(Direction.SW)).containsExactly( + new Position(Row.ONE, Column.C) + ), + () -> assertThat(directionListMap.get(Direction.W)).containsExactly( + new Position(Row.TWO, Column.C) + ), + () -> assertThat(directionListMap.get(Direction.NW)).containsExactly( + new Position(Row.THREE, Column.C) + ) + ); + } +} diff --git a/src/test/java/chess/domain/strategy/KnightMoveStrategyTest.java b/src/test/java/chess/domain/strategy/KnightMoveStrategyTest.java new file mode 100644 index 00000000000..b514cb0523b --- /dev/null +++ b/src/test/java/chess/domain/strategy/KnightMoveStrategyTest.java @@ -0,0 +1,59 @@ +package chess.domain.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class KnightMoveStrategyTest { + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ...n.... 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("나이트가 D2 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnEdgePosition() { + Position position = new Position(Row.TWO, Column.D); + MoveStrategy moveStrategy = new KnightMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> assertThat(directionListMap.get(Direction.NNE)).containsExactly( + new Position(Row.FOUR, Column.E) + ), + () -> assertThat(directionListMap.get(Direction.ENE)).containsExactly( + new Position(Row.THREE, Column.F) + ), + () -> assertThat(directionListMap.get(Direction.ESE)).containsExactly( + new Position(Row.ONE, Column.F) + ), + () -> assertThat(directionListMap.get(Direction.SSE)).isEmpty(), + () -> assertThat(directionListMap.get(Direction.SSW)).isEmpty(), + () -> assertThat(directionListMap.get(Direction.WSW)).containsExactly( + new Position(Row.ONE, Column.B) + ), + () -> assertThat(directionListMap.get(Direction.WNW)).containsExactly( + new Position(Row.THREE, Column.B) + ), + () -> assertThat(directionListMap.get(Direction.NNW)).containsExactly( + new Position(Row.FOUR, Column.C) + ) + ); + } +} diff --git a/src/test/java/chess/domain/strategy/QueenMoveStrategyTest.java b/src/test/java/chess/domain/strategy/QueenMoveStrategyTest.java new file mode 100644 index 00000000000..453e59746db --- /dev/null +++ b/src/test/java/chess/domain/strategy/QueenMoveStrategyTest.java @@ -0,0 +1,79 @@ +package chess.domain.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class QueenMoveStrategyTest { + + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ...q.... 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("퀸이 D2 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnEdgePosition() { + Position position = new Position(Row.TWO, Column.D); + MoveStrategy moveStrategy = new QueenMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.EIGHT, Column.D) + ), + () -> assertThat(directionListMap.get(Direction.NE)).containsExactly( + new Position(Row.THREE, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FIVE, Column.G), + new Position(Row.SIX, Column.H) + ), + () -> assertThat(directionListMap.get(Direction.E)).containsExactly( + new Position(Row.TWO, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.TWO, Column.G), + new Position(Row.TWO, Column.H) + ), + () -> assertThat(directionListMap.get(Direction.SE)).containsExactly( + new Position(Row.ONE, Column.E) + ), + () -> assertThat(directionListMap.get(Direction.S)).containsExactly( + new Position(Row.ONE, Column.D) + ), + () -> assertThat(directionListMap.get(Direction.SW)).containsExactly( + new Position(Row.ONE, Column.C) + ), + () -> assertThat(directionListMap.get(Direction.W)).containsExactly( + new Position(Row.TWO, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.TWO, Column.A) + ), + () -> assertThat(directionListMap.get(Direction.NW)).containsExactly( + new Position(Row.THREE, Column.C), + new Position(Row.FOUR, Column.B), + new Position(Row.FIVE, Column.A) + ) + ); + } +} diff --git a/src/test/java/chess/domain/strategy/RookMoveStrategyTest.java b/src/test/java/chess/domain/strategy/RookMoveStrategyTest.java new file mode 100644 index 00000000000..6b96804180d --- /dev/null +++ b/src/test/java/chess/domain/strategy/RookMoveStrategyTest.java @@ -0,0 +1,85 @@ +package chess.domain.strategy; + +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RookMoveStrategyTest { + + @Test + @DisplayName("룩이 A1 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnEdgePosition() { + Position position = new Position(Row.ONE, Column.A); + MoveStrategy moveStrategy = new RookMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> Assertions.assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.TWO, Column.A), + new Position(Row.THREE, Column.A), + new Position(Row.FOUR, Column.A), + new Position(Row.FIVE, Column.A), + new Position(Row.SIX, Column.A), + new Position(Row.SEVEN, Column.A), + new Position(Row.EIGHT, Column.A) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.S)).isEmpty(), + () -> Assertions.assertThat(directionListMap.get(Direction.E)).containsExactly( + new Position(Row.ONE, Column.B), + new Position(Row.ONE, Column.C), + new Position(Row.ONE, Column.D), + new Position(Row.ONE, Column.E), + new Position(Row.ONE, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.ONE, Column.H) + + ), + () -> Assertions.assertThat(directionListMap.get(Direction.W)).isEmpty() + ); + } + + @Test + @DisplayName("룩이 D4 에 있을 때 방향에 따라 움직일 수 있는 후보 포지션을 차례대로 저장한다.") + void calculateCandidateOnCenterPosition() { + Position position = new Position(Row.FOUR, Column.D); + MoveStrategy moveStrategy = new RookMoveStrategy(); + + Map> directionListMap = moveStrategy.generateMovablePositions(position); + + assertAll( + () -> Assertions.assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.FIVE, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.EIGHT, Column.D) + ), + () -> Assertions.assertThat(directionListMap.get(Direction.E)).containsExactly( + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + + ), + () -> Assertions.assertThat(directionListMap.get(Direction.S)).containsExactly( + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.ONE, Column.D) + ) + , + () -> Assertions.assertThat(directionListMap.get(Direction.W)).containsExactly( + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.A) + ) + ); + } +} diff --git a/src/test/java/chess/domain/strategy/WhitePawnMoveStrategyTest.java b/src/test/java/chess/domain/strategy/WhitePawnMoveStrategyTest.java new file mode 100644 index 00000000000..2016034b19f --- /dev/null +++ b/src/test/java/chess/domain/strategy/WhitePawnMoveStrategyTest.java @@ -0,0 +1,76 @@ +package chess.domain.strategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Direction; +import chess.domain.piece.PieceType; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import java.util.Map; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class WhitePawnMoveStrategyTest { + + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * ........ 3 + * ......p. 2 + * ........ 1 (rank 1) + * + * abcdefgh + */ + @Test + @DisplayName("화이트 폰은 시작 위치에서는 아래로 2칸을 움직일 수 있다.") + void canMoveTwoDistanceAtStartPositionTest() { + PieceType whitePawn = PieceType.WHITE_PAWN; + + Map> directionListMap = whitePawn.generateAllDirectionPositions( + new Position(Row.TWO, Column.G)); + + assertAll( + () -> assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.THREE, Column.G), + new Position(Row.FOUR, Column.G)), + () -> assertThat(directionListMap.get(Direction.S)).isNull(), + () -> assertThat(directionListMap.get(Direction.E)).isNull(), + () -> assertThat(directionListMap.get(Direction.W)).isNull(), + () -> assertThat(directionListMap.get(Direction.NE)).containsExactly(new Position(Row.THREE, Column.H)), + () -> assertThat(directionListMap.get(Direction.NW)).containsExactly(new Position(Row.THREE, Column.F)) + ); + } + + /** + * ........ 8 (rank 8) + * ........ 7 + * ........ 6 + * ........ 5 + * ........ 4 + * .......p 3 + * ........ 2 + * ........ 1 (rank1) + * + * abcdefgh + */ + @Test + @DisplayName("화이트 폰은 시작 위치가 아니면 위로 1칸만 움직일 수 있다.") + void canMoveOneDistancePositionTest() { + PieceType whitePawn = PieceType.WHITE_PAWN; + + Map> directionListMap = whitePawn.generateAllDirectionPositions( + new Position(Row.THREE, Column.H)); + + assertAll( + () -> assertThat(directionListMap.get(Direction.N)).containsExactly( + new Position(Row.FOUR, Column.H) + ) + ); + } +} diff --git a/src/test/java/chess/repository/fake/BlackPieceRepository.java b/src/test/java/chess/repository/fake/BlackPieceRepository.java new file mode 100644 index 00000000000..5721a27ffd5 --- /dev/null +++ b/src/test/java/chess/repository/fake/BlackPieceRepository.java @@ -0,0 +1,50 @@ +package chess.repository.fake; + +import chess.domain.board.position.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.BoardRepository; +import java.util.List; +import java.util.Map; + +public class BlackPieceRepository implements BoardRepository { + + @Override + public void savePiece(Piece piece, Position position, Long roomId) { + } + + @Override + public boolean existsPieceByPosition(Position position, Long roomId) { + return true; + } + + @Override + public void deletePieceByPosition(Position position, Long roomId) { + } + + @Override + public Piece findPieceByPosition(Position position, Long roomId) { + return new Piece(PieceType.BLACK_PAWN, Color.BLACK); + } + + @Override + public List findPiecesByColor(Color piece_color, Long roomId) { + return null; + } + + @Override + public List getPieceCountByPieceType(PieceType pieceType, Long roomId) { + return null; + } + + @Override + public Map findAllPieceByRoomId(Long roomId) { + return null; + } + + @Override + public List findPieceByPieceType(PieceType pieceType, Long roomId) { + return null; + } +} diff --git a/src/test/java/chess/repository/fake/FakeBoardRepository.java b/src/test/java/chess/repository/fake/FakeBoardRepository.java new file mode 100644 index 00000000000..635bd6afbb7 --- /dev/null +++ b/src/test/java/chess/repository/fake/FakeBoardRepository.java @@ -0,0 +1,62 @@ +package chess.repository.fake; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.BoardRepository; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FakeBoardRepository implements BoardRepository { + + @Override + public void savePiece(Piece piece, Position position, Long roomId) { + } + + @Override + public boolean existsPieceByPosition(Position position, Long roomId) { + return true; + } + + @Override + public void deletePieceByPosition(Position position, Long roomId) { + } + + @Override + public Piece findPieceByPosition(Position position, Long roomId) { + return new Piece(PieceType.KING, Color.WHITE); + } + + @Override + public List findPiecesByColor(Color piece_color, Long roomId) { + return List.of( + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.ROOK, Color.WHITE)); + } + + @Override + public List getPieceCountByPieceType(PieceType pieceType, Long roomId) { + return List.of(2, 2); + } + + @Override + public Map findAllPieceByRoomId(Long roomId) { + Map map = new HashMap<>(); + map.put(new Position(Row.TWO, Column.C), new Piece(PieceType.WHITE_PAWN, Color.WHITE)); + return map; + } + + @Override + public List findPieceByPieceType(PieceType pieceType, Long roomId) { + return List.of( + new Piece(PieceType.KING, Color.WHITE), + new Piece(PieceType.KING, Color.BLACK)); + } +} diff --git a/src/test/java/chess/repository/fake/FakeRoomRepository.java b/src/test/java/chess/repository/fake/FakeRoomRepository.java new file mode 100644 index 00000000000..4d7aa89e3b1 --- /dev/null +++ b/src/test/java/chess/repository/fake/FakeRoomRepository.java @@ -0,0 +1,39 @@ +package chess.repository.fake; + +import chess.domain.game.Room; +import chess.domain.game.RoomName; +import chess.domain.piece.Color; +import chess.repository.RoomRepository; +import java.util.List; + +public class FakeRoomRepository implements RoomRepository { + @Override + public List findAllRoomNames() { + return null; + } + + @Override + public boolean existsRoomName(RoomName roomName) { + return false; + } + + @Override + public void saveRoom(RoomName roomName) { + + } + + @Override + public void updateRoomTurn(Color color, Long roomId) { + + } + + @Override + public Room findRoomByName(RoomName roomName) { + return null; + } + + @Override + public Color findTurnById(Long roomId) { + return Color.BLACK; + } +} diff --git a/src/test/java/chess/repository/fake/NotExistsPieceRepository.java b/src/test/java/chess/repository/fake/NotExistsPieceRepository.java new file mode 100644 index 00000000000..dc86c643f05 --- /dev/null +++ b/src/test/java/chess/repository/fake/NotExistsPieceRepository.java @@ -0,0 +1,50 @@ +package chess.repository.fake; + +import chess.domain.board.position.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.BoardRepository; +import java.util.List; +import java.util.Map; + +public class NotExistsPieceRepository implements BoardRepository { + + @Override + public void savePiece(Piece piece, Position position, Long roomId) { + } + + @Override + public boolean existsPieceByPosition(Position position, Long roomId) { + return false; + } + + @Override + public void deletePieceByPosition(Position position, Long roomId) { + } + + @Override + public Piece findPieceByPosition(Position position, Long roomId) { + return null; + } + + @Override + public List findPiecesByColor(Color piece_color, Long roomId) { + return null; + } + + @Override + public List getPieceCountByPieceType(PieceType pieceType, Long roomId) { + return null; + } + + @Override + public Map findAllPieceByRoomId(Long roomId) { + return null; + } + + @Override + public List findPieceByPieceType(PieceType pieceType, Long roomId) { + return null; + } +} diff --git a/src/test/java/chess/repository/fake/WhitePieceRepository.java b/src/test/java/chess/repository/fake/WhitePieceRepository.java new file mode 100644 index 00000000000..3e7f3c2c65a --- /dev/null +++ b/src/test/java/chess/repository/fake/WhitePieceRepository.java @@ -0,0 +1,55 @@ +package chess.repository.fake; + +import chess.domain.board.position.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceType; +import chess.repository.BoardRepository; +import java.util.List; +import java.util.Map; + +public class WhitePieceRepository implements BoardRepository { + + @Override + public void savePiece(Piece piece, Position position, Long roomId) { + } + + @Override + public boolean existsPieceByPosition(Position position, Long roomId) { + return true; + } + + @Override + public void deletePieceByPosition(Position position, Long roomId) { + } + + @Override + public Piece findPieceByPosition(Position position, Long roomId) { + return new Piece(PieceType.WHITE_PAWN, Color.WHITE); + } + + @Override + public List findPiecesByColor(Color piece_color, Long roomId) { + return List.of( + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.WHITE_PAWN, Color.WHITE), + new Piece(PieceType.ROOK, Color.WHITE)); + } + + @Override + public List getPieceCountByPieceType(PieceType pieceType, Long roomId) { + return List.of(2, 2); + } + + @Override + public Map findAllPieceByRoomId(Long roomId) { + return null; + } + + @Override + public List findPieceByPieceType(PieceType pieceType, Long roomId) { + return List.of(new Piece(PieceType.KING, Color.WHITE)); + } +} diff --git a/src/test/java/chess/service/BoardServiceTest.java b/src/test/java/chess/service/BoardServiceTest.java new file mode 100644 index 00000000000..2a078d138f3 --- /dev/null +++ b/src/test/java/chess/service/BoardServiceTest.java @@ -0,0 +1,64 @@ +package chess.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.domain.board.position.Column; +import chess.domain.board.position.Position; +import chess.domain.board.position.Row; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.repository.fake.FakeBoardRepository; +import chess.repository.fake.FakeRoomRepository; +import chess.repository.fake.NotExistsPieceRepository; +import chess.repository.fake.WhitePieceRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BoardServiceTest { + + private RoomRepository roomRepository; + private Position from; + private Position to; + + @BeforeEach + void setUp() { + roomRepository = new FakeRoomRepository(); + from = new Position(Row.TWO, Column.C); + to = new Position(Row.THREE, Column.C); + } + + @DisplayName("선택한 위치에 기물이 존재하지 않으면 에러를 반환한다.") + @Test + void findFromPositionPieceTest() { + BoardRepository boardRepository = new NotExistsPieceRepository(); + BoardService boardService = new BoardService(roomRepository, boardRepository); + + assertThatThrownBy(() -> boardService.movePiece(from, to, 0L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("선택한 기물이 존재하지 않습니다."); + } + + @DisplayName("이동시키려는 기물이 상대 기물이면 에러를 반환한다.") + @Test + void findCurrentTurnTest() { + BoardRepository boardRepository = new WhitePieceRepository(); + BoardService boardService = new BoardService(roomRepository, boardRepository); + + assertThatThrownBy(() -> boardService.movePiece(from, to, 0L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("상대방의 기물을 움직일 수 없습니다. 현재 턴 : BLACK"); + } + + @DisplayName("이동 위치에 기물이 존재하고, 조회한 기물 타입이 킹이면 참을 반환한다.") + @Test + void isCheckmateTest() { + BoardRepository boardRepository = new FakeBoardRepository(); + BoardService boardService = new BoardService(roomRepository, boardRepository); + + boolean isCheckmate = boardService.isCheckmate(from, 0L); + + Assertions.assertThat(isCheckmate).isTrue(); + } +} diff --git a/src/test/java/chess/service/GameServiceTest.java b/src/test/java/chess/service/GameServiceTest.java new file mode 100644 index 00000000000..14fc9a8bfdc --- /dev/null +++ b/src/test/java/chess/service/GameServiceTest.java @@ -0,0 +1,59 @@ +package chess.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import chess.domain.game.Score; +import chess.domain.game.Winner; +import chess.domain.piece.Color; +import chess.repository.BoardRepository; +import chess.repository.RoomRepository; +import chess.repository.fake.FakeBoardRepository; +import chess.repository.fake.FakeRoomRepository; +import chess.repository.fake.WhitePieceRepository; +import chess.service.dto.ChessGameResult; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameServiceTest { + + private RoomRepository roomRepository; + private BoardRepository boardRepository; + + @BeforeEach + void setUp() { + roomRepository = new FakeRoomRepository(); + boardRepository = new FakeBoardRepository(); + } + + @DisplayName("점수를 계산하여 반환한다.") + @Test + void getScoreTest() { + GameService gameService = new GameService(roomRepository, boardRepository); + + Map score = gameService.getScore(0L); + + assertThat(score.get(Color.WHITE).score()).isEqualTo(7); + } + + @DisplayName("살아있는 킹의 수가 2개일 때 점수를 이용하여 게임 결과를 생성한다.") + @Test + void generateGameResultTestWithScore() { + GameService gameService = new GameService(roomRepository, boardRepository); + + ChessGameResult chessGameResult = gameService.generateGameResult(0L); + + assertThat(chessGameResult.getWinner()).isEqualTo(Winner.DRAW); + } + + @DisplayName("살아있는 킹의 수가 1개일 때 킹의 색을 확인하고 게임 결과를 생성한다.") + @Test + void generateGameResultTestWithCheckmate() { + GameService gameService = new GameService(roomRepository, new WhitePieceRepository()); + + ChessGameResult chessGameResult = gameService.generateGameResult(0L); + + assertThat(chessGameResult.getWinner()).isEqualTo(Winner.WHITE_WIN); + } +} diff --git a/src/test/java/chess/view/mapper/ColumnMapperTest.java b/src/test/java/chess/view/mapper/ColumnMapperTest.java new file mode 100644 index 00000000000..b65381001ed --- /dev/null +++ b/src/test/java/chess/view/mapper/ColumnMapperTest.java @@ -0,0 +1,37 @@ +package chess.view.mapper; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import chess.domain.board.position.Column; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ColumnMapperTest { + + @Test + @DisplayName("문자열로 Column를 찾는다.") + void findByInputValueSuccessTest() { + assertAll( + () -> assertEquals(Column.A, ColumnMapper.findByInputValue("a")), + () -> assertEquals(Column.B, ColumnMapper.findByInputValue("b")), + () -> assertEquals(Column.C, ColumnMapper.findByInputValue("c")), + () -> assertEquals(Column.D, ColumnMapper.findByInputValue("d")), + () -> assertEquals(Column.E, ColumnMapper.findByInputValue("e")), + () -> assertEquals(Column.F, ColumnMapper.findByInputValue("f")), + () -> assertEquals(Column.G, ColumnMapper.findByInputValue("g")), + () -> assertEquals(Column.H, ColumnMapper.findByInputValue("h")) + ); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "zxc", "123"}) + @DisplayName("이름으로 Column를 찾는데 실패한다.") + void findByInputValueFailTest(String value) { + assertThatThrownBy(() -> ColumnMapper.findByInputValue(value)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/chess/view/mapper/RowMapperTest.java b/src/test/java/chess/view/mapper/RowMapperTest.java new file mode 100644 index 00000000000..7ea5314d669 --- /dev/null +++ b/src/test/java/chess/view/mapper/RowMapperTest.java @@ -0,0 +1,37 @@ +package chess.view.mapper; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import chess.domain.board.position.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class RowMapperTest { + + @Test + @DisplayName("이름으로 Row를 찾는다.") + void findByInputValueSuccessTest() { + assertAll( + () -> assertEquals(Row.ONE, RowMapper.findByInputValue("1")), + () -> assertEquals(Row.TWO, RowMapper.findByInputValue("2")), + () -> assertEquals(Row.THREE, RowMapper.findByInputValue("3")), + () -> assertEquals(Row.FOUR, RowMapper.findByInputValue("4")), + () -> assertEquals(Row.FIVE, RowMapper.findByInputValue("5")), + () -> assertEquals(Row.SIX, RowMapper.findByInputValue("6")), + () -> assertEquals(Row.SEVEN, RowMapper.findByInputValue("7")), + () -> assertEquals(Row.EIGHT, RowMapper.findByInputValue("8")) + ); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "zxc", "123"}) + @DisplayName("이름으로 Row를 찾는데 실패한다.") + void findByInputValueFailTest(String value) { + assertThatThrownBy(() -> RowMapper.findByInputValue(value)) + .isInstanceOf(IllegalArgumentException.class); + } +}