diff --git a/README.md b/README.md index 8102f91c87..27be1288e5 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,35 @@ ## 우아한테크코스 코드리뷰 - [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) + +## 실행 흐름 +1. 보드에 있는 기물을 선택하면, 해당 기물이 이동할 수 있는 위치들을 알려준다. +2. 사용자가 위치를 입력하면 해당 기물을 지정 위치로 이동시킨다. +3. 이동 위치에 기물이 존재하는 경우, 해당 기물을 잡아서 점수를 얻을 수 있다. + +## 기물 이동 구현하기 + +- [x] 비숍 + - [x] 대각선으로 원하는만큼 이동 가능 + - [x] 장애물을 통과할 수 없음 +- [x] 왕 + - [x] 상, 하, 좌, 우, 대각선 한 칸씩 이동 가능 +- [x] 나이트 + - [x] 상, 하, 좌, 우 방향으로 앞으로 한 칸, 대각선으로 한 칸 이동 가능 + - [x] 장애물을 통과할 수 있음 +- [ ] 폰 + - [ ] 처음 이동할 때는 앞으로 두 칸 이동도 가능 (한 칸만 움직일 수도 있음) + - [ ] 상대 말을 잡을 때는 무조건 대각선으로만 잡을 수 있음 + - [ ] 뒤로는 이동할 수 없음 +- [x] 여왕 + - [x] 상, 하, 좌, 우, 대각선 원하는만큼 이동 가능 + - [x] 장애물을 통과할 수 없음 +- [x] 룩 + - [x] 상, 하, 좌, 우 원하는만큼 이동 가능 + - [x] 장애물을 통과할 수 없음 + +## 기물 배치하기 + +## 기물 잡기 + +- [ ] 기물이 이동한 위치에 다른 팀의 기물이 존재하는 경우 diff --git a/src/main/java/chess/Board.java b/src/main/java/chess/Board.java new file mode 100644 index 0000000000..64de7489aa --- /dev/null +++ b/src/main/java/chess/Board.java @@ -0,0 +1,5 @@ +package chess; + +public class Board { + +} diff --git a/src/main/java/chess/ChessPositions.java b/src/main/java/chess/ChessPositions.java new file mode 100644 index 0000000000..4eb67e43d2 --- /dev/null +++ b/src/main/java/chess/ChessPositions.java @@ -0,0 +1,14 @@ +package chess; + +import chess.piece.ChessPiece; + +import java.util.List; +import java.util.Map; + +public class ChessPositions { + private final Map positions; + + public ChessPositions(Map positions) { + this.positions = positions; + } +} diff --git a/src/main/java/chess/Column.java b/src/main/java/chess/Column.java index b64b4dc77a..ed0ffc07fc 100644 --- a/src/main/java/chess/Column.java +++ b/src/main/java/chess/Column.java @@ -1,6 +1,7 @@ package chess; -public enum Column { +public enum +Column { A, B, diff --git a/src/main/java/chess/piece/Bishop.java b/src/main/java/chess/piece/Bishop.java index b14ab70f98..472f23963b 100644 --- a/src/main/java/chess/piece/Bishop.java +++ b/src/main/java/chess/piece/Bishop.java @@ -1,5 +1,69 @@ package chess.piece; -public class Bishop { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Bishop implements ChessPiece { + private final Color color; + private final List movements = List.of( + Movement.LEFT_UP, + Movement.RIGHT_UP, + Movement.LEFT_DOWN, + Movement.RIGHT_DOWN + ); + + public Bishop(Color color) { + this.color = color; + } + + @Override + public Position move(Position from, Position to, Map positions) { + if (!isValidPath(from, to, positions)) { + throw new IllegalArgumentException("경로가 유효하지 않습니다."); + } + return to; + } + + private boolean isValidPath(Position from, Position to, Map positions) { + List availableDestinations = getAvailableDestinations(from, positions); + return availableDestinations.contains(to); + } + + public List getAvailableDestinations(Position startPosition, Map positions) { + List destinations = new ArrayList<>(); + + for (Movement movement : movements) { + Position currentPosition = startPosition; + while (currentPosition.canMove(movement)) { + currentPosition = currentPosition.move(movement); + if (!canMove(currentPosition, positions)) { + break; + } + destinations.add(currentPosition); + if (positions.containsKey(currentPosition)) { + break; + } + } + } + + return destinations; + } + + private boolean canMove(Position targetPosition, Map positions) { + return !positions.containsKey(targetPosition) || canCatch(targetPosition, positions); + } + + private boolean canCatch(Position targetPosition, Map positions) { + return positions.containsKey(targetPosition) && positions.get(targetPosition).getColor() != color; + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/main/java/chess/piece/ChessPiece.java b/src/main/java/chess/piece/ChessPiece.java new file mode 100644 index 0000000000..d6e9ab0d98 --- /dev/null +++ b/src/main/java/chess/piece/ChessPiece.java @@ -0,0 +1,13 @@ +package chess.piece; + +import chess.Color; +import chess.Position; + +import java.util.List; +import java.util.Map; + +public interface ChessPiece { + List getAvailableDestinations(Position startPosition, Map positions); + Color getColor(); + Position move(Position from, Position to, Map positions); +} diff --git a/src/main/java/chess/piece/King.java b/src/main/java/chess/piece/King.java index d64210cad1..4ef1736703 100644 --- a/src/main/java/chess/piece/King.java +++ b/src/main/java/chess/piece/King.java @@ -1,5 +1,57 @@ package chess.piece; -public class King { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.List; +import java.util.Map; + +public class King implements ChessPiece { + private final Color color; + private final List movements = List.of( + Movement.UP, + Movement.DOWN, + Movement.LEFT, + Movement.RIGHT, + Movement.LEFT_UP, + Movement.RIGHT_UP, + Movement.LEFT_DOWN, + Movement.RIGHT_DOWN + ); + + public King(Color color) { + this.color = color; + } + + @Override + public Position move(Position from, Position to, Map positions) { + if (!isValidPath(from, to, positions)) { + throw new IllegalArgumentException("경로가 유효하지 않습니다."); + } + return to; + } + + private boolean isValidPath(Position from, Position to, Map positions) { + List availableDestinations = getAvailableDestinations(from, positions); + return availableDestinations.contains(to); + } + + @Override + public List getAvailableDestinations(Position startPosition, Map positions) { + return movements.stream() + .filter(startPosition::canMove) + .map(startPosition::move) + .filter(position -> canMove(position, positions)) + .toList(); + } + + private boolean canMove(Position targetPosition, Map positions) { + return !positions.containsKey(targetPosition) || positions.get(targetPosition).getColor() != color; + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/main/java/chess/piece/Knight.java b/src/main/java/chess/piece/Knight.java index 2ee7c47a3b..3a340387b0 100644 --- a/src/main/java/chess/piece/Knight.java +++ b/src/main/java/chess/piece/Knight.java @@ -1,5 +1,60 @@ package chess.piece; -public class Knight { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Knight implements ChessPiece { + private final Color color; + private final List movements = List.of( + Movement.UP_UP_LEFT, + Movement.UP_UP_RIGHT, + Movement.DOWN_DOWN_LEFT, + Movement.DOWN_DOWN_RIGHT, + Movement.LEFT_LEFT_UP, + Movement.LEFT_LEFT_DOWN, + Movement.RIGHT_RIGHT_UP, + Movement.RIGHT_RIGHT_DOWN + ); + + public Knight(Color color) { + this.color = color; + } + + @Override + public Position move(Position from, Position to, Map positions) { + if (!isValidPath(from, to, positions)) { + throw new IllegalArgumentException("경로가 유효하지 않습니다."); + } + return to; + } + + private boolean isValidPath(Position from, Position to, Map positions) { + List availableDestinations = getAvailableDestinations(from, positions); + return availableDestinations.contains(to); + } + + @Override + public List getAvailableDestinations(Position startPosition, Map positions) { + List destinations = new ArrayList<>(); + for (Movement movement : movements) { + if (startPosition.canMove(movement) && canMove(startPosition.move(movement), positions)) { + destinations.add(startPosition.move(movement)); + } + } + return destinations; + } + + private boolean canMove(Position targetPosition, Map positions) { + return !positions.containsKey(targetPosition) || positions.get(targetPosition).getColor() != color; + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/main/java/chess/piece/Pawn.java b/src/main/java/chess/piece/Pawn.java index c8b6cafa51..e96b3a0193 100644 --- a/src/main/java/chess/piece/Pawn.java +++ b/src/main/java/chess/piece/Pawn.java @@ -1,5 +1,86 @@ package chess.piece; -public class Pawn { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class Pawn implements ChessPiece { + private final Color color; + private boolean isFirstMove; + + public Pawn(Color color) { + this.color = color; + isFirstMove = true; + } + + @Override + public Position move(Position from, Position to, Map positions) { + isFirstMove = false; + return null; + } + + @Override + public List getAvailableDestinations(Position startPosition, Map positions) { + List destinations = new ArrayList<>(); + for (Movement movement : getMovements()) { + if (canMove(startPosition, movement, positions)) { + destinations.add(startPosition.move(movement)); + } + } + return destinations; + } + + private List getMovements() { + if (color.isBlack()) { + return getBlackMovements(); + } + return getWhiteMovements(); + } + + private List getBlackMovements() { + List result = new ArrayList<>(List.of( + Movement.DOWN, + Movement.LEFT_DOWN, + Movement.RIGHT_DOWN + )); + if (isFirstMove) { + result.add(Movement.DOWN_DOWN); + } + return Collections.unmodifiableList(result); + } + + private List getWhiteMovements() { + List result = new ArrayList<>(List.of( + Movement.UP, + Movement.LEFT_UP, + Movement.RIGHT_UP + )); + if (isFirstMove) { + result.add(Movement.UP_UP); + } + return Collections.unmodifiableList(result); + } + + private boolean canMove(Position startPosition, Movement movement, Map positions) { + if (!startPosition.canMove(movement)) { + return false; + } + Position targetPosition = startPosition.move(movement); + if (movement.isDiagonal()) { + // 대각선 방향인 경우 - 상대 기물이 존재하는지 확인 + return positions.containsKey(targetPosition) && positions.get(targetPosition).getColor() != color; + } + // 빈 칸 확인 + return !positions.containsKey(targetPosition); + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/main/java/chess/piece/Queen.java b/src/main/java/chess/piece/Queen.java index 9b547261c4..6d8bfb35e4 100644 --- a/src/main/java/chess/piece/Queen.java +++ b/src/main/java/chess/piece/Queen.java @@ -1,5 +1,74 @@ package chess.piece; -public class Queen { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Queen implements ChessPiece { + private final Color color; + private final List movements = List.of( + Movement.UP, + Movement.DOWN, + Movement.LEFT, + Movement.RIGHT, + Movement.LEFT_UP, + Movement.RIGHT_UP, + Movement.LEFT_DOWN, + Movement.RIGHT_DOWN + ); + + public Queen(Color color) { + this.color = color; + } + + @Override + public Position move(Position from, Position to, Map positions) { + if (!isValidPath(from, to, positions)) { + throw new IllegalArgumentException("경로가 유효하지 않습니다."); + } + return to; + } + + private boolean isValidPath(Position from, Position to, Map positions) { + List availableDestinations = getAvailableDestinations(from, positions); + return availableDestinations.contains(to); + } + + @Override + public List getAvailableDestinations(Position startPosition, Map positions) { + List destinations = new ArrayList<>(); + + for (Movement movement : movements) { + Position currentPosition = startPosition; + while (currentPosition.canMove(movement)) { + currentPosition = currentPosition.move(movement); + if (!canMove(currentPosition, positions)) { + break; + } + destinations.add(currentPosition); + if (positions.containsKey(currentPosition)) { + break; + } + } + } + + return destinations; + } + + private boolean canMove(Position targetPosition, Map positions) { + return !positions.containsKey(targetPosition) || canCatch(targetPosition, positions); + } + + private boolean canCatch(Position targetPosition, Map positions) { + return positions.containsKey(targetPosition) && positions.get(targetPosition).getColor() != color; + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/main/java/chess/piece/Rook.java b/src/main/java/chess/piece/Rook.java index 7ed4d08bf0..c84dd70313 100644 --- a/src/main/java/chess/piece/Rook.java +++ b/src/main/java/chess/piece/Rook.java @@ -1,5 +1,70 @@ package chess.piece; -public class Rook { +import chess.Color; +import chess.Movement; +import chess.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Rook implements ChessPiece { + private final Color color; + private final List movements = List.of( + Movement.UP, + Movement.DOWN, + Movement.LEFT, + Movement.RIGHT + ); + + public Rook(Color color) { + this.color = color; + } + + @Override + public Position move(Position from, Position to, Map positions) { + if (!isValidPath(from, to, positions)) { + throw new IllegalArgumentException("경로가 유효하지 않습니다."); + } + return to; + } + + private boolean isValidPath(Position from, Position to, Map positions) { + List availableDestinations = getAvailableDestinations(from, positions); + return availableDestinations.contains(to); + } + + @Override + public List getAvailableDestinations(Position startPosition, Map positions) { + List destinations = new ArrayList<>(); + + for (Movement movement : movements) { + Position currentPosition = startPosition; + while (currentPosition.canMove(movement)) { + currentPosition = currentPosition.move(movement); + if (!canMove(currentPosition, positions)) { + break; + } + destinations.add(currentPosition); + if (positions.containsKey(currentPosition)) { + break; + } + } + } + + return destinations; + } + + private boolean canMove(Position targetPosition, Map positions) { + return !positions.containsKey(targetPosition) || canCatch(targetPosition, positions); + } + + private boolean canCatch(Position targetPosition, Map positions) { + return positions.containsKey(targetPosition) && positions.get(targetPosition).getColor() != color; + } + + @Override + public Color getColor() { + return color; + } } diff --git a/src/test/java/chess/piece/BishopTest.java b/src/test/java/chess/piece/BishopTest.java new file mode 100644 index 0000000000..370943e7f5 --- /dev/null +++ b/src/test/java/chess/piece/BishopTest.java @@ -0,0 +1,108 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class BishopTest { + @DisplayName("장애물이 존재하지 않는 경우, 보드를 벗어나지 않는 영역 내에서 모든 대각선 방향으로 움직일 수 있다.") + @Test + void notExistHurdle() { + // given + Map positions = Map.of(); + Bishop bishop = new Bishop(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.FIVE, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.SEVEN, Column.G), + new Position(Row.EIGHT, Column.H), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A) + ); + + // when + List destinations = bishop.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("이동경로에 같은 팀 기물이 존재하는 경우, 해당 기물들을 마주하기 전까지의 위치로 움직일 수 있다.") + @Test + void existSameTeamHurdle() { + // given + Map positions = Map.of( + new Position(Row.SIX, Column.F), new Bishop(Color.BLACK), + new Position(Row.ONE, Column.G), new Bishop(Color.BLACK) + ); + Bishop bishop = new Bishop(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.FIVE, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A) + ); + + // when + List destinations = bishop.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("이동경로에 다른 팀 기물이 존재하는 경우, 해당 기물들의 위치로 움직일 수 있다.") + @Test + void existOtherTeamHurdle() { + // given + Map positions = Map.of( + new Position(Row.SIX, Column.F), new Bishop(Color.WHITE), + new Position(Row.ONE, Column.G), new Bishop(Color.WHITE) + ); + Bishop bishop = new Bishop(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.FIVE, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A) + ); + + // when + List destinations = bishop.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/chess/piece/KingTest.java b/src/test/java/chess/piece/KingTest.java new file mode 100644 index 0000000000..6f91f4efb7 --- /dev/null +++ b/src/test/java/chess/piece/KingTest.java @@ -0,0 +1,69 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class KingTest { + @DisplayName("장애물이 없는 경우, 8개 방향으로 한 칸씩 이동할 수 있다.") + @Test + void notExistHurdles() { + // given + Map positions = Map.of(); + King king = new King(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.FIVE, Column.D), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.THREE, Column.D), + new Position(Row.THREE, Column.E) + ); + + // when + List destinations = king.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("8개 방향 중에서 같은 팀의 기물이 없는 곳으로만 이동할 수 있다.") + @Test + void existHurdles() { + // given + Map positions = Map.of( + new Position(Row.FIVE, Column.D), new Bishop(Color.BLACK), + new Position(Row.THREE, Column.C), new King(Color.WHITE) + ); + King king = new King(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.FIVE, Column.E), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.THREE, Column.D), + new Position(Row.THREE, Column.E) + ); + + // when + List destinations = king.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/chess/piece/KnightTest.java b/src/test/java/chess/piece/KnightTest.java new file mode 100644 index 0000000000..8d233b4b4e --- /dev/null +++ b/src/test/java/chess/piece/KnightTest.java @@ -0,0 +1,69 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class KnightTest { + @DisplayName("나이트는 목적지를 제외한 경로 상에 장애물 존재 여부와 상관 없이 이동할 수 있다.") + @Test + void noMatterHurdle() { + Map positions = Map.of( + new Position(Row.FIVE, Column.D), new King(Color.WHITE), + new Position(Row.FOUR, Column.C), new Bishop(Color.BLACK) + ); + Knight knight = new Knight(Color.BLACK); + List expected = List.of( + new Position(Row.SIX, Column.C), + new Position(Row.FIVE, Column.B), + new Position(Row.SIX, Column.E), + new Position(Row.FIVE, Column.F), + new Position(Row.THREE, Column.B), + new Position(Row.TWO, Column.C), + new Position(Row.TWO, Column.E), + new Position(Row.THREE, Column.F) + ); + + // when + List destinations = knight.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("목적지에 같은 팀의 기물이 있는 경우, 목적지 후보에서 제외된다.") + @Test + void existSameTeamAtDestination() { + Map positions = Map.of( + new Position(Row.SIX, Column.C), new King(Color.BLACK), + new Position(Row.THREE, Column.F), new Bishop(Color.BLACK) + ); + Knight knight = new Knight(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.B), + new Position(Row.SIX, Column.E), + new Position(Row.FIVE, Column.F), + new Position(Row.THREE, Column.B), + new Position(Row.TWO, Column.C), + new Position(Row.TWO, Column.E) + ); + + // when + List destinations = knight.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/chess/piece/PawnTest.java b/src/test/java/chess/piece/PawnTest.java new file mode 100644 index 0000000000..e2d2c1a68f --- /dev/null +++ b/src/test/java/chess/piece/PawnTest.java @@ -0,0 +1,260 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class PawnTest { + @DisplayName("BLACK - 처음 움직이는 경우 두 칸까지 전진할 수 있다.") + @Test + void notExistHurdle() { + // given + Position pawnPosition = new Position(Row.SEVEN, Column.D); + Pawn pawn = new Pawn(Color.BLACK); + Map positions = Map.of( + pawnPosition, pawn + ); + List expected = List.of( + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("BLACK - 전진 방향의 대각선에 상대편 기물이 있는 경우 해당 위치로 이동할 수 있다.") + @Test + void existEnemyAtDiagonal() { + // given + Position pawnPosition = new Position(Row.SEVEN, Column.D); + Pawn pawn = new Pawn(Color.BLACK); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.SIX, Column.C), new Knight(Color.WHITE) + ); + List expected = List.of( + new Position(Row.SIX, Column.C), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("BLACK - 전진 방향의 대각선에 같은편 기물이 있는 경우 해당 위치로 이동할 수 없다.") + @Test + void existSameTeamHurdle() { + // given + Position pawnPosition = new Position(Row.SEVEN, Column.D); + Pawn pawn = new Pawn(Color.BLACK); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.SIX, Column.C), new Knight(Color.BLACK) + ); + List expected = List.of( + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("BLACK - 이전에 움직인 이력이 있는 경우, 앞으로 한 칸만 전진할 수 있따.") + @Test + void onlyOneForwardMoved_BLACK() { + // given + Pawn pawn = new Pawn(Color.BLACK); + Position previous = new Position(Row.SEVEN, Column.D); + Position current = new Position(Row.SIX, Column.D); + pawn.move(previous, current, Map.of()); + + List expect = List.of( + new Position(Row.FIVE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations(current, Map.of()); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expect); + } + + @DisplayName("WHITE - 처음 움직이는 경우 두 칸까지 전진할 수 있다.") + @Test + void notExistHurdleWhite() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn + ); + List expected = List.of( + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("WHITE - 전진 방향의 대각선에 상대편 기물이 있는 경우 해당 위치로 이동할 수 있다.") + @Test + void existEnemyAtDiagonal_WHITE() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.THREE, Column.E), new Knight(Color.BLACK) + ); + List expected = List.of( + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.D), + new Position(Row.THREE, Column.E) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("WHITE - 전진 방향의 대각선에 같은편 기물이 있는 경우 해당 위치로 이동할 수 없다.") + @Test + void existSameTeamHurdle_WHITE() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.THREE, Column.E), new Knight(Color.WHITE) + ); + List expected = List.of( + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations( + pawnPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("WHITE - 이전에 움직인 이력이 있는 경우, 앞으로 한 칸만 전진할 수 있따.") + @Test + void onlyOneForwardMoved_WHITE() { + // given + Pawn pawn = new Pawn(Color.WHITE); + Position previous = new Position(Row.TWO, Column.D); + Position current = new Position(Row.THREE, Column.D); + pawn.move(previous, current, Map.of()); + + List expect = List.of( + new Position(Row.FOUR, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations(current, Map.of()); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expect); + } + + @DisplayName("처음 움직일 때 두 칸 앞에 적 기물이 존재하는 경우 두 칸 이동이 불가능하다.") + @Test + void firstMoveExistEnemy() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.FOUR, Column.D), new Knight(Color.BLACK) + ); + List expected = List.of( + new Position(Row.THREE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations(pawnPosition, positions); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("처음 움직일 때 두 칸 앞에 같은 팀 기물이 존재하는 경우 두 칸 이동이 불가능하다.") + @Test + void firstMoveExistSameTeam() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.FOUR, Column.D), new Knight(Color.WHITE) + ); + List expected = List.of( + new Position(Row.THREE, Column.D) + ); + + // when + List destinations = pawn.getAvailableDestinations(pawnPosition, positions); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("이동방향 바로 앞에 상대 기물이 존재하는 경우 움직일 수 없다.") + @Test + void firstMoveExistEnemyFront() { + // given + Position pawnPosition = new Position(Row.TWO, Column.D); + Pawn pawn = new Pawn(Color.WHITE); + Map positions = Map.of( + pawnPosition, pawn, + new Position(Row.THREE, Column.D), new Knight(Color.BLACK) + ); + List expected = List.of(); + + // when + List destinations = pawn.getAvailableDestinations(pawnPosition, positions); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/chess/piece/QueenTest.java b/src/test/java/chess/piece/QueenTest.java new file mode 100644 index 0000000000..1a8724b7ea --- /dev/null +++ b/src/test/java/chess/piece/QueenTest.java @@ -0,0 +1,146 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class QueenTest { + @DisplayName("장애물이 존재하지 않는 경우, 8개 방향으로 끝까지 이동할 수 있다.") + @Test + void notExistHurdles() { + // given + Map positions = Map.of(); + Queen queen = new Queen(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.FIVE, Column.E), + new Position(Row.SIX, Column.F), + new Position(Row.SEVEN, Column.G), + new Position(Row.EIGHT, Column.H), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A), + new Position(Row.EIGHT, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.ONE, Column.D), + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + ); + + // when + List destinations = queen.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("상대편 기물이 존재하는 경우, 8개 방향으로 상대편 기물 위치까지 이동할 수 있다.") + @Test + void moveUntilEnemyPositions() { + // given + Map positions = Map.of( + new Position(Row.FIVE, Column.E), new Bishop(Color.WHITE), + new Position(Row.TWO, Column.D), new Knight(Color.WHITE) + ); + Queen queen = new Queen(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.FIVE, Column.E), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A), + new Position(Row.EIGHT, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + ); + + // when + List destinations = queen.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("같은 팀 기물이 존재하는 경우, 해당 위치 이전까지만 이동할 수 있다.") + @Test + void existSameTeam() { + // given + Map positions = Map.of( + new Position(Row.FIVE, Column.E), new Bishop(Color.BLACK), + new Position(Row.TWO, Column.D), new Knight(Color.BLACK) + ); + Queen queen = new Queen(Color.BLACK); + List expected = List.of( + new Position(Row.FIVE, Column.C), + new Position(Row.SIX, Column.B), + new Position(Row.SEVEN, Column.A), + new Position(Row.THREE, Column.E), + new Position(Row.TWO, Column.F), + new Position(Row.ONE, Column.G), + new Position(Row.THREE, Column.C), + new Position(Row.TWO, Column.B), + new Position(Row.ONE, Column.A), + new Position(Row.EIGHT, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + ); + + // when + List destinations = queen.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +} diff --git a/src/test/java/chess/piece/RookTest.java b/src/test/java/chess/piece/RookTest.java new file mode 100644 index 0000000000..2d56cf38f0 --- /dev/null +++ b/src/test/java/chess/piece/RookTest.java @@ -0,0 +1,116 @@ +package chess.piece; + +import chess.Color; +import chess.Column; +import chess.Position; +import chess.Row; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class RookTest { + @DisplayName("룩 - 장애물이 존재하지 않을 경우 이동 경로 끝까지 이동할 수 있다.") + @Test + void notExistHurdles() { + // given + Position rookPosition = new Position(Row.FOUR, Column.D); + Rook rook = new Rook(Color.BLACK); + Map positions = Map.of( + rookPosition, rook + ); + List expected = List.of( + new Position(Row.EIGHT, Column.D), + new Position(Row.SEVEN, Column.D), + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.TWO, Column.D), + new Position(Row.ONE, Column.D), + + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + ); + + // when + List destinations = rook.getAvailableDestinations( + rookPosition, positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("이동경로에 같은 팀 기물이 존재하는 경우, 해당 기물들을 마주하기 전까지의 위치로 움직일 수 있다.") + @Test + void existSameTeamHurdle() { + // given + Position rookPosition = new Position(Row.FOUR, Column.D); + Rook rook = new Rook(Color.BLACK); + Map positions = Map.of( + rookPosition, rook, + new Position(Row.SIX, Column.D), new Bishop(Color.BLACK), + new Position(Row.FOUR, Column.H), new Bishop(Color.BLACK), + new Position(Row.THREE, Column.D), new Knight(Color.BLACK) + ); + List expected = List.of( + new Position(Row.FIVE, Column.D), + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G) + ); + + // when + List destinations = rook.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("이동경로에 다른 팀 기물이 존재하는 경우, 해당 기물들의 위치로 움직일 수 있다.") + @Test + void existOtherTeamHurdle() { + // given + Position rookPosition = new Position(Row.FOUR, Column.D); + Rook rook = new Rook(Color.BLACK); + Map positions = Map.of( + rookPosition, rook, + new Position(Row.SIX, Column.D), new Bishop(Color.WHITE), + new Position(Row.FOUR, Column.H), new Bishop(Color.WHITE), + new Position(Row.THREE, Column.D), new Knight(Color.WHITE) + ); + List expected = List.of( + new Position(Row.SIX, Column.D), + new Position(Row.FIVE, Column.D), + new Position(Row.THREE, Column.D), + new Position(Row.FOUR, Column.A), + new Position(Row.FOUR, Column.B), + new Position(Row.FOUR, Column.C), + new Position(Row.FOUR, Column.E), + new Position(Row.FOUR, Column.F), + new Position(Row.FOUR, Column.G), + new Position(Row.FOUR, Column.H) + ); + + // when + List destinations = rook.getAvailableDestinations( + new Position(Row.FOUR, Column.D), positions + ); + + // then + assertThat(destinations).containsExactlyInAnyOrderElementsOf(expected); + } +}