From 1ceab076a7762776160b6b5801992e01eaaadddd Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 16:22:44 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=ED=82=B9=EC=9D=84=20=EC=9B=80?= =?UTF-8?q?=EC=A7=81=EC=9D=BC=20=EC=88=98=20=EC=9E=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 +++- src/main/java/chess/Game.java | 17 +++ src/main/java/chess/board/Board.java | 111 ++++++++++++++++++ src/main/java/chess/{ => board}/Color.java | 2 +- src/main/java/chess/piece/Bishop.java | 17 ++- src/main/java/chess/piece/King.java | 17 ++- src/main/java/chess/piece/Knight.java | 24 +++- src/main/java/chess/piece/Pawn.java | 25 +++- src/main/java/chess/piece/Piece.java | 30 +++++ src/main/java/chess/piece/PieceType.java | 12 ++ src/main/java/chess/piece/Queen.java | 23 +++- src/main/java/chess/piece/Rook.java | 18 ++- .../java/chess/{ => position}/Column.java | 2 +- .../java/chess/{ => position}/Movement.java | 2 +- .../java/chess/{ => position}/Position.java | 2 +- src/main/java/chess/{ => position}/Row.java | 2 +- src/main/java/chess/view/BoardView.java | 25 ++++ src/main/java/chess/view/PieceNotation.java | 43 +++++++ src/test/java/chess/board/BoardTest.java | 91 ++++++++++++++ src/test/java/chess/fixture/BoardFixture.java | 27 +++++ .../PositionFixture.java} | 10 +- .../java/chess/{ => position}/ColumnTest.java | 8 +- .../chess/{ => position}/PositionTest.java | 59 +++++----- .../java/chess/{ => position}/RowTest.java | 8 +- 24 files changed, 543 insertions(+), 54 deletions(-) create mode 100644 src/main/java/chess/Game.java create mode 100644 src/main/java/chess/board/Board.java rename src/main/java/chess/{ => board}/Color.java (95%) create mode 100644 src/main/java/chess/piece/Piece.java create mode 100644 src/main/java/chess/piece/PieceType.java rename src/main/java/chess/{ => position}/Column.java (97%) rename src/main/java/chess/{ => position}/Movement.java (97%) rename src/main/java/chess/{ => position}/Position.java (99%) rename src/main/java/chess/{ => position}/Row.java (97%) create mode 100644 src/main/java/chess/view/BoardView.java create mode 100644 src/main/java/chess/view/PieceNotation.java create mode 100644 src/test/java/chess/board/BoardTest.java create mode 100644 src/test/java/chess/fixture/BoardFixture.java rename src/test/java/chess/{Fixtures.java => fixture/PositionFixture.java} (96%) rename src/test/java/chess/{ => position}/ColumnTest.java (99%) rename src/test/java/chess/{ => position}/PositionTest.java (90%) rename src/test/java/chess/{ => position}/RowTest.java (99%) diff --git a/README.md b/README.md index 8102f91c870..07cce7421cf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,24 @@ 체스 미션 저장소 -## 우아한테크코스 코드리뷰 +## 게임 설명 + +``` +RNBQKBNR 8 (rank 8) +PPPPPPPP 7 (rank 7) +........ 6 +........ 5 +........ 4 +........ 3 +pppppppp 2 +rnbqkbnr 1 (rank 1) + +abcdefgh +``` + +- 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다. +- 각 진영이 번갈아가면서 기물을 움직여야 하며 흰색(소문자)가 먼저 움직인다. +- 게임 시작: start +- 게임 종료: end +- 게임 이동: move source target - 예) move b2 b3 -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) diff --git a/src/main/java/chess/Game.java b/src/main/java/chess/Game.java new file mode 100644 index 00000000000..ef396eb018c --- /dev/null +++ b/src/main/java/chess/Game.java @@ -0,0 +1,17 @@ +package chess; + +import chess.board.Board; +import chess.view.BoardView; + +public class Game { + private final BoardView boardView = new BoardView(); + + public static void main(String[] args) { + new Game().start(); + } + + public void start() { + Board board = new Board(); + boardView.display(board); + } +} diff --git a/src/main/java/chess/board/Board.java b/src/main/java/chess/board/Board.java new file mode 100644 index 00000000000..164f88bcf9e --- /dev/null +++ b/src/main/java/chess/board/Board.java @@ -0,0 +1,111 @@ +package chess.board; + +import chess.piece.Bishop; +import chess.piece.King; +import chess.piece.Knight; +import chess.piece.Pawn; +import chess.piece.Piece; +import chess.piece.PieceType; +import chess.piece.Queen; +import chess.piece.Rook; +import chess.position.Column; +import chess.position.Position; +import chess.position.Row; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public final class Board { + + private final Map colors; + private final Map pieces; + + { + // 기본 Empty + Map colors = new HashMap<>(); + for (Row row : Row.values()) { + for (Column column : Column.values()) { + colors.put(new Position(row, column), Color.EMPTY); + } + } + this.colors = colors; + } + + public Board(Map colors, Map pieces) { + for (Entry newColorEntry : colors.entrySet()) { + Position position = newColorEntry.getKey(); + Color color = newColorEntry.getValue(); + this.colors.put(position, color); + } + this.pieces = new HashMap<>(pieces); + } + + public Board() { + // 색깔 넣기 + for (Column column : Column.values()) { + // Rank 8, 7 -> Black + colors.put(new Position(Row.EIGHT, column), Color.BLACK); + colors.put(new Position(Row.SEVEN, column), Color.BLACK); + // Rank 2, 1 -> White + colors.put(new Position(Row.TWO, column), Color.WHITE); + colors.put(new Position(Row.ONE, column), Color.WHITE); + } + // 기물 넣기 + Map pieces = new HashMap<>(); + for (Column column : Column.values()) { // 폰 + pieces.put(new Position(Row.SEVEN, column), Pawn.black()); + pieces.put(new Position(Row.TWO, column), Pawn.white()); + } + for (Row row : List.of(Row.ONE, Row.EIGHT)) { // 폰을 제외한 기물 + pieces.put(new Position(row, Column.A), Rook.create()); + pieces.put(new Position(row, Column.B), Knight.create()); + pieces.put(new Position(row, Column.C), Bishop.create()); + pieces.put(new Position(row, Column.D), Queen.create()); + pieces.put(new Position(row, Column.E), King.create()); + pieces.put(new Position(row, Column.F), Bishop.create()); + pieces.put(new Position(row, Column.G), Knight.create()); + pieces.put(new Position(row, Column.H), Rook.create()); + } + this.pieces = pieces; + } + + public void move(Position start, Position end) { + Piece piece = findPiece(start); + Color color = colorAt(start); + validateEndPosition(start, end); + piece.validateMove(start, end); + pieces.put(end, piece); + colors.put(end, color); + } + + private void validateEndPosition(Position start, Position end) { + if (colors.get(end).isEmpty()) { + return; + } + if (colors.get(end) == colors.get(start)) { + throw new IllegalArgumentException("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + } + + public Color colorAt(Position position) { + if (!colors.containsKey(position)) { + throw new IllegalStateException("[ERROR] 해당 위치의 색깔을 찾을 수 없습니다."); + } + return colors.get(position); + } + + public PieceType pieceTypeAt(Position position) { + if (!pieces.containsKey(position)) { + throw new IllegalStateException("[ERROR] 해당 위치에 기물이 없습니다."); + } + return pieces.get(position).type(); + } + + private Piece findPiece(Position position) { + if (!pieces.containsKey(position)) { + throw new IllegalArgumentException("[ERROR] 해당 위치에 기물이 없습니다."); + } + return pieces.get(position); + } +} diff --git a/src/main/java/chess/Color.java b/src/main/java/chess/board/Color.java similarity index 95% rename from src/main/java/chess/Color.java rename to src/main/java/chess/board/Color.java index 55cd020b681..43e8dfdc4e7 100644 --- a/src/main/java/chess/Color.java +++ b/src/main/java/chess/board/Color.java @@ -1,4 +1,4 @@ -package chess; +package chess.board; public enum Color { diff --git a/src/main/java/chess/piece/Bishop.java b/src/main/java/chess/piece/Bishop.java index b14ab70f981..9e06e974209 100644 --- a/src/main/java/chess/piece/Bishop.java +++ b/src/main/java/chess/piece/Bishop.java @@ -1,5 +1,20 @@ package chess.piece; -public class Bishop { +import static chess.position.Movement.LEFT_DOWN; +import static chess.position.Movement.LEFT_UP; +import static chess.position.Movement.RIGHT_DOWN; +import static chess.position.Movement.RIGHT_UP; +import chess.position.Movement; +import java.util.List; + +public final class Bishop extends Piece { + + private Bishop(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static Bishop create() { + return new Bishop(List.of(LEFT_UP, LEFT_DOWN, RIGHT_UP, RIGHT_DOWN), PieceType.BISHOP); + } } diff --git a/src/main/java/chess/piece/King.java b/src/main/java/chess/piece/King.java index d64210cad13..0e14c432bfb 100644 --- a/src/main/java/chess/piece/King.java +++ b/src/main/java/chess/piece/King.java @@ -1,5 +1,20 @@ package chess.piece; -public class King { +import static chess.position.Movement.DOWN; +import static chess.position.Movement.LEFT; +import static chess.position.Movement.RIGHT; +import static chess.position.Movement.UP; +import chess.position.Movement; +import java.util.List; + +public class King extends Piece { + + public King(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static King create() { + return new King(List.of(UP, DOWN, LEFT, RIGHT), PieceType.KING); + } } diff --git a/src/main/java/chess/piece/Knight.java b/src/main/java/chess/piece/Knight.java index 2ee7c47a3bc..cae3a457340 100644 --- a/src/main/java/chess/piece/Knight.java +++ b/src/main/java/chess/piece/Knight.java @@ -1,5 +1,27 @@ package chess.piece; -public class Knight { +import static chess.position.Movement.DOWN_DOWN_LEFT; +import static chess.position.Movement.DOWN_DOWN_RIGHT; +import static chess.position.Movement.LEFT_LEFT_DOWN; +import static chess.position.Movement.LEFT_LEFT_UP; +import static chess.position.Movement.RIGHT_RIGHT_DOWN; +import static chess.position.Movement.RIGHT_RIGHT_UP; +import static chess.position.Movement.UP_UP_LEFT; +import static chess.position.Movement.UP_UP_RIGHT; +import chess.position.Movement; +import java.util.List; + +public final class Knight extends Piece { + + public Knight(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static Knight create() { + return new Knight( + List.of(LEFT_LEFT_UP, LEFT_LEFT_DOWN, RIGHT_RIGHT_UP, RIGHT_RIGHT_DOWN, + DOWN_DOWN_LEFT, DOWN_DOWN_RIGHT, UP_UP_LEFT, UP_UP_RIGHT), + PieceType.KNIGHT); + } } diff --git a/src/main/java/chess/piece/Pawn.java b/src/main/java/chess/piece/Pawn.java index c8b6cafa51e..8f6a6a43ee9 100644 --- a/src/main/java/chess/piece/Pawn.java +++ b/src/main/java/chess/piece/Pawn.java @@ -1,5 +1,28 @@ package chess.piece; -public class Pawn { +import static chess.position.Movement.DOWN; +import static chess.position.Movement.DOWN_DOWN; +import static chess.position.Movement.LEFT_DOWN; +import static chess.position.Movement.LEFT_UP; +import static chess.position.Movement.RIGHT_DOWN; +import static chess.position.Movement.RIGHT_UP; +import static chess.position.Movement.UP; +import static chess.position.Movement.UP_UP; +import chess.position.Movement; +import java.util.List; + +public final class Pawn extends Piece { + + public Pawn(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static Pawn white() { + return new Pawn(List.of(UP_UP, UP, RIGHT_UP, LEFT_UP), PieceType.PAWN); + } + + public static Pawn black() { + return new Pawn(List.of(DOWN_DOWN, DOWN, RIGHT_DOWN, LEFT_DOWN), PieceType.PAWN); + } } diff --git a/src/main/java/chess/piece/Piece.java b/src/main/java/chess/piece/Piece.java new file mode 100644 index 00000000000..b385f734e75 --- /dev/null +++ b/src/main/java/chess/piece/Piece.java @@ -0,0 +1,30 @@ +package chess.piece; + +import chess.position.Movement; +import chess.position.Position; +import java.util.List; + +public abstract class Piece { + private final List movements; + private final PieceType pieceType; + + public Piece(List movements, PieceType pieceType) { + this.movements = movements; + this.pieceType = pieceType; + } + + public PieceType type() { + return pieceType; + } + + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + Position nextPosition = start.move(movement); + if (nextPosition.equals(end)) { // 일치하는게 있음 + return; + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } +} diff --git a/src/main/java/chess/piece/PieceType.java b/src/main/java/chess/piece/PieceType.java new file mode 100644 index 00000000000..d338c7a4a70 --- /dev/null +++ b/src/main/java/chess/piece/PieceType.java @@ -0,0 +1,12 @@ +package chess.piece; + +public enum PieceType { + + BISHOP, + KING, + KNIGHT, + PAWN, + QUEEN, + ROOk, + NONE; +} diff --git a/src/main/java/chess/piece/Queen.java b/src/main/java/chess/piece/Queen.java index 9b547261c4b..859aeac018b 100644 --- a/src/main/java/chess/piece/Queen.java +++ b/src/main/java/chess/piece/Queen.java @@ -1,5 +1,26 @@ package chess.piece; -public class Queen { +import static chess.position.Movement.DOWN; +import static chess.position.Movement.LEFT; +import static chess.position.Movement.LEFT_DOWN; +import static chess.position.Movement.LEFT_UP; +import static chess.position.Movement.RIGHT; +import static chess.position.Movement.RIGHT_DOWN; +import static chess.position.Movement.RIGHT_UP; +import static chess.position.Movement.UP; +import chess.position.Movement; +import java.util.List; + +public final class Queen extends Piece { + + public Queen(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static Queen create() { + return new Queen(List.of(LEFT_UP, LEFT_DOWN, RIGHT_UP, RIGHT_DOWN, + LEFT, RIGHT, UP, DOWN) + , PieceType.QUEEN); + } } diff --git a/src/main/java/chess/piece/Rook.java b/src/main/java/chess/piece/Rook.java index 7ed4d08bf03..69a5e0a5ed6 100644 --- a/src/main/java/chess/piece/Rook.java +++ b/src/main/java/chess/piece/Rook.java @@ -1,5 +1,21 @@ package chess.piece; -public class Rook { +import static chess.position.Movement.DOWN; +import static chess.position.Movement.LEFT; +import static chess.position.Movement.RIGHT; +import static chess.position.Movement.UP; +import chess.position.Movement; +import java.util.List; + +public final class Rook extends Piece { + + public Rook(List movements, PieceType pieceType) { + super(movements, pieceType); + } + + public static Rook create() { + return new Rook(List.of(LEFT, RIGHT, UP, DOWN), + PieceType.ROOk); + } } diff --git a/src/main/java/chess/Column.java b/src/main/java/chess/position/Column.java similarity index 97% rename from src/main/java/chess/Column.java rename to src/main/java/chess/position/Column.java index b64b4dc77a3..4099a335b6a 100644 --- a/src/main/java/chess/Column.java +++ b/src/main/java/chess/position/Column.java @@ -1,4 +1,4 @@ -package chess; +package chess.position; public enum Column { diff --git a/src/main/java/chess/Movement.java b/src/main/java/chess/position/Movement.java similarity index 97% rename from src/main/java/chess/Movement.java rename to src/main/java/chess/position/Movement.java index e57c6e91bb9..60d4815db36 100644 --- a/src/main/java/chess/Movement.java +++ b/src/main/java/chess/position/Movement.java @@ -1,4 +1,4 @@ -package chess; +package chess.position; public enum Movement { UP(0, 1), diff --git a/src/main/java/chess/Position.java b/src/main/java/chess/position/Position.java similarity index 99% rename from src/main/java/chess/Position.java rename to src/main/java/chess/position/Position.java index 3ebeb0ea185..14b12e1a74d 100644 --- a/src/main/java/chess/Position.java +++ b/src/main/java/chess/position/Position.java @@ -1,4 +1,4 @@ -package chess; +package chess.position; public record Position( Column column, diff --git a/src/main/java/chess/Row.java b/src/main/java/chess/position/Row.java similarity index 97% rename from src/main/java/chess/Row.java rename to src/main/java/chess/position/Row.java index 126ed048daa..12ff8956d5f 100644 --- a/src/main/java/chess/Row.java +++ b/src/main/java/chess/position/Row.java @@ -1,4 +1,4 @@ -package chess; +package chess.position; public enum Row { diff --git a/src/main/java/chess/view/BoardView.java b/src/main/java/chess/view/BoardView.java new file mode 100644 index 00000000000..bee286b8172 --- /dev/null +++ b/src/main/java/chess/view/BoardView.java @@ -0,0 +1,25 @@ +package chess.view; + +import chess.board.Board; +import chess.board.Color; +import chess.position.Column; +import chess.position.Position; +import chess.position.Row; + +public final class BoardView { + + public void display(Board board) { + for (Row row : Row.values()) { + for (Column column : Column.values()) { + Position position = new Position(row, column); + Color color = board.colorAt(position); + if (color == Color.EMPTY) { + System.out.print("."); + } else { + System.out.printf("%c", PieceNotation.of(color, board.pieceTypeAt(position))); + } + } + System.out.println(); + } + } +} diff --git a/src/main/java/chess/view/PieceNotation.java b/src/main/java/chess/view/PieceNotation.java new file mode 100644 index 00000000000..6ca4ed17a44 --- /dev/null +++ b/src/main/java/chess/view/PieceNotation.java @@ -0,0 +1,43 @@ +package chess.view; + +import chess.board.Color; +import chess.piece.PieceType; +import java.util.Arrays; + +public enum PieceNotation { + + BISHOP('b', PieceType.BISHOP), + KING('k', PieceType.KING), + KNIGHT('n', PieceType.KNIGHT), + PAWN('p', PieceType.PAWN), + QUEEN('q', PieceType.QUEEN), + ROOk('r', PieceType.ROOk); + + private final Character notation; + private final PieceType pieceType; + + PieceNotation(Character notation, PieceType pieceType) { + this.notation = notation; + this.pieceType = pieceType; + } + + public static Character of(Color color, PieceType targetPieceType) { + PieceNotation targetNotation = Arrays.stream(PieceNotation.values()) + .filter(pieceNotation -> pieceNotation.getPieceType() == targetPieceType) + .findAny() + .orElseThrow(() -> new IllegalStateException("[ERROR] 기물의 표기법을 찾을 수 없습니다.")); + if (color.isBlack()) { + return Character.toUpperCase(targetNotation.getNotation()); + } + // white + return Character.toLowerCase(targetNotation.getNotation()); + } + + public Character getNotation() { + return notation; + } + + public PieceType getPieceType() { + return pieceType; + } +} diff --git a/src/test/java/chess/board/BoardTest.java b/src/test/java/chess/board/BoardTest.java new file mode 100644 index 00000000000..7c9d2b3cb6e --- /dev/null +++ b/src/test/java/chess/board/BoardTest.java @@ -0,0 +1,91 @@ +package chess.board; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import chess.fixture.BoardFixture; +import chess.fixture.PositionFixture; +import chess.piece.King; +import chess.piece.Pawn; +import chess.piece.PieceType; +import chess.position.Position; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BoardTest { + + // 킹의 움직임 + @DisplayName("시작 위치와 도착 위치를 받아서 킹의 위치를 바꿀 수 있다.") + @Test + void test1() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C1; + Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); + // when + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, King.create()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("킹이 도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithTWoWhitePiece(PositionFixture.C2, PositionFixture.C3, + King.create(), Pawn.white()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("킹이 움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C4; + Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } + + @DisplayName("킹이 도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithTWoOppositePiece(PositionFixture.C2, PositionFixture.C3, + King.create(), Pawn.black()); + // when + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } +} diff --git a/src/test/java/chess/fixture/BoardFixture.java b/src/test/java/chess/fixture/BoardFixture.java new file mode 100644 index 00000000000..ad5de7c3e71 --- /dev/null +++ b/src/test/java/chess/fixture/BoardFixture.java @@ -0,0 +1,27 @@ +package chess.fixture; + +import chess.board.Board; +import chess.board.Color; +import chess.piece.Piece; +import chess.position.Position; +import java.util.Map; + +public class BoardFixture { + public static Board createBoardWithOneWhitePiece(Position position, Piece piece) { + return new Board(Map.of(position, Color.WHITE), Map.of(position, piece)); + } + + public static Board createBoardWithTWoWhitePiece(Position position1, Position position2, Piece piece1, + Piece piece2) { + return new Board(Map.of(position1, Color.WHITE, position2, Color.WHITE), + Map.of(position1, piece1, position2, piece2)); + } + + public static Board createBoardWithTWoOppositePiece(Position whitePosition, + Position blackPosition, + Piece whitePiece, + Piece blackPiece) { + return new Board(Map.of(whitePosition, Color.WHITE, blackPosition, Color.BLACK), + Map.of(whitePosition, whitePiece, blackPosition, blackPiece)); + } +} diff --git a/src/test/java/chess/Fixtures.java b/src/test/java/chess/fixture/PositionFixture.java similarity index 96% rename from src/test/java/chess/Fixtures.java rename to src/test/java/chess/fixture/PositionFixture.java index f940ab37137..426c6484896 100644 --- a/src/test/java/chess/Fixtures.java +++ b/src/test/java/chess/fixture/PositionFixture.java @@ -1,7 +1,11 @@ -package chess; +package chess.fixture; + +import chess.position.Column; +import chess.position.Position; +import chess.position.Row; @SuppressWarnings("unused") -public final class Fixtures { +public final class PositionFixture { public static final Position A1 = new Position(Column.A, Row.ONE); public static final Position A2 = new Position(Column.A, Row.TWO); @@ -75,6 +79,6 @@ public final class Fixtures { public static final Position H7 = new Position(Column.H, Row.SEVEN); public static final Position H8 = new Position(Column.H, Row.EIGHT); - private Fixtures() { + private PositionFixture() { } } diff --git a/src/test/java/chess/ColumnTest.java b/src/test/java/chess/position/ColumnTest.java similarity index 99% rename from src/test/java/chess/ColumnTest.java rename to src/test/java/chess/position/ColumnTest.java index e43523240f7..5c386b7dc05 100644 --- a/src/test/java/chess/ColumnTest.java +++ b/src/test/java/chess/position/ColumnTest.java @@ -1,11 +1,11 @@ -package chess; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +package chess.position; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + @DisplayName("열") class ColumnTest { diff --git a/src/test/java/chess/PositionTest.java b/src/test/java/chess/position/PositionTest.java similarity index 90% rename from src/test/java/chess/PositionTest.java rename to src/test/java/chess/position/PositionTest.java index 3ad7cc64084..e07222ab682 100644 --- a/src/test/java/chess/PositionTest.java +++ b/src/test/java/chess/position/PositionTest.java @@ -1,39 +1,38 @@ -package chess; +package chess.position; + +import static chess.fixture.PositionFixture.A1; +import static chess.fixture.PositionFixture.A2; +import static chess.fixture.PositionFixture.A3; +import static chess.fixture.PositionFixture.A6; +import static chess.fixture.PositionFixture.A7; +import static chess.fixture.PositionFixture.A8; +import static chess.fixture.PositionFixture.B1; +import static chess.fixture.PositionFixture.B2; +import static chess.fixture.PositionFixture.B3; +import static chess.fixture.PositionFixture.B7; +import static chess.fixture.PositionFixture.B8; +import static chess.fixture.PositionFixture.C1; +import static chess.fixture.PositionFixture.F1; +import static chess.fixture.PositionFixture.F8; +import static chess.fixture.PositionFixture.G1; +import static chess.fixture.PositionFixture.G2; +import static chess.fixture.PositionFixture.G6; +import static chess.fixture.PositionFixture.G7; +import static chess.fixture.PositionFixture.G8; +import static chess.fixture.PositionFixture.H1; +import static chess.fixture.PositionFixture.H2; +import static chess.fixture.PositionFixture.H7; +import static chess.fixture.PositionFixture.H8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.stream.Stream; - -import static chess.Fixtures.A1; -import static chess.Fixtures.A2; -import static chess.Fixtures.A3; -import static chess.Fixtures.A6; -import static chess.Fixtures.A7; -import static chess.Fixtures.A8; -import static chess.Fixtures.B1; -import static chess.Fixtures.B2; -import static chess.Fixtures.B3; -import static chess.Fixtures.B7; -import static chess.Fixtures.B8; -import static chess.Fixtures.C1; -import static chess.Fixtures.F1; -import static chess.Fixtures.F8; -import static chess.Fixtures.G1; -import static chess.Fixtures.G2; -import static chess.Fixtures.G6; -import static chess.Fixtures.G7; -import static chess.Fixtures.G8; -import static chess.Fixtures.H1; -import static chess.Fixtures.H2; -import static chess.Fixtures.H7; -import static chess.Fixtures.H8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - @DisplayName("위치") class PositionTest { diff --git a/src/test/java/chess/RowTest.java b/src/test/java/chess/position/RowTest.java similarity index 99% rename from src/test/java/chess/RowTest.java rename to src/test/java/chess/position/RowTest.java index fcb65485410..d222f1affc4 100644 --- a/src/test/java/chess/RowTest.java +++ b/src/test/java/chess/position/RowTest.java @@ -1,11 +1,11 @@ -package chess; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +package chess.position; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + @DisplayName("행") class RowTest { From 217d38a242846a0be0d3ca8d63ea1f58c983088d Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 16:34:08 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9B=80=EC=A7=81=EC=9D=BC=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/piece/Piece.java | 3 + src/test/java/chess/board/BoardTest.java | 217 ++++++++++++++++------- 2 files changed, 152 insertions(+), 68 deletions(-) diff --git a/src/main/java/chess/piece/Piece.java b/src/main/java/chess/piece/Piece.java index b385f734e75..23815c94dc1 100644 --- a/src/main/java/chess/piece/Piece.java +++ b/src/main/java/chess/piece/Piece.java @@ -19,6 +19,9 @@ public PieceType type() { public void validateMove(Position start, Position end) { for (Movement movement : movements) { + if (!start.canMove(movement)) { + continue; + } Position nextPosition = start.move(movement); if (nextPosition.equals(end)) { // 일치하는게 있음 return; diff --git a/src/test/java/chess/board/BoardTest.java b/src/test/java/chess/board/BoardTest.java index 7c9d2b3cb6e..bae97407e2a 100644 --- a/src/test/java/chess/board/BoardTest.java +++ b/src/test/java/chess/board/BoardTest.java @@ -6,86 +6,167 @@ import chess.fixture.BoardFixture; import chess.fixture.PositionFixture; import chess.piece.King; +import chess.piece.Knight; import chess.piece.Pawn; +import chess.piece.Piece; import chess.piece.PieceType; import chess.position.Position; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class BoardTest { - // 킹의 움직임 - @DisplayName("시작 위치와 도착 위치를 받아서 킹의 위치를 바꿀 수 있다.") - @Test - void test1() { - // given - Position start = PositionFixture.C2; - Position end = PositionFixture.C1; - Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); - // when - board.move(start, end); - // then - assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); - assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); - } + @Nested + @DisplayName("킹의 움직임") + class KingTest { - @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") - @Test - void test11() { - // given - Position start = PositionFixture.C2; - Position end = PositionFixture.C3; - Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, King.create()); - // when - // then - assertThatThrownBy(() -> board.move(start, end)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); - } + // 킹의 움직임 + @DisplayName("킹은 C2에서 C1으로 이동할 수 있다.") + @Test + void test1() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C1; + Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); + // when + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } - @DisplayName("킹이 도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") - @Test - void test3() { - // given - Position start = PositionFixture.C2; - Position end = PositionFixture.C3; - Board board = BoardFixture.createBoardWithTWoWhitePiece(PositionFixture.C2, PositionFixture.C3, - King.create(), Pawn.white()); - // when - // then - assertThatThrownBy(() -> board.move(start, end)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); - } + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, King.create()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("킹이 도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithTWoWhitePiece(PositionFixture.C2, PositionFixture.C3, + King.create(), Pawn.white()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("킹이 움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C4; + Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } - @DisplayName("킹이 움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") - @Test - void test2() { - // given - Position start = PositionFixture.C2; - Position end = PositionFixture.C4; - Board board = BoardFixture.createBoardWithOneWhitePiece(start, King.create()); - // when - // then - assertThatThrownBy(() -> board.move(start, end)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + @DisplayName("킹이 도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Position start = PositionFixture.C2; + Position end = PositionFixture.C3; + Board board = BoardFixture.createBoardWithTWoOppositePiece(PositionFixture.C2, PositionFixture.C3, + King.create(), Pawn.black()); + // when + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } } - @DisplayName("킹이 도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") - @Test - void test4() { - // given - Position start = PositionFixture.C2; - Position end = PositionFixture.C3; - Board board = BoardFixture.createBoardWithTWoOppositePiece(PositionFixture.C2, PositionFixture.C3, - King.create(), Pawn.black()); - // when - assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); - assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); - board.move(start, end); - // then - assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KING); - assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + @Nested + @DisplayName("나이트의 움직임") + class KnightTest { + private final Position start = PositionFixture.C2; + private final Position end = PositionFixture.E3; + private final Piece knight = Knight.create(); + + // 나이트의 움직임 + @DisplayName("나이트는 C2에서 E3로 움직일 수 있다.") + @Test + void test1() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, knight); + // when + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KNIGHT); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, knight); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Board board = BoardFixture.createBoardWithTWoWhitePiece(start, end, + knight, knight); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, knight); + // when + // then + assertThatThrownBy(() -> board.move(start, PositionFixture.A2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } + + @DisplayName("도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Board board = BoardFixture.createBoardWithTWoOppositePiece(start, end, + knight, Pawn.black()); + // when + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.KNIGHT); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } } } From 54f88f254aa5f4c49418295215e33ad1cc6d4616 Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 17:19:35 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=A3=A9,=20=EB=B9=84=EC=88=8D,=20?= =?UTF-8?q?=ED=80=B8=EC=9D=84=20=EC=9B=80=EC=A7=81=EC=9D=BC=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/board/Board.java | 13 + src/main/java/chess/piece/Bishop.java | 17 ++ src/main/java/chess/piece/King.java | 16 + src/main/java/chess/piece/Knight.java | 16 + src/main/java/chess/piece/Pawn.java | 17 ++ src/main/java/chess/piece/Piece.java | 29 +- src/main/java/chess/piece/PieceType.java | 23 +- src/main/java/chess/piece/Queen.java | 16 + src/main/java/chess/piece/Rook.java | 18 +- src/main/java/chess/view/PieceNotation.java | 2 +- src/test/java/chess/board/BoardTest.java | 289 +++++++++++++++++- src/test/java/chess/fixture/BoardFixture.java | 2 +- 12 files changed, 433 insertions(+), 25 deletions(-) diff --git a/src/main/java/chess/board/Board.java b/src/main/java/chess/board/Board.java index 164f88bcf9e..3e933c84f07 100644 --- a/src/main/java/chess/board/Board.java +++ b/src/main/java/chess/board/Board.java @@ -9,6 +9,7 @@ import chess.piece.Queen; import chess.piece.Rook; import chess.position.Column; +import chess.position.Movement; import chess.position.Position; import chess.position.Row; import java.util.HashMap; @@ -75,10 +76,22 @@ public void move(Position start, Position end) { Color color = colorAt(start); validateEndPosition(start, end); piece.validateMove(start, end); + if (piece.type().canBeBlocked()) { + validateRouteNotBlocked(piece.getValidateMovement(start, end), start, end); + } pieces.put(end, piece); colors.put(end, color); } + private void validateRouteNotBlocked(Movement movement, Position start, Position end) { + Position nextPosition = start; + while (!(nextPosition = nextPosition.move(movement)).equals(end)) { // end 끝까지 가면 끝 + if (pieces.containsKey(nextPosition)) { // 이 위치에 어떤 기물이 있는 경우 + throw new IllegalArgumentException("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } + } + private void validateEndPosition(Position start, Position end) { if (colors.get(end).isEmpty()) { return; diff --git a/src/main/java/chess/piece/Bishop.java b/src/main/java/chess/piece/Bishop.java index 9e06e974209..e01e3f3dc40 100644 --- a/src/main/java/chess/piece/Bishop.java +++ b/src/main/java/chess/piece/Bishop.java @@ -6,6 +6,7 @@ import static chess.position.Movement.RIGHT_UP; import chess.position.Movement; +import chess.position.Position; import java.util.List; public final class Bishop extends Piece { @@ -17,4 +18,20 @@ private Bishop(List movements, PieceType pieceType) { public static Bishop create() { return new Bishop(List.of(LEFT_UP, LEFT_DOWN, RIGHT_UP, RIGHT_DOWN), PieceType.BISHOP); } + + @Override + + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + Position nextPosition = start; + while (nextPosition.canMove(movement)) { // 보드 끝까지 가면 끝 + nextPosition = nextPosition.move(movement); // 1칸 움직임 + if (nextPosition.equals(end)) { // 가고자 하는 위치랑 일치함 + return; + } + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } } diff --git a/src/main/java/chess/piece/King.java b/src/main/java/chess/piece/King.java index 0e14c432bfb..c7cbd97ae57 100644 --- a/src/main/java/chess/piece/King.java +++ b/src/main/java/chess/piece/King.java @@ -6,6 +6,7 @@ import static chess.position.Movement.UP; import chess.position.Movement; +import chess.position.Position; import java.util.List; public class King extends Piece { @@ -17,4 +18,19 @@ public King(List movements, PieceType pieceType) { public static King create() { return new King(List.of(UP, DOWN, LEFT, RIGHT), PieceType.KING); } + + @Override + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + if (!start.canMove(movement)) { + continue; + } + Position nextPosition = start.move(movement); + if (nextPosition.equals(end)) { // 일치하는게 있음 + return; + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } } diff --git a/src/main/java/chess/piece/Knight.java b/src/main/java/chess/piece/Knight.java index cae3a457340..07f101a48fa 100644 --- a/src/main/java/chess/piece/Knight.java +++ b/src/main/java/chess/piece/Knight.java @@ -10,6 +10,7 @@ import static chess.position.Movement.UP_UP_RIGHT; import chess.position.Movement; +import chess.position.Position; import java.util.List; public final class Knight extends Piece { @@ -24,4 +25,19 @@ public static Knight create() { DOWN_DOWN_LEFT, DOWN_DOWN_RIGHT, UP_UP_LEFT, UP_UP_RIGHT), PieceType.KNIGHT); } + + @Override + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + if (!start.canMove(movement)) { + continue; + } + Position nextPosition = start.move(movement); + if (nextPosition.equals(end)) { // 일치하는게 있음 + return; + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } } diff --git a/src/main/java/chess/piece/Pawn.java b/src/main/java/chess/piece/Pawn.java index 8f6a6a43ee9..99cc8f4f937 100644 --- a/src/main/java/chess/piece/Pawn.java +++ b/src/main/java/chess/piece/Pawn.java @@ -10,6 +10,7 @@ import static chess.position.Movement.UP_UP; import chess.position.Movement; +import chess.position.Position; import java.util.List; public final class Pawn extends Piece { @@ -25,4 +26,20 @@ public static Pawn white() { public static Pawn black() { return new Pawn(List.of(DOWN_DOWN, DOWN, RIGHT_DOWN, LEFT_DOWN), PieceType.PAWN); } + + @Override + + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + if (!start.canMove(movement)) { + continue; + } + Position nextPosition = start.move(movement); + if (nextPosition.equals(end)) { // 일치하는게 있음 + return; + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } } diff --git a/src/main/java/chess/piece/Piece.java b/src/main/java/chess/piece/Piece.java index 23815c94dc1..4c94b423e41 100644 --- a/src/main/java/chess/piece/Piece.java +++ b/src/main/java/chess/piece/Piece.java @@ -5,29 +5,32 @@ import java.util.List; public abstract class Piece { - private final List movements; - private final PieceType pieceType; + + protected final List movements; + protected final PieceType pieceType; public Piece(List movements, PieceType pieceType) { this.movements = movements; this.pieceType = pieceType; } - public PieceType type() { - return pieceType; - } + public abstract void validateMove(Position start, Position end); - public void validateMove(Position start, Position end) { + public Movement getValidateMovement(Position start, Position end) { for (Movement movement : movements) { - if (!start.canMove(movement)) { - continue; - } - Position nextPosition = start.move(movement); - if (nextPosition.equals(end)) { // 일치하는게 있음 - return; + Position nextPosition = start; + while (nextPosition.canMove(movement)) { // 보드 끝까지 가면 끝 + nextPosition = nextPosition.move(movement); // 1칸 움직임 + if (nextPosition.equals(end)) { // 가고자 하는 위치랑 일치함 + return movement; + } } } // 일치하는거 못찾음 - throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + throw new IllegalStateException("[ERROR] 해당하는 규칙을 찾을 수 없습니다.."); + } + + public PieceType type() { + return pieceType; } } diff --git a/src/main/java/chess/piece/PieceType.java b/src/main/java/chess/piece/PieceType.java index d338c7a4a70..1bc2ce61f49 100644 --- a/src/main/java/chess/piece/PieceType.java +++ b/src/main/java/chess/piece/PieceType.java @@ -2,11 +2,20 @@ public enum PieceType { - BISHOP, - KING, - KNIGHT, - PAWN, - QUEEN, - ROOk, - NONE; + BISHOP(true), + KING(false), + KNIGHT(false), + PAWN(false), + QUEEN(true), + ROOK(true); + + private final boolean canBeBlocked; + + PieceType(boolean canBeBlocked) { + this.canBeBlocked = canBeBlocked; + } + + public boolean canBeBlocked() { + return canBeBlocked; + } } diff --git a/src/main/java/chess/piece/Queen.java b/src/main/java/chess/piece/Queen.java index 859aeac018b..893dc6ef66e 100644 --- a/src/main/java/chess/piece/Queen.java +++ b/src/main/java/chess/piece/Queen.java @@ -10,6 +10,7 @@ import static chess.position.Movement.UP; import chess.position.Movement; +import chess.position.Position; import java.util.List; public final class Queen extends Piece { @@ -23,4 +24,19 @@ public static Queen create() { LEFT, RIGHT, UP, DOWN) , PieceType.QUEEN); } + + @Override + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + Position nextPosition = start; + while (nextPosition.canMove(movement)) { // 보드 끝까지 가면 끝 + nextPosition = nextPosition.move(movement); // 1칸 움직임 + if (nextPosition.equals(end)) { // 가고자 하는 위치랑 일치함 + return; + } + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } } diff --git a/src/main/java/chess/piece/Rook.java b/src/main/java/chess/piece/Rook.java index 69a5e0a5ed6..1d4a3a52fcb 100644 --- a/src/main/java/chess/piece/Rook.java +++ b/src/main/java/chess/piece/Rook.java @@ -6,6 +6,7 @@ import static chess.position.Movement.UP; import chess.position.Movement; +import chess.position.Position; import java.util.List; public final class Rook extends Piece { @@ -16,6 +17,21 @@ public Rook(List movements, PieceType pieceType) { public static Rook create() { return new Rook(List.of(LEFT, RIGHT, UP, DOWN), - PieceType.ROOk); + PieceType.ROOK); + } + + @Override + public void validateMove(Position start, Position end) { + for (Movement movement : movements) { + Position nextPosition = start; + while (nextPosition.canMove(movement)) { // 보드 끝까지 가면 끝 + nextPosition = nextPosition.move(movement); // 1칸 움직임 + if (nextPosition.equals(end)) { // 가고자 하는 위치랑 일치함 + return; + } + } + } + // 일치하는거 못찾음 + throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); } } diff --git a/src/main/java/chess/view/PieceNotation.java b/src/main/java/chess/view/PieceNotation.java index 6ca4ed17a44..4f76b4502c8 100644 --- a/src/main/java/chess/view/PieceNotation.java +++ b/src/main/java/chess/view/PieceNotation.java @@ -11,7 +11,7 @@ public enum PieceNotation { KNIGHT('n', PieceType.KNIGHT), PAWN('p', PieceType.PAWN), QUEEN('q', PieceType.QUEEN), - ROOk('r', PieceType.ROOk); + ROOk('r', PieceType.ROOK); private final Character notation; private final PieceType pieceType; diff --git a/src/test/java/chess/board/BoardTest.java b/src/test/java/chess/board/BoardTest.java index bae97407e2a..a259a022313 100644 --- a/src/test/java/chess/board/BoardTest.java +++ b/src/test/java/chess/board/BoardTest.java @@ -5,11 +5,14 @@ import chess.fixture.BoardFixture; import chess.fixture.PositionFixture; +import chess.piece.Bishop; import chess.piece.King; import chess.piece.Knight; import chess.piece.Pawn; import chess.piece.Piece; import chess.piece.PieceType; +import chess.piece.Queen; +import chess.piece.Rook; import chess.position.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -85,7 +88,7 @@ void test4() { // given Position start = PositionFixture.C2; Position end = PositionFixture.C3; - Board board = BoardFixture.createBoardWithTWoOppositePiece(PositionFixture.C2, PositionFixture.C3, + Board board = BoardFixture.createBoardWithTwoOppositePiece(PositionFixture.C2, PositionFixture.C3, King.create(), Pawn.black()); // when assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); @@ -158,7 +161,7 @@ void test2() { @Test void test4() { // given - Board board = BoardFixture.createBoardWithTWoOppositePiece(start, end, + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, end, knight, Pawn.black()); // when assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); @@ -169,4 +172,286 @@ void test4() { assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); } } + + + @Nested + @DisplayName("룩의 움직임") + class RookTest { + private final Position start = PositionFixture.A1; + private final Position end = PositionFixture.A8; + private final Piece rook = Rook.create(); + + @DisplayName("A1에서 A8로 움직일 수 있다.") + @Test + void test1() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, rook); + // when + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.ROOK); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, rook); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Board board = BoardFixture.createBoardWithTWoWhitePiece(start, end, + rook, rook); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, rook); + // when + // then + assertThatThrownBy(() -> board.move(start, PositionFixture.B2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } + + @DisplayName("도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, end, + rook, Pawn.black()); + // when + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.ROOK); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("중간 경로에 기물이 존재할 경우 예외가 발생한다.") + @Test + void test5() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, PositionFixture.A3, + rook, Pawn.black()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } + + @Nested + @DisplayName("비숍의 움직임") + class BishopTest { + private final Position start = PositionFixture.A1; + private final Position end = PositionFixture.H8; + private final Piece bishop = Bishop.create(); + + @DisplayName("A1에서 H8로 움직일 수 있다.") + @Test + void test1() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, bishop); + // when + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.BISHOP); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, bishop); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Board board = BoardFixture.createBoardWithTWoWhitePiece(start, end, + bishop, bishop); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, bishop); + // when + // then + assertThatThrownBy(() -> board.move(start, PositionFixture.B1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } + + @DisplayName("도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, end, + bishop, Pawn.black()); + // when + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end)).isEqualTo(Color.BLACK); + board.move(start, end); + // then + assertThat(board.pieceTypeAt(end)).isEqualTo(PieceType.BISHOP); + assertThat(board.colorAt(end)).isEqualTo(Color.WHITE); + } + + @DisplayName("중간 경로에 기물이 존재할 경우 예외가 발생한다.") + @Test + void test5() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, PositionFixture.C3, + bishop, Pawn.black()); + // when + // then + assertThatThrownBy(() -> board.move(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } + + @Nested + @DisplayName("퀸의 움직임") + class QueenTest { + private final Position start = PositionFixture.A1; + private final Position end1 = PositionFixture.H8; + private final Position end2 = PositionFixture.A8; + private final Position end3 = PositionFixture.H1; + private final Piece queen = Queen.create(); + + @DisplayName("A1에서 H8로 움직일 수 있다.") + @Test + void test1() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, queen); + // when + board.move(start, end1); + // then + assertThat(board.pieceTypeAt(end1)).isEqualTo(PieceType.QUEEN); + assertThat(board.colorAt(end1)).isEqualTo(Color.WHITE); + } + + @DisplayName("A1에서 A8로 움직일 수 있다.") + @Test + void test111() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, queen); + // when + board.move(start, end2); + // then + assertThat(board.pieceTypeAt(end2)).isEqualTo(PieceType.QUEEN); + assertThat(board.colorAt(end2)).isEqualTo(Color.WHITE); + } + + @DisplayName("A1에서 H1으로 움직일 수 있다.") + @Test + void test1111() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, queen); + // when + board.move(start, end3); + // then + assertThat(board.pieceTypeAt(end3)).isEqualTo(PieceType.QUEEN); + assertThat(board.colorAt(end3)).isEqualTo(Color.WHITE); + } + + @DisplayName("시작 위치에 기물이 없을 경우 에외가 발생한다.") + @Test + void test11() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(PositionFixture.C1, queen); + // when + // then + assertThatThrownBy(() -> board.move(start, end2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 기물이 없습니다."); + } + + @DisplayName("도착할 위치에 같은 팀의 기물이 있는 경우 예외가 발생한다.") + @Test + void test3() { + // given + Board board = BoardFixture.createBoardWithTWoWhitePiece(start, end2, + queen, queen); + // when + // then + assertThatThrownBy(() -> board.move(start, end2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); + } + + @DisplayName("움직일 수 없는 위치를 입력한 경우 에외가 발생한다.") + @Test + void test2() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, queen); + // when + // then + assertThatThrownBy(() -> board.move(start, PositionFixture.C2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); + } + + @DisplayName("도착할 위치에 다른 팀의 기물이 있는 경우 해당 기물을 잡을 수 있다.") + @Test + void test4() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, end2, + queen, Pawn.black()); + // when + assertThat(board.pieceTypeAt(end2)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(end2)).isEqualTo(Color.BLACK); + board.move(start, end2); + // then + assertThat(board.pieceTypeAt(end2)).isEqualTo(PieceType.QUEEN); + assertThat(board.colorAt(end2)).isEqualTo(Color.WHITE); + } + + @DisplayName("중간 경로에 기물이 존재할 경우 예외가 발생한다.") + @Test + void test5() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, PositionFixture.A4, + queen, Pawn.black()); + // when + // then + assertThatThrownBy(() -> board.move(start, end2)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } } diff --git a/src/test/java/chess/fixture/BoardFixture.java b/src/test/java/chess/fixture/BoardFixture.java index ad5de7c3e71..d29096ff8d8 100644 --- a/src/test/java/chess/fixture/BoardFixture.java +++ b/src/test/java/chess/fixture/BoardFixture.java @@ -17,7 +17,7 @@ public static Board createBoardWithTWoWhitePiece(Position position1, Position po Map.of(position1, piece1, position2, piece2)); } - public static Board createBoardWithTWoOppositePiece(Position whitePosition, + public static Board createBoardWithTwoOppositePiece(Position whitePosition, Position blackPosition, Piece whitePiece, Piece blackPiece) { From c4d78a5330a53408c01a58c4ae34dbae1d5c8f35 Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 19:12:37 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=ED=8F=B0=EC=9D=84=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/Game.java | 35 ++++++++- src/main/java/chess/board/Board.java | 33 +++++++- src/main/java/chess/piece/Bishop.java | 1 - src/main/java/chess/piece/Pawn.java | 3 +- src/main/java/chess/piece/Piece.java | 10 +++ src/test/java/chess/board/BoardTest.java | 97 ++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 5 deletions(-) diff --git a/src/main/java/chess/Game.java b/src/main/java/chess/Game.java index ef396eb018c..74b09529ac0 100644 --- a/src/main/java/chess/Game.java +++ b/src/main/java/chess/Game.java @@ -1,10 +1,32 @@ package chess; import chess.board.Board; +import chess.position.Column; +import chess.position.Position; +import chess.position.Row; import chess.view.BoardView; +import java.util.Map; +import java.util.Scanner; public class Game { private final BoardView boardView = new BoardView(); + private final Scanner scanner = new Scanner(System.in); + private final Map columnInput = Map.of( + 'A', Column.A, + 'B', Column.B, + 'C', Column.C, + 'D', Column.D, + 'F', Column.F, + 'G', Column.G, + 'H', Column.H); + private final Map rowInput = Map.of( + '1', Row.ONE, + '2', Row.TWO, + '3', Row.THREE, + '4', Row.FOUR, + '5', Row.FIVE, + '6', Row.SIX, + '7', Row.SEVEN); public static void main(String[] args) { new Game().start(); @@ -12,6 +34,17 @@ public static void main(String[] args) { public void start() { Board board = new Board(); - boardView.display(board); + String input = "start"; + while (!input.equals("end")) { + boardView.display(board); + input = scanner.nextLine(); + char srcColumn = input.charAt(5); + char srcRow = input.charAt(6); + char desColumn = input.charAt(8); + char desRow = input.charAt(9); + Position start = new Position(rowInput.get(srcRow), columnInput.get(srcColumn)); + Position end = new Position(rowInput.get(desRow), columnInput.get(desColumn)); + board.move(start, end); + } } } diff --git a/src/main/java/chess/board/Board.java b/src/main/java/chess/board/Board.java index 3e933c84f07..62f84b8a43d 100644 --- a/src/main/java/chess/board/Board.java +++ b/src/main/java/chess/board/Board.java @@ -79,8 +79,37 @@ public void move(Position start, Position end) { if (piece.type().canBeBlocked()) { validateRouteNotBlocked(piece.getValidateMovement(start, end), start, end); } + if (piece.type() == PieceType.PAWN) { + validatePawn(piece, start, end); + } pieces.put(end, piece); colors.put(end, color); + pieces.remove(start); + colors.put(start, Color.EMPTY); + piece.recordMoved(); + } + + private void validatePawn(Piece piece, Position start, Position end) { + Movement validateMovement = piece.getValidateMovement(start, end); + if (validateMovement == Movement.UP_UP) { + if (piece.moved()) { + throw new IllegalArgumentException("[ERROR] 폰은 첫 움직임에만 두 칸 이동할 수 있습니다."); + } + if (colorAt(start.move(Movement.UP)) != Color.EMPTY) { + throw new IllegalArgumentException("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } + if (validateMovement == Movement.DOWN_DOWN) { + if (piece.moved()) { + throw new IllegalArgumentException("[ERROR] 폰은 첫 움직임에만 두 칸 이동할 수 있습니다."); + } + if (colorAt(start.move(Movement.DOWN)) != Color.EMPTY) { + throw new IllegalArgumentException("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + } + if (validateMovement.isDiagonal() && colorAt(end) == Color.EMPTY) { + throw new IllegalArgumentException("[ERROR] 해당 위치에 잡을 수 있는 기물이 없습니다."); + } } private void validateRouteNotBlocked(Movement movement, Position start, Position end) { @@ -93,10 +122,10 @@ private void validateRouteNotBlocked(Movement movement, Position start, Position } private void validateEndPosition(Position start, Position end) { - if (colors.get(end).isEmpty()) { + if (colorAt(end).isEmpty()) { return; } - if (colors.get(end) == colors.get(start)) { + if (colorAt(end) == colorAt(start)) { throw new IllegalArgumentException("[ERROR] 같은 팀의 기물은 잡을 수 없습니다."); } } diff --git a/src/main/java/chess/piece/Bishop.java b/src/main/java/chess/piece/Bishop.java index e01e3f3dc40..09faabacc8e 100644 --- a/src/main/java/chess/piece/Bishop.java +++ b/src/main/java/chess/piece/Bishop.java @@ -20,7 +20,6 @@ public static Bishop create() { } @Override - public void validateMove(Position start, Position end) { for (Movement movement : movements) { Position nextPosition = start; diff --git a/src/main/java/chess/piece/Pawn.java b/src/main/java/chess/piece/Pawn.java index 99cc8f4f937..2d2483f69bd 100644 --- a/src/main/java/chess/piece/Pawn.java +++ b/src/main/java/chess/piece/Pawn.java @@ -28,7 +28,6 @@ public static Pawn black() { } @Override - public void validateMove(Position start, Position end) { for (Movement movement : movements) { if (!start.canMove(movement)) { @@ -42,4 +41,6 @@ public void validateMove(Position start, Position end) { // 일치하는거 못찾음 throw new IllegalArgumentException("[ERROR] 기물의 이동 규칙에 어긋나는 움직임입니다."); } + + } diff --git a/src/main/java/chess/piece/Piece.java b/src/main/java/chess/piece/Piece.java index 4c94b423e41..c3cc19b98fd 100644 --- a/src/main/java/chess/piece/Piece.java +++ b/src/main/java/chess/piece/Piece.java @@ -8,6 +8,8 @@ public abstract class Piece { protected final List movements; protected final PieceType pieceType; + private boolean moved = false; + public Piece(List movements, PieceType pieceType) { this.movements = movements; @@ -33,4 +35,12 @@ public Movement getValidateMovement(Position start, Position end) { public PieceType type() { return pieceType; } + + public void recordMoved() { + this.moved = true; + } + + public boolean moved() { + return moved; + } } diff --git a/src/test/java/chess/board/BoardTest.java b/src/test/java/chess/board/BoardTest.java index a259a022313..d8065df09a5 100644 --- a/src/test/java/chess/board/BoardTest.java +++ b/src/test/java/chess/board/BoardTest.java @@ -454,4 +454,101 @@ void test5() { .hasMessage("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); } } + + @Nested + @DisplayName("흰색 폰의 움직임") + class WhitePawnTest { + private final Position start = PositionFixture.C2; + private final Position upEnd = PositionFixture.C3; + private final Position upUpEnd = PositionFixture.C4; + private final Position rightUpEnd = PositionFixture.D3; + private final Position leftUpEnd = PositionFixture.B3; + private final Piece whitePawn = Pawn.white(); + + @DisplayName("C2에서 C3로 이동할 수 있다.") + @Test + void test1() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, whitePawn); + // when + board.move(start, upEnd); + // then + assertThat(board.pieceTypeAt(upEnd)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(upEnd)).isEqualTo(Color.WHITE); + } + + @DisplayName("C2에서 C4로 이동할 수 있다.") + @Test + void test2() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, whitePawn); + // when + board.move(start, upUpEnd); + // then + assertThat(board.pieceTypeAt(upUpEnd)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(upUpEnd)).isEqualTo(Color.WHITE); + } + + @DisplayName("중간에 기물이 있을 경우 두 칸 이동할 수 없다.") + @Test + void test22() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, upEnd, whitePawn, Pawn.black()); + // when + // then + assertThatThrownBy(() -> board.move(start, upUpEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 다른 기물에 의해 막혀서 이동할 수 없는 경로입니다."); + } + + @DisplayName("첫 이동이 아닌 경우 앞으로 2칸 이동할 수 없다.") + @Test + void test3() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, whitePawn); + // when + board.move(start, PositionFixture.C3); + // then + assertThatThrownBy(() -> board.move(PositionFixture.C3, PositionFixture.C5)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 폰은 첫 움직임에만 두 칸 이동할 수 있습니다."); + } + + @DisplayName("대각선 위에 아무 것도 없으면 이동할 수 없다.") + @Test + void test44() { + // given + Board board = BoardFixture.createBoardWithOneWhitePiece(start, whitePawn); + // when + // then + assertThatThrownBy(() -> board.move(start, rightUpEnd)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 해당 위치에 잡을 수 있는 기물이 없습니다."); + } + + @DisplayName("대각선 위에 상대 기물이 있으면 잡을 수 있다.") + @Test + void test4() { + // given + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, leftUpEnd, whitePawn, Pawn.black()); + // when + assertThat(board.pieceTypeAt(leftUpEnd)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(leftUpEnd)).isEqualTo(Color.BLACK); + board.move(start, leftUpEnd); + // then + assertThat(board.pieceTypeAt(leftUpEnd)).isEqualTo(PieceType.PAWN); + assertThat(board.colorAt(leftUpEnd)).isEqualTo(Color.WHITE); + } + } + + @Nested + @DisplayName("검은색 폰의 움직임") + class BlackPawnTest { + private final Position start = PositionFixture.C3; + private final Position downEnd = PositionFixture.C2; + private final Position downDownEnd = PositionFixture.C1; + private final Position rightDownEnd = PositionFixture.D2; + private final Position leftDownEnd = PositionFixture.B2; + private final Piece blackPawn = Pawn.black(); + } } From e6d7524806669a5411042f278c82333f1ed79737 Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 19:18:43 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EC=9E=85=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/Game.java | 33 ++++++++++++++++++++++--- src/main/java/chess/view/BoardView.java | 2 ++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/chess/Game.java b/src/main/java/chess/Game.java index 74b09529ac0..64b96b3d018 100644 --- a/src/main/java/chess/Game.java +++ b/src/main/java/chess/Game.java @@ -33,18 +33,43 @@ public static void main(String[] args) { } public void start() { + + System.out.println("\n" + + "RNBQKBNR 8 (rank 8)\n" + + "PPPPPPPP 7 (rank 7)\n" + + "........ 6\n" + + "........ 5\n" + + "........ 4\n" + + "........ 3\n" + + "pppppppp 2\n" + + "rnbqkbnr 1 (rank 1)\n" + + "abcdefgh\n" + + "\n" + + "- 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다.\n" + + "- 각 진영이 번갈아가면서 기물을 움직여야 하며 흰색(소문자)가 먼저 움직인다.\n" + + "- 게임 시작: start\n" + + "- 게임 종료: end\n" + + "- 게임 이동: move source target - 예) move b2 b3"); + Board board = new Board(); String input = "start"; - while (!input.equals("end")) { + while (true) { boardView.display(board); input = scanner.nextLine(); + if (input.equals("end")) { + return; + } char srcColumn = input.charAt(5); char srcRow = input.charAt(6); char desColumn = input.charAt(8); char desRow = input.charAt(9); - Position start = new Position(rowInput.get(srcRow), columnInput.get(srcColumn)); - Position end = new Position(rowInput.get(desRow), columnInput.get(desColumn)); - board.move(start, end); + try { + Position start = new Position(rowInput.get(srcRow), columnInput.get(srcColumn)); + Position end = new Position(rowInput.get(desRow), columnInput.get(desColumn)); + board.move(start, end); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } } } } diff --git a/src/main/java/chess/view/BoardView.java b/src/main/java/chess/view/BoardView.java index bee286b8172..73ac8e3323f 100644 --- a/src/main/java/chess/view/BoardView.java +++ b/src/main/java/chess/view/BoardView.java @@ -9,6 +9,7 @@ public final class BoardView { public void display(Board board) { + System.out.println(); for (Row row : Row.values()) { for (Column column : Column.values()) { Position position = new Position(row, column); @@ -21,5 +22,6 @@ public void display(Board board) { } System.out.println(); } + System.out.print("> "); } } From 63f9dd468af14f5cf17e582c5d04d97fc14ab144 Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 19:29:28 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=ED=84=B4=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/Game.java | 3 +-- src/main/java/chess/board/Board.java | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/chess/Game.java b/src/main/java/chess/Game.java index 64b96b3d018..784ca306fb3 100644 --- a/src/main/java/chess/Game.java +++ b/src/main/java/chess/Game.java @@ -47,9 +47,8 @@ public void start() { + "\n" + "- 체스판에서 각 진영은 검은색(대문자)과 흰색(소문자) 편으로 구분한다.\n" + "- 각 진영이 번갈아가면서 기물을 움직여야 하며 흰색(소문자)가 먼저 움직인다.\n" - + "- 게임 시작: start\n" + "- 게임 종료: end\n" - + "- 게임 이동: move source target - 예) move b2 b3"); + + "- 게임 이동: move source target - 예) move B2 B3"); Board board = new Board(); String input = "start"; diff --git a/src/main/java/chess/board/Board.java b/src/main/java/chess/board/Board.java index 62f84b8a43d..9dec5ebbd52 100644 --- a/src/main/java/chess/board/Board.java +++ b/src/main/java/chess/board/Board.java @@ -21,6 +21,7 @@ public final class Board { private final Map colors; private final Map pieces; + private Color turn = Color.WHITE; { // 기본 Empty @@ -74,6 +75,9 @@ public Board() { public void move(Position start, Position end) { Piece piece = findPiece(start); Color color = colorAt(start); + if (color != turn) { + throw new IllegalArgumentException("[ERROR] 선택한 색깔의 턴이 아닙니다."); + } validateEndPosition(start, end); piece.validateMove(start, end); if (piece.type().canBeBlocked()) { @@ -87,6 +91,7 @@ public void move(Position start, Position end) { pieces.remove(start); colors.put(start, Color.EMPTY); piece.recordMoved(); + turn = turn.opposite(); } private void validatePawn(Piece piece, Position start, Position end) { From 9e40a45043569d1ebee686b832ef0628288e805b Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 19:34:10 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EC=B6=9C=EB=A0=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/view/BoardView.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/chess/view/BoardView.java b/src/main/java/chess/view/BoardView.java index 73ac8e3323f..f2bb1e8ba7a 100644 --- a/src/main/java/chess/view/BoardView.java +++ b/src/main/java/chess/view/BoardView.java @@ -5,12 +5,24 @@ import chess.position.Column; import chess.position.Position; import chess.position.Row; +import java.util.Map; public final class BoardView { + private final Map rowMap = Map.of( + Row.ONE, '1', + Row.TWO, '2', + Row.THREE, '3', + Row.FOUR, '4', + Row.FIVE, '5', + Row.SIX, '6', + Row.SEVEN, '7', + Row.EIGHT, '8'); + public void display(Board board) { System.out.println(); for (Row row : Row.values()) { + System.out.printf("%c ", rowMap.get(row)); for (Column column : Column.values()) { Position position = new Position(row, column); Color color = board.colorAt(position); @@ -22,6 +34,7 @@ public void display(Board board) { } System.out.println(); } + System.out.println(" ABCDEFGH"); System.out.print("> "); } } From fe51d1833d3f7c1dfdce3be3298c4c4a54d083d0 Mon Sep 17 00:00:00 2001 From: yesjuhee Date: Sat, 22 Mar 2025 21:00:42 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=EA=B9=A8=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/chess/board/BoardTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/chess/board/BoardTest.java b/src/test/java/chess/board/BoardTest.java index d8065df09a5..affc088b00e 100644 --- a/src/test/java/chess/board/BoardTest.java +++ b/src/test/java/chess/board/BoardTest.java @@ -505,9 +505,11 @@ void test22() { @Test void test3() { // given - Board board = BoardFixture.createBoardWithOneWhitePiece(start, whitePawn); + Board board = BoardFixture.createBoardWithTwoOppositePiece(start, PositionFixture.A8, Pawn.white(), + Pawn.black()); // when board.move(start, PositionFixture.C3); + board.move(PositionFixture.A8, PositionFixture.A6); // then assertThatThrownBy(() -> board.move(PositionFixture.C3, PositionFixture.C5)) .isInstanceOf(IllegalArgumentException.class)