From 3a12a76a3f649a8b54d82ba759da8c1a3b8e6c25 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Mon, 16 Aug 2021 20:15:06 +0900 Subject: [PATCH 01/38] =?UTF-8?q?feat:=20=ED=94=BC=EC=8A=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 43 +++++++++++++++++++ build.gradle | 1 + src/main/java/chess/domain/piece/Bishop.java | 8 ++++ src/main/java/chess/domain/piece/Color.java | 6 +++ src/main/java/chess/domain/piece/King.java | 8 ++++ src/main/java/chess/domain/piece/Knight.java | 8 ++++ src/main/java/chess/domain/piece/Pawn.java | 8 ++++ src/main/java/chess/domain/piece/Piece.java | 15 +++++++ src/main/java/chess/domain/piece/Queen.java | 8 ++++ src/main/java/chess/domain/piece/Rook.java | 8 ++++ .../java/chess/domain/piece/PieceTest.java | 22 ++++++++++ src/test/java/empty.txt | 0 12 files changed, 135 insertions(+) create mode 100644 src/main/java/chess/domain/piece/Bishop.java create mode 100644 src/main/java/chess/domain/piece/Color.java create mode 100644 src/main/java/chess/domain/piece/King.java create mode 100644 src/main/java/chess/domain/piece/Knight.java create mode 100644 src/main/java/chess/domain/piece/Pawn.java create mode 100644 src/main/java/chess/domain/piece/Piece.java create mode 100644 src/main/java/chess/domain/piece/Queen.java create mode 100644 src/main/java/chess/domain/piece/Rook.java create mode 100644 src/test/java/chess/domain/piece/PieceTest.java delete mode 100644 src/test/java/empty.txt diff --git a/README.md b/README.md index 4a9cf0f..0a5fb11 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ # java-chess 게임 + +## 구현해야할 목록 + +- [] 입력 +- [] 출력 + +- [x] 기물(Piece) + - [x] 색상 + +- [] 체스판(Board) + - [] 체스판 + - [] 생성 시 32개의 기물 초기화 + +- [x] 기물 색상(Color) + - [x] 색상 + +- [] 파일(File) +- [] 랭크(Rank) + +- [] 체스판 : 8 X 8 + - 가로 ↠ : a ~ h + - 세로 ↟ : 1 ~ 8 + +## 이동 규칙 + +- [] 폰 (1 or 0.5) + - 적 방향 직선 1칸 이동 (처음 이동 시에는 2칸 이동 가능) + - 공격 : 적 방향 좌, 우 대각선 1칸 + +- [] 룩 (5) + - 모든 직선 방향으로 원하는 만큼 이동 가능 + +- [] 나이트 (2.5) + - 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 (진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다.) + +- [] 비숍 (3) + - 모든 대각선 방향으로 원하는 만큼 이동 가능 + +- [] 퀸 (9) + - 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) + +- [] 킹 + - 모든 방향 1칸 이동 (상대의 공격 범위로는 이동 불가능) diff --git a/build.gradle b/build.gradle index d41dd5b..50af46f 100644 --- a/build.gradle +++ b/build.gradle @@ -18,4 +18,5 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' testCompile "org.assertj:assertj-core:3.14.0" + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.4.2' } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java new file mode 100644 index 0000000..f18d082 --- /dev/null +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class Bishop extends Piece { + + public Bishop(final Color color) { + super(color); + } +} diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/piece/Color.java new file mode 100644 index 0000000..517538f --- /dev/null +++ b/src/main/java/chess/domain/piece/Color.java @@ -0,0 +1,6 @@ +package chess.domain.piece; + +public enum Color { + WHITE, + BLACK +} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java new file mode 100644 index 0000000..957ae20 --- /dev/null +++ b/src/main/java/chess/domain/piece/King.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class King extends Piece { + + public King(final Color color) { + super(color); + } +} diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java new file mode 100644 index 0000000..1536a29 --- /dev/null +++ b/src/main/java/chess/domain/piece/Knight.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class Knight extends Piece { + + public Knight(final Color color) { + super(color); + } +} diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java new file mode 100644 index 0000000..4f3b3fb --- /dev/null +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class Pawn extends Piece { + + public Pawn(final Color color) { + super(color); + } +} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java new file mode 100644 index 0000000..e7f862b --- /dev/null +++ b/src/main/java/chess/domain/piece/Piece.java @@ -0,0 +1,15 @@ +package chess.domain.piece; + +import static chess.domain.piece.Color.WHITE; + +public abstract class Piece { + private final Color color; + + Piece(final Color color) { + this.color = color; + } + + public boolean isWhite() { + return WHITE == color; + } +} diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java new file mode 100644 index 0000000..a331082 --- /dev/null +++ b/src/main/java/chess/domain/piece/Queen.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class Queen extends Piece { + + public Queen(final Color color) { + super(color); + } +} diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java new file mode 100644 index 0000000..d9eb8a6 --- /dev/null +++ b/src/main/java/chess/domain/piece/Rook.java @@ -0,0 +1,8 @@ +package chess.domain.piece; + +public class Rook extends Piece { + + public Rook(final Color color) { + super(color); + } +} diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java new file mode 100644 index 0000000..cb607b1 --- /dev/null +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -0,0 +1,22 @@ +package chess.domain.piece; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PieceTest { + + + @ParameterizedTest + @CsvSource({"WHITE, true", "BLACK, false"}) + @DisplayName("색상을 인자로 받아 객체를 생성한다.") + void create(Color color, boolean expected) { + //given, when + Piece piece = new Pawn(color); + + //then + assertThat(piece.isWhite()).isEqualTo(expected); + } +} diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29..0000000 From 667d30ad6ce11cff200976afeb57e0051275171e Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Mon, 16 Aug 2021 20:56:27 +0900 Subject: [PATCH 02/38] =?UTF-8?q?feat:=20=ED=94=BC=EC=8A=A4=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=BA=90=EC=8B=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++---- src/main/java/chess/domain/board/File.java | 12 ++++++ .../java/chess/domain/board/Position.java | 38 ++++++++++++++++++ src/main/java/chess/domain/board/Rank.java | 23 +++++++++++ .../java/chess/domain/board/PositionTest.java | 40 +++++++++++++++++++ 5 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 src/main/java/chess/domain/board/File.java create mode 100644 src/main/java/chess/domain/board/Position.java create mode 100644 src/main/java/chess/domain/board/Rank.java create mode 100644 src/test/java/chess/domain/board/PositionTest.java diff --git a/README.md b/README.md index 0a5fb11..4a11cef 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,14 @@ - [x] 기물(Piece) - [x] 색상 +- [x] 기물 위치(Position) + - [x] 파일 + - [x] 랭크 + - [x] 64개의 캐싱된 위치 + + - 기능 + - [x] 전달받은 인자에 해당하는 위치 객체 반환 + - [] 체스판(Board) - [] 체스판 - [] 생성 시 32개의 기물 초기화 @@ -15,13 +23,6 @@ - [x] 기물 색상(Color) - [x] 색상 -- [] 파일(File) -- [] 랭크(Rank) - -- [] 체스판 : 8 X 8 - - 가로 ↠ : a ~ h - - 세로 ↟ : 1 ~ 8 - ## 이동 규칙 - [] 폰 (1 or 0.5) diff --git a/src/main/java/chess/domain/board/File.java b/src/main/java/chess/domain/board/File.java new file mode 100644 index 0000000..ef33fa5 --- /dev/null +++ b/src/main/java/chess/domain/board/File.java @@ -0,0 +1,12 @@ +package chess.domain.board; + +public enum File { + a, + b, + c, + d, + e, + f, + g, + h +} diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java new file mode 100644 index 0000000..c31cc1e --- /dev/null +++ b/src/main/java/chess/domain/board/Position.java @@ -0,0 +1,38 @@ +package chess.domain.board; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class Position { + private static final Map POSITIONS = createPositions(); + + private static Map createPositions() { + Map positions = new HashMap<>(); + + Arrays.stream(File.values()) + .forEach(file -> Arrays.stream(Rank.values()).forEach(rank -> positions.put(createKey(file, rank), new Position(file, rank)))); + + return positions; + } + + private static String createKey(final File file, final Rank rank) { + return file.name() + rank.getRank(); + } + + public static Position from(final String key) { + return POSITIONS.get(key); + } + + public static Position from(final File file, final Rank rank) { + return POSITIONS.get(createKey(file, rank)); + } + + private final File file; + private final Rank rank; + + private Position(final File file, final Rank rank) { + this.file = file; + this.rank = rank; + } +} diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java new file mode 100644 index 0000000..33a7cfb --- /dev/null +++ b/src/main/java/chess/domain/board/Rank.java @@ -0,0 +1,23 @@ +package chess.domain.board; + +public enum Rank { + R1(1), + R2(2), + R3(3), + R4(4), + R5(5), + R6(6), + R7(7), + R8(8); + + private final int rank; + + Rank(final int rank) { + this.rank = rank; + } + + public int getRank() { + return rank; + } +} + diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java new file mode 100644 index 0000000..44604d4 --- /dev/null +++ b/src/test/java/chess/domain/board/PositionTest.java @@ -0,0 +1,40 @@ +package chess.domain.board; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PositionTest { + + @Test + @DisplayName("가로, 세로 인자에 해당하는 위치를 반환한다.") + void from_file_and_rank() { + //given + File file = File.a; + Rank rank = Rank.R1; + + //when + Position position = Position.from(file, rank); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } + + @Test + @DisplayName("문자열 키에 해당하는 위치를 반환한다.") + void from_key() { + //given + File file = File.a; + Rank rank = Rank.R1; + String key = file.name() + rank.getRank(); + + //when + Position position = Position.from(key); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } +} From 05e7c1de96dcb4adec282a4cef0f750db0745948 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Mon, 16 Aug 2021 21:45:42 +0900 Subject: [PATCH 03/38] =?UTF-8?q?feat:=20=EC=B2=B4=EC=8A=A4=ED=8C=90=2032?= =?UTF-8?q?=EA=B0=9C=EC=9D=98=20=ED=94=BC=EC=8A=A4=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94,=20=EC=83=9D=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +-- src/main/java/chess/domain/board/Board.java | 47 +++++++++++++++++++ .../java/chess/domain/board/BoardTest.java | 24 ++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/main/java/chess/domain/board/Board.java create mode 100644 src/test/java/chess/domain/board/BoardTest.java diff --git a/README.md b/README.md index 4a11cef..3353994 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,10 @@ - 기능 - [x] 전달받은 인자에 해당하는 위치 객체 반환 -- [] 체스판(Board) - - [] 체스판 - - [] 생성 시 32개의 기물 초기화 +- [x] 체스판(Board) + - [x] 체스판 + - [x] 생성 시 32개의 기물 초기화 + - [x] 인자로 전달받은 위치에 기물이 있는지 확인 - [x] 기물 색상(Color) - [x] 색상 diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java new file mode 100644 index 0000000..3c85ecc --- /dev/null +++ b/src/main/java/chess/domain/board/Board.java @@ -0,0 +1,47 @@ +package chess.domain.board; + +import chess.domain.piece.Bishop; +import chess.domain.piece.Color; +import chess.domain.piece.King; +import chess.domain.piece.Knight; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class Board { + private final Map board = new HashMap<>(); + + public Board() { + initializePawns(); + initializeOthers(Rank.R1, Color.WHITE); + initializeOthers(Rank.R8, Color.BLACK); + } + + private void initializeOthers(Rank rank, Color color) { + board.put(Position.from(File.a, rank), new Rook(color)); + board.put(Position.from(File.b, rank), new Knight(color)); + board.put(Position.from(File.c, rank), new Bishop(color)); + board.put(Position.from(File.d, rank), new Queen(color)); + board.put(Position.from(File.e, rank), new King(color)); + board.put(Position.from(File.f, rank), new Bishop(color)); + board.put(Position.from(File.g, rank), new Knight(color)); + board.put(Position.from(File.h, rank), new Rook(color)); + } + + private void initializePawns() { + Arrays.stream(File.values()) + .forEach(file -> { + board.put(Position.from(file, Rank.R2), new Pawn(Color.WHITE)); + board.put(Position.from(file, Rank.R7), new Pawn(Color.BLACK)); + }); + } + + public boolean isEmpty(Position position) { + return board.get(position) == null; + } +} diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java new file mode 100644 index 0000000..2cbcee9 --- /dev/null +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -0,0 +1,24 @@ +package chess.domain.board; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class BoardTest { + + @ParameterizedTest + @CsvSource({"a1, false", "a3, true"}) + @DisplayName("객체를 생성한다.") + void create(String key, boolean expected) { + //given + Position position = Position.from(key); + + //when + Board board = new Board(); + + //then + assertThat(board.isEmpty(position)).isEqualTo(expected); + } +} From 2d3380301a493e38509dcd9270e661aff366e982 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 15:03:26 +0900 Subject: [PATCH 04/38] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=A2=85?= =?UTF-8?q?=EB=A5=98=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20=EB=A7=A4=ED=95=91=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=9D=B4=EB=A6=84=20=EB=A7=A4=ED=8D=BC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++-- .../domain/piece/mapper/BishopMapper.java | 18 +++++++++ .../chess/domain/piece/mapper/KingMapper.java | 18 +++++++++ .../domain/piece/mapper/KnightMapper.java | 18 +++++++++ .../chess/domain/piece/mapper/PawnMapper.java | 18 +++++++++ .../domain/piece/mapper/PieceMapper.java | 21 ++++++++++ .../domain/piece/mapper/PieceMappers.java | 38 +++++++++++++++++++ .../domain/piece/mapper/QueenMapper.java | 18 +++++++++ .../chess/domain/piece/mapper/RookMapper.java | 18 +++++++++ .../domain/piece/mapper/PieceMappersTest.java | 35 +++++++++++++++++ 10 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 src/main/java/chess/domain/piece/mapper/BishopMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/KingMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/KnightMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/PawnMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/PieceMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/PieceMappers.java create mode 100644 src/main/java/chess/domain/piece/mapper/QueenMapper.java create mode 100644 src/main/java/chess/domain/piece/mapper/RookMapper.java create mode 100644 src/test/java/chess/domain/piece/mapper/PieceMappersTest.java diff --git a/README.md b/README.md index 3353994..3f45505 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,18 @@ - 기능 - [x] 전달받은 인자에 해당하는 위치 객체 반환 +- [x] 기물 색상(Color) + - [x] 색상 + +- [x] 기물 이름 매퍼 + - [x] 기물 종류에 따라 이름을 매핑 + + - [x] 체스판(Board) - [x] 체스판 - [x] 생성 시 32개의 기물 초기화 - [x] 인자로 전달받은 위치에 기물이 있는지 확인 -- [x] 기물 색상(Color) - - [x] 색상 - ## 이동 규칙 - [] 폰 (1 or 0.5) diff --git a/src/main/java/chess/domain/piece/mapper/BishopMapper.java b/src/main/java/chess/domain/piece/mapper/BishopMapper.java new file mode 100644 index 0000000..f7512df --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/BishopMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Bishop; +import chess.domain.piece.Piece; + +public class BishopMapper extends PieceMapper { + + private static final String NAME = "b"; + + public BishopMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof Bishop; + } +} diff --git a/src/main/java/chess/domain/piece/mapper/KingMapper.java b/src/main/java/chess/domain/piece/mapper/KingMapper.java new file mode 100644 index 0000000..9b38e06 --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/KingMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.King; +import chess.domain.piece.Piece; + +public class KingMapper extends PieceMapper { + + private static final String NAME = "k"; + + public KingMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof King; + } +} diff --git a/src/main/java/chess/domain/piece/mapper/KnightMapper.java b/src/main/java/chess/domain/piece/mapper/KnightMapper.java new file mode 100644 index 0000000..af07be6 --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/KnightMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Knight; +import chess.domain.piece.Piece; + +public class KnightMapper extends PieceMapper { + + private static final String NAME = "n"; + + public KnightMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof Knight; + } +} diff --git a/src/main/java/chess/domain/piece/mapper/PawnMapper.java b/src/main/java/chess/domain/piece/mapper/PawnMapper.java new file mode 100644 index 0000000..7b416aa --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/PawnMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; + +public class PawnMapper extends PieceMapper { + + private static final String NAME = "p"; + + public PawnMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof Pawn; + } +} diff --git a/src/main/java/chess/domain/piece/mapper/PieceMapper.java b/src/main/java/chess/domain/piece/mapper/PieceMapper.java new file mode 100644 index 0000000..1880424 --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/PieceMapper.java @@ -0,0 +1,21 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Piece; + +public abstract class PieceMapper { + private final String name; + + PieceMapper(final String name) { + this.name = name; + } + + abstract boolean supports(Piece piece); + + public String findNameBy(Piece piece) { + if (piece.isWhite()) { + return name; + } + + return name.toUpperCase(); + } +} diff --git a/src/main/java/chess/domain/piece/mapper/PieceMappers.java b/src/main/java/chess/domain/piece/mapper/PieceMappers.java new file mode 100644 index 0000000..be4d012 --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/PieceMappers.java @@ -0,0 +1,38 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Piece; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.NoSuchElementException; + +public class PieceMappers { + + private static final Collection pieceMappers = createMappers(); + + private static Collection createMappers() { + return Collections.unmodifiableCollection( + Arrays.asList( + new PawnMapper(), + new KnightMapper(), + new BishopMapper(), + new RookMapper(), + new QueenMapper(), + new KingMapper() + )); + } + + public static String findNameBy(final Piece piece) { + return pieceMappers.stream() + .filter(mapper -> mapper.supports(piece)) + .map(mapper -> mapper.findNameBy(piece)) + .findAny() + .orElseThrow(() -> { + throw new NoSuchElementException("해당 기물에 해당하는 매퍼가 존재하지 않습니다."); + }); + } + + private PieceMappers() { + } +} diff --git a/src/main/java/chess/domain/piece/mapper/QueenMapper.java b/src/main/java/chess/domain/piece/mapper/QueenMapper.java new file mode 100644 index 0000000..bf20c2a --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/QueenMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Piece; +import chess.domain.piece.Queen; + +public class QueenMapper extends PieceMapper { + + private static final String NAME = "q"; + + public QueenMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof Queen; + } +} diff --git a/src/main/java/chess/domain/piece/mapper/RookMapper.java b/src/main/java/chess/domain/piece/mapper/RookMapper.java new file mode 100644 index 0000000..9f38c6d --- /dev/null +++ b/src/main/java/chess/domain/piece/mapper/RookMapper.java @@ -0,0 +1,18 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Piece; +import chess.domain.piece.Rook; + +public class RookMapper extends PieceMapper { + + private static final String NAME = "r"; + + public RookMapper() { + super(NAME); + } + + @Override + public boolean supports(final Piece piece) { + return piece instanceof Rook; + } +} diff --git a/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java b/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java new file mode 100644 index 0000000..6582ff8 --- /dev/null +++ b/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java @@ -0,0 +1,35 @@ +package chess.domain.piece.mapper; + +import chess.domain.piece.Color; +import chess.domain.piece.Pawn; +import chess.domain.piece.Piece; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PieceMappersTest { + + @ParameterizedTest + @MethodSource("createParams") + @DisplayName("피스가 주어지면 해당 피스와 색상에 맞는 이름을 반환한다.") + void find(Piece piece, String expected) { + //given, when + String name = PieceMappers.findNameBy(piece); + + //then + assertThat(name).isEqualTo(expected); + + } + + private static Stream createParams() { + return Stream.of( + Arguments.of(new Pawn(Color.WHITE), "p"), + Arguments.of(new Pawn(Color.BLACK), "P") + ); + } +} From cc544923f6fd0b7b7c87b2a202b854a497fb9fd2 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 15:04:37 +0900 Subject: [PATCH 05/38] =?UTF-8?q?feat:=20=EC=B2=B4=EC=8A=A4=ED=8C=90=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + src/main/java/chess/ConsoleApplication.java | 14 +++++++ .../chess/controller/ChessController.java | 20 +++++++++ .../java/chess/controller/dto/BoardDto.java | 41 +++++++++++++++++++ .../chess/controller/dto/PositionDto.java | 28 +++++++++++++ src/main/java/chess/domain/board/Board.java | 13 ++++++ src/main/java/chess/domain/board/File.java | 26 ++++++++---- .../java/chess/domain/board/Position.java | 10 +++-- src/main/java/chess/domain/board/Rank.java | 26 ++++++------ .../java/chess/view/ConsoleOutputView.java | 18 ++++++++ src/main/java/chess/view/OutputView.java | 7 ++++ .../java/chess/domain/board/PositionTest.java | 2 +- 12 files changed, 182 insertions(+), 24 deletions(-) create mode 100644 src/main/java/chess/ConsoleApplication.java create mode 100644 src/main/java/chess/controller/ChessController.java create mode 100644 src/main/java/chess/controller/dto/BoardDto.java create mode 100644 src/main/java/chess/controller/dto/PositionDto.java create mode 100644 src/main/java/chess/view/ConsoleOutputView.java create mode 100644 src/main/java/chess/view/OutputView.java diff --git a/README.md b/README.md index 3f45505..cefdbd6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ - [] 입력 - [] 출력 + - [x] 체스판 전체 출력 - [x] 기물(Piece) - [x] 색상 diff --git a/src/main/java/chess/ConsoleApplication.java b/src/main/java/chess/ConsoleApplication.java new file mode 100644 index 0000000..a220824 --- /dev/null +++ b/src/main/java/chess/ConsoleApplication.java @@ -0,0 +1,14 @@ +package chess; + +import chess.controller.ChessController; +import chess.view.ConsoleOutputView; +import chess.view.OutputView; + +public class ConsoleApplication { + + public static void main(String[] args) { + OutputView outputView = new ConsoleOutputView(); + ChessController chessController = new ChessController(outputView); + chessController.run(); + } +} diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ChessController.java new file mode 100644 index 0000000..2d57135 --- /dev/null +++ b/src/main/java/chess/controller/ChessController.java @@ -0,0 +1,20 @@ +package chess.controller; + +import chess.controller.dto.BoardDto; +import chess.domain.board.Board; +import chess.view.OutputView; + +public class ChessController { + + private final OutputView outputView; + + public ChessController(final OutputView outputView) { + this.outputView = outputView; + } + + public void run() { + Board board = new Board(); + BoardDto boardDto = new BoardDto(board); + outputView.printBoard(boardDto); + } +} diff --git a/src/main/java/chess/controller/dto/BoardDto.java b/src/main/java/chess/controller/dto/BoardDto.java new file mode 100644 index 0000000..31dd52c --- /dev/null +++ b/src/main/java/chess/controller/dto/BoardDto.java @@ -0,0 +1,41 @@ +package chess.controller.dto; + +import chess.domain.board.Board; +import chess.domain.board.File; +import chess.domain.board.Position; +import chess.domain.board.Rank; +import chess.domain.piece.Piece; +import chess.domain.piece.mapper.PieceMappers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class BoardDto { + private List positionDtos = new ArrayList<>(); + + public BoardDto(final Board board) { + Arrays.stream(Rank.values()).forEach(rank -> Arrays.stream( + File.values()).forEach(file -> addPositionDto(file, rank, board))); + } + + private void addPositionDto(final File file, final Rank rank, final Board board) { + Position position = Position.from(file, rank); + + if (board.isEmpty(position)) { + PositionDto positionDto = new PositionDto(position.getFile()); + positionDtos.add(positionDto); + return; + } + + Piece piece = board.findBy(position); + String name = PieceMappers.findNameBy(piece); + PositionDto positionDto = new PositionDto(position.getFile(), name); + positionDtos.add(positionDto); + } + + public List getPositionDtos() { + return Collections.unmodifiableList(positionDtos); + } +} diff --git a/src/main/java/chess/controller/dto/PositionDto.java b/src/main/java/chess/controller/dto/PositionDto.java new file mode 100644 index 0000000..384a91d --- /dev/null +++ b/src/main/java/chess/controller/dto/PositionDto.java @@ -0,0 +1,28 @@ +package chess.controller.dto; + +import chess.domain.board.File; + +public class PositionDto { + + private static final String DEFAULT_NAME = "."; + + private final boolean isLastFile; + private final String name; + + public PositionDto(final File file, final String name) { + this.isLastFile = (file == File.h); + this.name = name; + } + + public PositionDto(final File file) { + this(file, DEFAULT_NAME); + } + + public boolean isLastFile() { + return isLastFile; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 3c85ecc..d099359 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -10,6 +10,7 @@ import chess.domain.piece.Rook; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -44,4 +45,16 @@ private void initializePawns() { public boolean isEmpty(Position position) { return board.get(position) == null; } + + public Piece findBy(final Position position) { + return board.get(position); + } + + public Collection getAllPresentPieces() { + return board.values(); + } + + public Collection getAllPresentPositions() { + return board.keySet(); + } } diff --git a/src/main/java/chess/domain/board/File.java b/src/main/java/chess/domain/board/File.java index ef33fa5..c30eed7 100644 --- a/src/main/java/chess/domain/board/File.java +++ b/src/main/java/chess/domain/board/File.java @@ -1,12 +1,22 @@ package chess.domain.board; public enum File { - a, - b, - c, - d, - e, - f, - g, - h + a(1), + b(2), + c(3), + d(4), + e(5), + f(6), + g(7), + h(8); + + private final int index; + + File(final int index) { + this.index = index; + } + + public int getIndex() { + return index; + } } diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index c31cc1e..8a2be56 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -1,14 +1,14 @@ package chess.domain.board; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; public class Position { private static final Map POSITIONS = createPositions(); private static Map createPositions() { - Map positions = new HashMap<>(); + Map positions = new LinkedHashMap<>(); Arrays.stream(File.values()) .forEach(file -> Arrays.stream(Rank.values()).forEach(rank -> positions.put(createKey(file, rank), new Position(file, rank)))); @@ -17,7 +17,7 @@ private static Map createPositions() { } private static String createKey(final File file, final Rank rank) { - return file.name() + rank.getRank(); + return file.name() + rank.getIndex(); } public static Position from(final String key) { @@ -35,4 +35,8 @@ private Position(final File file, final Rank rank) { this.file = file; this.rank = rank; } + + public File getFile() { + return file; + } } diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java index 33a7cfb..781d032 100644 --- a/src/main/java/chess/domain/board/Rank.java +++ b/src/main/java/chess/domain/board/Rank.java @@ -1,23 +1,25 @@ package chess.domain.board; public enum Rank { - R1(1), - R2(2), - R3(3), - R4(4), - R5(5), - R6(6), + R8(8), R7(7), - R8(8); + R6(6), + R5(5), + R4(4), + R3(3), + R2(2), + R1(1); - private final int rank; + private final int index; - Rank(final int rank) { - this.rank = rank; + Rank(final int index) { + this.index = index; } - public int getRank() { - return rank; + public int getIndex() { + return index; } + + } diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java new file mode 100644 index 0000000..8d08771 --- /dev/null +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -0,0 +1,18 @@ +package chess.view; + +import chess.controller.dto.BoardDto; + +public class ConsoleOutputView implements OutputView { + + + @Override + public void printBoard(final BoardDto boardDto) { + boardDto.getPositionDtos() + .forEach(positionDto -> { + System.out.print(positionDto.getName()); + if (positionDto.isLastFile()) { + System.out.println(); + } + }); + } +} diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java new file mode 100644 index 0000000..1040482 --- /dev/null +++ b/src/main/java/chess/view/OutputView.java @@ -0,0 +1,7 @@ +package chess.view; + +import chess.controller.dto.BoardDto; + +public interface OutputView { + void printBoard(BoardDto boardDto); +} diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java index 44604d4..ad1ef0a 100644 --- a/src/test/java/chess/domain/board/PositionTest.java +++ b/src/test/java/chess/domain/board/PositionTest.java @@ -28,7 +28,7 @@ void from_key() { //given File file = File.a; Rank rank = Rank.R1; - String key = file.name() + rank.getRank(); + String key = file.name() + rank.getIndex(); //when Position position = Position.from(key); From 8bc0bd5b409f56e0c9347efa37fdcf2c67bc98bf Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 15:46:16 +0900 Subject: [PATCH 06/38] =?UTF-8?q?feat:=20=EC=B2=B4=EC=8A=A4=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EC=8B=9C=EC=9E=91&=EC=A2=85=EB=A3=8C=20=EB=AA=85?= =?UTF-8?q?=EB=A0=B9=EC=96=B4=20=EC=9E=85=EB=A0=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +++++++++------ src/main/java/chess/ConsoleApplication.java | 5 +- .../chess/controller/ChessController.java | 17 +++++-- src/main/java/chess/domain/ChessGame.java | 34 ++++++++++++++ .../java/chess/view/ConsoleInputView.java | 23 ++++++++++ src/main/java/chess/view/InputView.java | 5 ++ src/test/java/chess/domain/ChessGameTest.java | 46 +++++++++++++++++++ 7 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 src/main/java/chess/domain/ChessGame.java create mode 100644 src/main/java/chess/view/ConsoleInputView.java create mode 100644 src/main/java/chess/view/InputView.java create mode 100644 src/test/java/chess/domain/ChessGameTest.java diff --git a/README.md b/README.md index cefdbd6..73f18df 100644 --- a/README.md +++ b/README.md @@ -3,31 +3,38 @@ ## 구현해야할 목록 - [] 입력 + - [x] 시작 여부 입력 - [] 출력 - [x] 체스판 전체 출력 -- [x] 기물(Piece) - - [x] 색상 +- [ ] 체스게임(ChessGame) + - [x] 체스판 + - [x] 게임 시작&종료 상태 제어 + - [x] 명령어 검증 및 처리 -- [x] 기물 위치(Position) - - [x] 파일 - - [x] 랭크 - - [x] 64개의 캐싱된 위치 +- [x] 체스판(Board) + - [x] 체스판 + - [x] 생성 시 32개의 기물 초기화 + - [x] 인자로 전달받은 위치에 기물이 있는지 확인 - - 기능 - - [x] 전달받은 인자에 해당하는 위치 객체 반환 -- [x] 기물 색상(Color) +- [x] 기물(Piece) - [x] 색상 -- [x] 기물 이름 매퍼 - - [x] 기물 종류에 따라 이름을 매핑 + - [x] 기물 위치(Position) + - [x] 파일 + - [x] 랭크 + - [x] 64개의 캐싱된 위치 + - 기능 + - [x] 전달받은 인자에 해당하는 위치 객체 반환 + + - [x] 기물 색상(Color) + - [x] 색상 + + - [x] 기물 이름 매퍼 + - [x] 기물 종류에 따라 이름을 매핑 -- [x] 체스판(Board) - - [x] 체스판 - - [x] 생성 시 32개의 기물 초기화 - - [x] 인자로 전달받은 위치에 기물이 있는지 확인 ## 이동 규칙 diff --git a/src/main/java/chess/ConsoleApplication.java b/src/main/java/chess/ConsoleApplication.java index a220824..d5f97f2 100644 --- a/src/main/java/chess/ConsoleApplication.java +++ b/src/main/java/chess/ConsoleApplication.java @@ -1,14 +1,17 @@ package chess; import chess.controller.ChessController; +import chess.view.ConsoleInputView; import chess.view.ConsoleOutputView; +import chess.view.InputView; import chess.view.OutputView; public class ConsoleApplication { public static void main(String[] args) { OutputView outputView = new ConsoleOutputView(); - ChessController chessController = new ChessController(outputView); + InputView inputView = new ConsoleInputView(); + ChessController chessController = new ChessController(inputView, outputView); chessController.run(); } } diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ChessController.java index 2d57135..0c0d25b 100644 --- a/src/main/java/chess/controller/ChessController.java +++ b/src/main/java/chess/controller/ChessController.java @@ -1,20 +1,27 @@ package chess.controller; import chess.controller.dto.BoardDto; -import chess.domain.board.Board; +import chess.domain.ChessGame; +import chess.view.InputView; import chess.view.OutputView; public class ChessController { + private final InputView inputView; private final OutputView outputView; - public ChessController(final OutputView outputView) { + public ChessController(final InputView inputView, final OutputView outputView) { + this.inputView = inputView; this.outputView = outputView; } public void run() { - Board board = new Board(); - BoardDto boardDto = new BoardDto(board); - outputView.printBoard(boardDto); + String initialCommand = inputView.getInitialCommand(); + ChessGame chessGame = new ChessGame(initialCommand); + + while (chessGame.isRunning()) { + BoardDto boardDto = new BoardDto(chessGame.getBoard()); + outputView.printBoard(boardDto); + } } } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java new file mode 100644 index 0000000..d426698 --- /dev/null +++ b/src/main/java/chess/domain/ChessGame.java @@ -0,0 +1,34 @@ +package chess.domain; + +import chess.domain.board.Board; + +public class ChessGame { + + private static final String START = "start"; + private static final String END = "end"; + + private final Board board = new Board(); + private boolean isRunning = false; + + public ChessGame(final String initialCommand) { + if (initialCommand.equals(START)) { + isRunning = true; + return; + } + + if (initialCommand.equals(END)) { + return; + } + + throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); + } + + + public boolean isRunning() { + return isRunning; + } + + public Board getBoard() { + return board; + } +} diff --git a/src/main/java/chess/view/ConsoleInputView.java b/src/main/java/chess/view/ConsoleInputView.java new file mode 100644 index 0000000..1438cf8 --- /dev/null +++ b/src/main/java/chess/view/ConsoleInputView.java @@ -0,0 +1,23 @@ +package chess.view; + +import java.util.Scanner; + +public class ConsoleInputView implements InputView { + private static final Scanner scanner = new Scanner(System.in); + private static final String HEADER = "> "; + + @Override + public String getInitialCommand() { + System.out.println(HEADER + "체스 게임을 시작합니다."); + System.out.println(HEADER + "게임 시작은 start, 종료는 end 명령을 입력하세요."); + String input = scanner.nextLine(); + validateNull(input); + return input.trim(); + } + + private void validateNull(final String input) { + if (input == null) { + throw new IllegalArgumentException("잘못된 입력입니다."); + } + } +} diff --git a/src/main/java/chess/view/InputView.java b/src/main/java/chess/view/InputView.java new file mode 100644 index 0000000..8b5bfc9 --- /dev/null +++ b/src/main/java/chess/view/InputView.java @@ -0,0 +1,5 @@ +package chess.view; + +public interface InputView { + String getInitialCommand(); +} diff --git a/src/test/java/chess/domain/ChessGameTest.java b/src/test/java/chess/domain/ChessGameTest.java new file mode 100644 index 0000000..891110c --- /dev/null +++ b/src/test/java/chess/domain/ChessGameTest.java @@ -0,0 +1,46 @@ +package chess.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ChessGameTest { + + @Test + @DisplayName("게임 시작 명령어를 입력받을 경우 게임을 시작한다.") + void create_start_command() { + //given + String command = "start"; + + //when + ChessGame chessGame = new ChessGame(command); + + //then + assertThat(chessGame.isRunning()).isTrue(); + } + + @Test + @DisplayName("게임 종료 명령어를 입력받을 경우 게임을 종료한다.") + void create_end_command() { + //given + String command = "end"; + + //when + ChessGame chessGame = new ChessGame(command); + + //then + assertThat(chessGame.isRunning()).isFalse(); + } + + @Test + @DisplayName("유효하지 않은 명령어를 입력받을 경우 예외를 던진다.") + void create_invalid_command() { + //given + String command = "invalid_command"; + + //when, then + assertThrows(UnsupportedOperationException.class, () -> new ChessGame(command)); + } +} From f6c2eb47bb7e440f4cc5d001a88aa71c166afc11 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 17:24:11 +0900 Subject: [PATCH 07/38] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EC=8B=9C=20=EA=B3=B5=ED=86=B5=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++++- .../chess/controller/ChessController.java | 6 ++- src/main/java/chess/domain/ChessGame.java | 26 +++++++--- src/main/java/chess/domain/board/Board.java | 36 +++++++++---- .../java/chess/domain/command/Command.java | 52 +++++++++++++++++++ .../chess/domain/command/MoveParameters.java | 31 +++++++++++ src/main/java/chess/domain/piece/Piece.java | 4 ++ .../java/chess/view/ConsoleInputView.java | 2 +- src/main/java/chess/view/InputView.java | 2 +- src/test/java/chess/domain/ChessGameTest.java | 22 ++++---- .../java/chess/domain/board/BoardTest.java | 42 +++++++++++++++ 11 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 src/main/java/chess/domain/command/Command.java create mode 100644 src/main/java/chess/domain/command/MoveParameters.java diff --git a/README.md b/README.md index 73f18df..1e86af3 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,28 @@ - [ ] 체스게임(ChessGame) - [x] 체스판 + - [] 현재 플레이어 - [x] 게임 시작&종료 상태 제어 - - [x] 명령어 검증 및 처리 + + - 기능 + - [x] 명령어 검증 및 처리 + - [x] start : 게임 실행 + - [x] end : 게임 종료 + - [] move : 인자로 전달받은 source 위치에서 target 위치로 기물 이동 - [x] 체스판(Board) - [x] 체스판 - [x] 생성 시 32개의 기물 초기화 - - [x] 인자로 전달받은 위치에 기물이 있는지 확인 + - 기능 + - [x] 인자로 전달받은 위치에 기물이 있는지 확인 + - [x] ERROR : 존재하지 않을 경우 + - [x] 같은 색상 기물인지 확인 + - [x] ERROR : 다른 색상일 경우 + - [x] 시작과 도착 위치의 기물이 다른 색상인지 확인 + - [x] ERROR : 같은 색상일 경우 + - [] source 위치에서 target 위치로 기물 이동 + - [] ERROR : target 위치에 같은 색상의 기물이 있을 경우 - [x] 기물(Piece) - [x] 색상 @@ -56,3 +70,10 @@ - [] 킹 - 모든 방향 1칸 이동 (상대의 공격 범위로는 이동 불가능) + +## Advanced + +체스판(Board) + +- [] 위치가 체스판 범위 내인지 확인 + - [] ERROR : 범위 밖일 경우 diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ChessController.java index 0c0d25b..f9a587b 100644 --- a/src/main/java/chess/controller/ChessController.java +++ b/src/main/java/chess/controller/ChessController.java @@ -2,6 +2,7 @@ import chess.controller.dto.BoardDto; import chess.domain.ChessGame; +import chess.domain.command.Command; import chess.view.InputView; import chess.view.OutputView; @@ -16,10 +17,11 @@ public ChessController(final InputView inputView, final OutputView outputView) { } public void run() { - String initialCommand = inputView.getInitialCommand(); - ChessGame chessGame = new ChessGame(initialCommand); + ChessGame chessGame = new ChessGame(); while (chessGame.isRunning()) { + Command command = new Command(inputView.getCommand()); + chessGame.run(command); BoardDto boardDto = new BoardDto(chessGame.getBoard()); outputView.printBoard(boardDto); } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index d426698..d1462a4 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -1,28 +1,38 @@ package chess.domain; import chess.domain.board.Board; +import chess.domain.command.Command; +import chess.domain.command.MoveParameters; public class ChessGame { - private static final String START = "start"; - private static final String END = "end"; - private final Board board = new Board(); - private boolean isRunning = false; + private boolean isRunning = true; + private boolean isWhiteTurn = true; + + public ChessGame() { + } - public ChessGame(final String initialCommand) { - if (initialCommand.equals(START)) { - isRunning = true; + public void run(final Command command) { + if (command.isStart()) { return; } - if (initialCommand.equals(END)) { + if (command.isEnd()) { + isRunning = false; return; } + if (command.isMove()) { + move(command.getMoveParameters()); + } + throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); } + private void move(final MoveParameters moveParameters) { + board.move(moveParameters, isWhiteTurn); + } public boolean isRunning() { return isRunning; diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index d099359..ed9e999 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -1,5 +1,6 @@ package chess.domain.board; +import chess.domain.command.MoveParameters; import chess.domain.piece.Bishop; import chess.domain.piece.Color; import chess.domain.piece.King; @@ -10,7 +11,6 @@ import chess.domain.piece.Rook; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -42,19 +42,37 @@ private void initializePawns() { }); } - public boolean isEmpty(Position position) { - return board.get(position) == null; + public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) { + Position source = moveParameters.getSource(); + Position target = moveParameters.getTarget(); + Piece sourcePiece = findBy(source); + + validateColor(isWhiteTurn, sourcePiece.isWhite()); + + Piece targetPiece = findBy(target); + if (sourcePiece.hasSameColor(targetPiece)) { + throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); + } + + board.put(target, sourcePiece); + board.remove(source); } - public Piece findBy(final Position position) { - return board.get(position); + private void validateColor(final boolean expectedColor, final boolean sourcePieceColor) { + if (sourcePieceColor != expectedColor) { + throw new IllegalArgumentException("같은 색상의 기물만 움직일 수 있습니다."); + } } - public Collection getAllPresentPieces() { - return board.values(); + public Piece findBy(final Position position) { + if (isEmpty(position)) { + throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + } + + return board.get(position); } - public Collection getAllPresentPositions() { - return board.keySet(); + public boolean isEmpty(Position position) { + return board.get(position) == null; } } diff --git a/src/main/java/chess/domain/command/Command.java b/src/main/java/chess/domain/command/Command.java new file mode 100644 index 0000000..ddea82a --- /dev/null +++ b/src/main/java/chess/domain/command/Command.java @@ -0,0 +1,52 @@ +package chess.domain.command; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class Command { + + private static final String DELIMITER = " "; + private static final int COMMAND_INDEX = 0; + + private static final String START = "start"; + private static final String END = "end"; + private static final String MOVE = "move"; + + private final String command; + private final List parameters; + + public Command(final String commandLine) { + List chunks = Arrays.stream(commandLine.split(DELIMITER)) + .map(String::trim) + .map(String::toLowerCase) + .collect(Collectors.toList()); + + if (chunks.isEmpty()) { + throw new IllegalArgumentException(); + } + + this.command = chunks.get(COMMAND_INDEX); + this.parameters = chunks.subList(COMMAND_INDEX + 1, chunks.size()); + } + + public boolean isStart() { + return command.equals(START); + } + + public boolean isEnd() { + return command.equals(END); + } + + public boolean isMove() { + return command.equals(MOVE); + } + + public MoveParameters getMoveParameters() { + if (parameters.size() != 2) { + throw new IllegalArgumentException(); + } + + return new MoveParameters(parameters); + } +} diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java new file mode 100644 index 0000000..44e0947 --- /dev/null +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -0,0 +1,31 @@ +package chess.domain.command; + +import chess.domain.board.Position; + +import java.util.List; + +public class MoveParameters { + + private static final int SOURCE_INDEX = 0; + private static final int TARGET_INDEX = 1; + + private final Position source; + private final Position target; + + public MoveParameters(final List parameters) { + this(Position.from(parameters.get(SOURCE_INDEX)), Position.from(parameters.get(TARGET_INDEX))); + } + + public MoveParameters(final Position source, final Position target) { + this.source = source; + this.target = target; + } + + public Position getSource() { + return source; + } + + public Position getTarget() { + return target; + } +} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index e7f862b..c704e19 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -12,4 +12,8 @@ public abstract class Piece { public boolean isWhite() { return WHITE == color; } + + public boolean hasSameColor(final Piece target) { + return this.color == target.color; + } } diff --git a/src/main/java/chess/view/ConsoleInputView.java b/src/main/java/chess/view/ConsoleInputView.java index 1438cf8..2f63d25 100644 --- a/src/main/java/chess/view/ConsoleInputView.java +++ b/src/main/java/chess/view/ConsoleInputView.java @@ -7,7 +7,7 @@ public class ConsoleInputView implements InputView { private static final String HEADER = "> "; @Override - public String getInitialCommand() { + public String getCommand() { System.out.println(HEADER + "체스 게임을 시작합니다."); System.out.println(HEADER + "게임 시작은 start, 종료는 end 명령을 입력하세요."); String input = scanner.nextLine(); diff --git a/src/main/java/chess/view/InputView.java b/src/main/java/chess/view/InputView.java index 8b5bfc9..3504758 100644 --- a/src/main/java/chess/view/InputView.java +++ b/src/main/java/chess/view/InputView.java @@ -1,5 +1,5 @@ package chess.view; public interface InputView { - String getInitialCommand(); + String getCommand(); } diff --git a/src/test/java/chess/domain/ChessGameTest.java b/src/test/java/chess/domain/ChessGameTest.java index 891110c..54a43d9 100644 --- a/src/test/java/chess/domain/ChessGameTest.java +++ b/src/test/java/chess/domain/ChessGameTest.java @@ -1,5 +1,6 @@ package chess.domain; +import chess.domain.command.Command; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,12 +11,13 @@ public class ChessGameTest { @Test @DisplayName("게임 시작 명령어를 입력받을 경우 게임을 시작한다.") - void create_start_command() { + void run_start_command() { //given - String command = "start"; + ChessGame chessGame = new ChessGame(); + Command command = new Command("start"); //when - ChessGame chessGame = new ChessGame(command); + chessGame.run(command); //then assertThat(chessGame.isRunning()).isTrue(); @@ -23,12 +25,13 @@ void create_start_command() { @Test @DisplayName("게임 종료 명령어를 입력받을 경우 게임을 종료한다.") - void create_end_command() { + void run_end_command() { //given - String command = "end"; + ChessGame chessGame = new ChessGame(); + Command command = new Command("end"); //when - ChessGame chessGame = new ChessGame(command); + chessGame.run(command); //then assertThat(chessGame.isRunning()).isFalse(); @@ -36,11 +39,12 @@ void create_end_command() { @Test @DisplayName("유효하지 않은 명령어를 입력받을 경우 예외를 던진다.") - void create_invalid_command() { + void run_invalid_command() { //given - String command = "invalid_command"; + ChessGame chessGame = new ChessGame(); + Command command = new Command("invalid_command"); //when, then - assertThrows(UnsupportedOperationException.class, () -> new ChessGame(command)); + assertThrows(UnsupportedOperationException.class, () -> chessGame.run(command)); } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 2cbcee9..8e06054 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,9 +1,12 @@ package chess.domain.board; +import chess.domain.command.MoveParameters; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class BoardTest { @@ -21,4 +24,43 @@ void create(String key, boolean expected) { //then assertThat(board.isEmpty(position)).isEqualTo(expected); } + + @Test + @DisplayName("인자로 받은 시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") + void move_invalid_source_position() { + //given + Board board = new Board(); + Position source = Position.from("b3"); + Position target = Position.from("b4"); + MoveParameters moveParameters = new MoveParameters(source, target); + + //when, then + assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, true)); + } + + @Test + @DisplayName("인자로 받은 색상과 일치하지 않을 경우 예외가 발생한다.") + void move_different_color() { + //given + Board board = new Board(); + Position source = Position.from("b2"); + Position target = Position.from("b3"); + MoveParameters moveParameters = new MoveParameters(source, target); + + //when, then + assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, false)); + } + + @Test + @DisplayName("인자로 받은 시작과 도착 위치의 기물이 같은 색상일 경우 예외가 발생한다.") + void move_source_and_destination_same_color() { + //given + Board board = new Board(); + Position source = Position.from("a1"); + Position target = Position.from("a2"); + MoveParameters moveParameters = new MoveParameters(source, target); + + //when, then + assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, true)); + } } From 22d29d1f454a5e2930c2ce21db3dcd249c099563 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 20:42:06 +0900 Subject: [PATCH 08/38] =?UTF-8?q?feat:=20=EB=B9=84=EC=88=8D=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- src/main/java/chess/domain/board/Board.java | 7 +++ src/main/java/chess/domain/board/File.java | 21 +++++++ .../java/chess/domain/board/Position.java | 24 +++++++- src/main/java/chess/domain/board/Rank.java | 19 ++++++ .../chess/domain/command/MoveParameters.java | 2 +- src/main/java/chess/domain/piece/Bishop.java | 34 +++++++++++ .../java/chess/domain/piece/Direction.java | 53 +++++++++++++++++ src/main/java/chess/domain/piece/King.java | 9 +++ src/main/java/chess/domain/piece/Knight.java | 9 +++ src/main/java/chess/domain/piece/Pawn.java | 9 +++ src/main/java/chess/domain/piece/Piece.java | 6 ++ src/main/java/chess/domain/piece/Queen.java | 9 +++ src/main/java/chess/domain/piece/Rook.java | 9 +++ .../java/chess/domain/board/BoardTest.java | 14 ++--- .../java/chess/domain/board/PositionTest.java | 2 +- .../java/chess/domain/piece/BishopTest.java | 59 +++++++++++++++++++ .../chess/domain/piece/DirectionTest.java | 21 +++++++ 18 files changed, 298 insertions(+), 12 deletions(-) create mode 100644 src/main/java/chess/domain/piece/Direction.java create mode 100644 src/test/java/chess/domain/piece/BishopTest.java create mode 100644 src/test/java/chess/domain/piece/DirectionTest.java diff --git a/README.md b/README.md index 1e86af3..7bbc323 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ - 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 (진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다.) - [] 비숍 (3) - - 모든 대각선 방향으로 원하는 만큼 이동 가능 + - [x] 모든 대각선 방향으로 원하는 만큼 이동 가능 + - [x] ERROR : 비숍 이동 패턴으로 이동할 수 없는 위치일 경우 - [] 퀸 (9) - 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index ed9e999..b825f4c 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class Board { private final Map board = new HashMap<>(); @@ -45,6 +46,11 @@ private void initializePawns() { public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) { Position source = moveParameters.getSource(); Position target = moveParameters.getTarget(); + + if (source.equals(target)) { + throw new IllegalArgumentException("출발 위치와 도착 위치가 같을 수 없습니다."); + } + Piece sourcePiece = findBy(source); validateColor(isWhiteTurn, sourcePiece.isWhite()); @@ -54,6 +60,7 @@ public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); } + Set passingPositions = sourcePiece.findPaths(source, target); board.put(target, sourcePiece); board.remove(source); } diff --git a/src/main/java/chess/domain/board/File.java b/src/main/java/chess/domain/board/File.java index c30eed7..2cb247b 100644 --- a/src/main/java/chess/domain/board/File.java +++ b/src/main/java/chess/domain/board/File.java @@ -1,5 +1,7 @@ package chess.domain.board; +import java.util.Arrays; + public enum File { a(1), b(2), @@ -16,7 +18,26 @@ public enum File { this.index = index; } + public static File of(final int fileIndex) { + return Arrays.stream(File.values()) + .filter(file -> hasSameIndex(fileIndex, file)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } + + private static boolean hasSameIndex(final int fileIndex, final File file) { + return file.getIndex() == fileIndex; + } + public int getIndex() { return index; } + + public int calculateGap(final File file) { + return this.index - file.index; + } + + public File add(final int amount) { + return File.of(this.index + amount); + } } diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index 8a2be56..b2f50dc 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -1,5 +1,7 @@ package chess.domain.board; +import chess.domain.piece.Direction; + import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -20,12 +22,12 @@ private static String createKey(final File file, final Rank rank) { return file.name() + rank.getIndex(); } - public static Position from(final String key) { + public static Position of(final String key) { return POSITIONS.get(key); } public static Position from(final File file, final Rank rank) { - return POSITIONS.get(createKey(file, rank)); + return of(createKey(file, rank)); } private final File file; @@ -39,4 +41,22 @@ private Position(final File file, final Rank rank) { public File getFile() { return file; } + + public Rank getRank() { + return rank; + } + + public int calculateFileGap(final Position source) { + return file.calculateGap(source.getFile()); + } + + public int calculateRankGap(final Position source) { + return rank.calculateGap(source.getRank()); + } + + public Position move(final Direction direction) { + File file = this.file.add(direction.getFile()); + Rank rank = this.rank.add(direction.getRank()); + return Position.from(file, rank); + } } diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java index 781d032..c76d31c 100644 --- a/src/main/java/chess/domain/board/Rank.java +++ b/src/main/java/chess/domain/board/Rank.java @@ -1,5 +1,7 @@ package chess.domain.board; +import java.util.Arrays; + public enum Rank { R8(8), R7(7), @@ -16,10 +18,27 @@ public enum Rank { this.index = index; } + public static Rank of(final int rankIndex) { + return Arrays.stream(Rank.values()) + .filter(rank -> hasSameIndex(rankIndex, rank)) + .findAny() + .orElseThrow(IllegalArgumentException::new); + } + + private static boolean hasSameIndex(final int rankIndex, final Rank rank) { + return rank.index == rankIndex; + } + public int getIndex() { return index; } + public int calculateGap(final Rank rank) { + return this.index - rank.index; + } + public Rank add(final int amount) { + return Rank.of(this.index + amount); + } } diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java index 44e0947..198b266 100644 --- a/src/main/java/chess/domain/command/MoveParameters.java +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -13,7 +13,7 @@ public class MoveParameters { private final Position target; public MoveParameters(final List parameters) { - this(Position.from(parameters.get(SOURCE_INDEX)), Position.from(parameters.get(TARGET_INDEX))); + this(Position.of(parameters.get(SOURCE_INDEX)), Position.of(parameters.get(TARGET_INDEX))); } public MoveParameters(final Position source, final Position target) { diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index f18d082..17ed760 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,8 +1,42 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.HashSet; +import java.util.Set; + public class Bishop extends Piece { public Bishop(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + int fileGap = target.calculateFileGap(source); + int rankGap = target.calculateRankGap(source); + validatePattern(fileGap, rankGap); + + Direction direction = Direction.of(fileGap, rankGap); + return collectPositions(source, target, direction); + } + + private Set collectPositions(final Position source, final Position target, final Direction direction) { + Set positions = new HashSet<>(); + Position current = source; + + while (!target.equals(current)) { + current = current.move(direction); + positions.add(current); + } + + positions.remove(target); + return positions; + } + + private void validatePattern(final int fileGap, final int rankGap) { + if (Math.abs(fileGap) != Math.abs(rankGap)) { + throw new IllegalArgumentException("비숍이 이동할 수 없는 위치입니다."); + } + } } diff --git a/src/main/java/chess/domain/piece/Direction.java b/src/main/java/chess/domain/piece/Direction.java new file mode 100644 index 0000000..b42d0fb --- /dev/null +++ b/src/main/java/chess/domain/piece/Direction.java @@ -0,0 +1,53 @@ +package chess.domain.piece; + +public enum Direction { + NORTH_EAST(1, 1), + SOUTH_EAST(1, -1), + NORTH_WEST(-1, 1), + SOUTH_WEST(-1, -1), + NORTH(0, 1), + SOUTH(0, -1), + EAST(1, 0), + WEST(-1, 0); + + private final int file; + private final int rank; + + Direction(final int file, final int rank) { + this.file = file; + this.rank = rank; + } + + public static Direction of(final int fileGap, final int rankGap) { + if (fileGap > 0 && rankGap > 0) { + return NORTH_EAST; + } + if (fileGap > 0 && rankGap < 0) { + return SOUTH_EAST; + } + if (fileGap < 0 && rankGap > 0) { + return NORTH_WEST; + } + if (fileGap < 0 && rankGap < 0) { + return SOUTH_WEST; + } + if (fileGap > 0) { + return EAST; + } + if (fileGap < 0) { + return WEST; + } + if (rankGap > 0) { + return NORTH; + } + return SOUTH; + } + + public int getFile() { + return file; + } + + public int getRank() { + return rank; + } +} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 957ae20..013b5b7 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -1,8 +1,17 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + public class King extends Piece { public King(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + return null; + } } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index 1536a29..f541a52 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,8 +1,17 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + public class Knight extends Piece { public Knight(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + return null; + } } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index 4f3b3fb..2b373a3 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,8 +1,17 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + public class Pawn extends Piece { public Pawn(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + return null; + } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index c704e19..f5178ce 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -1,5 +1,9 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + import static chess.domain.piece.Color.WHITE; public abstract class Piece { @@ -16,4 +20,6 @@ public boolean isWhite() { public boolean hasSameColor(final Piece target) { return this.color == target.color; } + + public abstract Set findPaths(final Position source, final Position target); } diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index a331082..dda7cfc 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -1,8 +1,17 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + public class Queen extends Piece { public Queen(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + return null; + } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index d9eb8a6..9691c13 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,8 +1,17 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Set; + public class Rook extends Piece { public Rook(final Color color) { super(color); } + + @Override + public Set findPaths(final Position source, final Position target) { + return null; + } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 8e06054..e41d19d 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -16,7 +16,7 @@ public class BoardTest { @DisplayName("객체를 생성한다.") void create(String key, boolean expected) { //given - Position position = Position.from(key); + Position position = Position.of(key); //when Board board = new Board(); @@ -30,8 +30,8 @@ void create(String key, boolean expected) { void move_invalid_source_position() { //given Board board = new Board(); - Position source = Position.from("b3"); - Position target = Position.from("b4"); + Position source = Position.of("b3"); + Position target = Position.of("b4"); MoveParameters moveParameters = new MoveParameters(source, target); //when, then @@ -43,8 +43,8 @@ void move_invalid_source_position() { void move_different_color() { //given Board board = new Board(); - Position source = Position.from("b2"); - Position target = Position.from("b3"); + Position source = Position.of("b2"); + Position target = Position.of("b3"); MoveParameters moveParameters = new MoveParameters(source, target); //when, then @@ -56,8 +56,8 @@ void move_different_color() { void move_source_and_destination_same_color() { //given Board board = new Board(); - Position source = Position.from("a1"); - Position target = Position.from("a2"); + Position source = Position.of("a1"); + Position target = Position.of("a2"); MoveParameters moveParameters = new MoveParameters(source, target); //when, then diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java index ad1ef0a..e30fada 100644 --- a/src/test/java/chess/domain/board/PositionTest.java +++ b/src/test/java/chess/domain/board/PositionTest.java @@ -31,7 +31,7 @@ void from_key() { String key = file.name() + rank.getIndex(); //when - Position position = Position.from(key); + Position position = Position.of(key); //then assertThat(position).extracting("file", "rank") diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java new file mode 100644 index 0000000..e9acbdb --- /dev/null +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -0,0 +1,59 @@ +package chess.domain.piece; + +import chess.domain.board.File; +import chess.domain.board.Position; +import chess.domain.board.Rank; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.groups.Tuple.tuple; + +class BishopTest { + + @ParameterizedTest + @MethodSource("createParameters") + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void get_passing_positions_success(String targetPosition, Tuple expected) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Bishop(Color.WHITE); + + //when + Set passingPositions = piece.findPaths(source, target); + + //then + assertThat(passingPositions).extracting("file", "rank") + .containsOnly(expected); + } + + @Test + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void get_passing_positions_invalid_gap() { + //given + Position source = Position.of("c1"); + Position target = Position.of("f5"); + Piece piece = new Bishop(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } + + private static Stream createParameters() { + return Stream.of( + Arguments.of("b6", tuple(File.c, Rank.R5)), + Arguments.of("b2", tuple(File.c, Rank.R3)), + Arguments.of("f2", tuple(File.e, Rank.R3)), + Arguments.of("f6", tuple(File.e, Rank.R5)) + ); + } +} diff --git a/src/test/java/chess/domain/piece/DirectionTest.java b/src/test/java/chess/domain/piece/DirectionTest.java new file mode 100644 index 0000000..14f355a --- /dev/null +++ b/src/test/java/chess/domain/piece/DirectionTest.java @@ -0,0 +1,21 @@ +package chess.domain.piece; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class DirectionTest { + + @ParameterizedTest + @CsvSource({"1,0,EAST", "-1,0,WEST", "0,1,NORTH", "0,-1,SOUTH", "1,1,NORTH_EAST", "1,-1,SOUTH_EAST", "-1,1,NORTH_WEST", "-1,-1,SOUTH_WEST"}) + @DisplayName("방향을 찾는다.") + void find_direction(int fileGap, int rankGap, Direction expected) { + //given, when + Direction direction = Direction.of(fileGap, rankGap); + + //then + assertThat(direction).isEqualTo(expected); + } +} From c877ccd87c7112c5be967d833bed6472a0355391 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 21:04:37 +0900 Subject: [PATCH 09/38] =?UTF-8?q?feat:=20=EB=A3=A9=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=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 --- README.md | 5 +- src/main/java/chess/domain/piece/Bishop.java | 31 +--------- src/main/java/chess/domain/piece/King.java | 8 +-- src/main/java/chess/domain/piece/Knight.java | 8 +-- src/main/java/chess/domain/piece/Pawn.java | 8 +-- src/main/java/chess/domain/piece/Piece.java | 33 ++++++++++- src/main/java/chess/domain/piece/Queen.java | 8 +-- src/main/java/chess/domain/piece/Rook.java | 10 ++-- .../java/chess/domain/piece/BishopTest.java | 8 +-- .../java/chess/domain/piece/RookTest.java | 59 +++++++++++++++++++ 10 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 src/test/java/chess/domain/piece/RookTest.java diff --git a/README.md b/README.md index 7bbc323..3353ecc 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,9 @@ - 적 방향 직선 1칸 이동 (처음 이동 시에는 2칸 이동 가능) - 공격 : 적 방향 좌, 우 대각선 1칸 -- [] 룩 (5) - - 모든 직선 방향으로 원하는 만큼 이동 가능 +- [x] 룩 (5) + - [x] 모든 직선 방향으로 원하는 만큼 이동 가능 + - [x] ERROR : 룩 이동 패턴으로 이동할 수 없는 위치일 경우 - [] 나이트 (2.5) - 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 (진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다.) diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 17ed760..46ab8f3 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,10 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.HashSet; -import java.util.Set; - public class Bishop extends Piece { public Bishop(final Color color) { @@ -12,30 +7,8 @@ public Bishop(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - int fileGap = target.calculateFileGap(source); - int rankGap = target.calculateRankGap(source); - validatePattern(fileGap, rankGap); - - Direction direction = Direction.of(fileGap, rankGap); - return collectPositions(source, target, direction); - } - - private Set collectPositions(final Position source, final Position target, final Direction direction) { - Set positions = new HashSet<>(); - Position current = source; - - while (!target.equals(current)) { - current = current.move(direction); - positions.add(current); - } - - positions.remove(target); - return positions; - } - - private void validatePattern(final int fileGap, final int rankGap) { - if (Math.abs(fileGap) != Math.abs(rankGap)) { + protected void validatePattern(final int fileGap, final int rankGap) { + if (!isDiagonal(fileGap, rankGap)) { throw new IllegalArgumentException("비숍이 이동할 수 없는 위치입니다."); } } diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 013b5b7..cbe91fd 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -1,9 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Set; - public class King extends Piece { public King(final Color color) { @@ -11,7 +7,7 @@ public King(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - return null; + protected void validatePattern(final int fileGap, final int rankGap) { + } } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index f541a52..7482eed 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,9 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Set; - public class Knight extends Piece { public Knight(final Color color) { @@ -11,7 +7,7 @@ public Knight(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - return null; + protected void validatePattern(final int fileGap, final int rankGap) { + } } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index 2b373a3..d26db1f 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,9 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Set; - public class Pawn extends Piece { public Pawn(final Color color) { @@ -11,7 +7,7 @@ public Pawn(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - return null; + protected void validatePattern(final int fileGap, final int rankGap) { + } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index f5178ce..43ca054 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -2,6 +2,7 @@ import chess.domain.board.Position; +import java.util.HashSet; import java.util.Set; import static chess.domain.piece.Color.WHITE; @@ -13,6 +14,8 @@ public abstract class Piece { this.color = color; } + protected abstract void validatePattern(final int fileGap, final int rankGap); + public boolean isWhite() { return WHITE == color; } @@ -21,5 +24,33 @@ public boolean hasSameColor(final Piece target) { return this.color == target.color; } - public abstract Set findPaths(final Position source, final Position target); + public Set findPaths(final Position source, final Position target) { + int fileGap = target.calculateFileGap(source); + int rankGap = target.calculateRankGap(source); + validatePattern(fileGap, rankGap); + + Direction direction = Direction.of(fileGap, rankGap); + return collectPositions(source, target, direction); + } + + protected boolean isStraight(final int fileGap, final int rankGap) { + return fileGap == 0 || rankGap == 0; + } + + protected boolean isDiagonal(final int fileGap, final int rankGap) { + return Math.abs(fileGap) == Math.abs(rankGap); + } + + public Set collectPositions(final Position source, final Position target, final Direction direction) { + Set positions = new HashSet<>(); + Position current = source; + + while (!target.equals(current)) { + current = current.move(direction); + positions.add(current); + } + + positions.remove(target); + return positions; + } } diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index dda7cfc..0ca452f 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -1,9 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Set; - public class Queen extends Piece { public Queen(final Color color) { @@ -11,7 +7,7 @@ public Queen(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - return null; + protected void validatePattern(final int fileGap, final int rankGap) { + } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index 9691c13..31c3e7a 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,9 +1,5 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Set; - public class Rook extends Piece { public Rook(final Color color) { @@ -11,7 +7,9 @@ public Rook(final Color color) { } @Override - public Set findPaths(final Position source, final Position target) { - return null; + protected void validatePattern(final int fileGap, final int rankGap) { + if (!isStraight(fileGap, rankGap)) { + throw new IllegalArgumentException("룩이 이동할 수 없는 위치입니다."); + } } } diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index e9acbdb..2cd3c0d 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -22,23 +22,23 @@ class BishopTest { @ParameterizedTest @MethodSource("createParameters") @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void get_passing_positions_success(String targetPosition, Tuple expected) { + void find_paths_success(String targetPosition, Tuple expected) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new Bishop(Color.WHITE); //when - Set passingPositions = piece.findPaths(source, target); + Set paths = piece.findPaths(source, target); //then - assertThat(passingPositions).extracting("file", "rank") + assertThat(paths).extracting("file", "rank") .containsOnly(expected); } @Test @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") - void get_passing_positions_invalid_gap() { + void find_paths_invalid_target() { //given Position source = Position.of("c1"); Position target = Position.of("f5"); diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java new file mode 100644 index 0000000..2e564e3 --- /dev/null +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -0,0 +1,59 @@ +package chess.domain.piece; + +import chess.domain.board.File; +import chess.domain.board.Position; +import chess.domain.board.Rank; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.groups.Tuple.tuple; + +public class RookTest { + + @ParameterizedTest + @MethodSource("createParameters") + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success(String targetPosition, Tuple expected) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Rook(Color.WHITE); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).extracting("file", "rank") + .containsOnly(expected); + } + + @Test + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void find_paths_invalid_target() { + //given + Position source = Position.of("c1"); + Position target = Position.of("f5"); + Piece piece = new Rook(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } + + private static Stream createParameters() { + return Stream.of( + Arguments.of("d2", tuple(File.d, Rank.R3)), + Arguments.of("d6", tuple(File.d, Rank.R5)), + Arguments.of("b4", tuple(File.c, Rank.R4)), + Arguments.of("f4", tuple(File.e, Rank.R4)) + ); + } +} From adc18b63f9204b159f4696850c5a8d3d3b7becb0 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 21:24:19 +0900 Subject: [PATCH 10/38] =?UTF-8?q?feat:=20=ED=80=B8=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=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 --- README.md | 7 +- src/main/java/chess/domain/piece/Piece.java | 7 +- src/main/java/chess/domain/piece/Queen.java | 4 +- .../java/chess/domain/piece/QueenTest.java | 76 +++++++++++++++++++ 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/test/java/chess/domain/piece/QueenTest.java diff --git a/README.md b/README.md index 3353ecc..ba3a871 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,13 @@ - [] 나이트 (2.5) - 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 (진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다.) -- [] 비숍 (3) +- [x] 비숍 (3) - [x] 모든 대각선 방향으로 원하는 만큼 이동 가능 - [x] ERROR : 비숍 이동 패턴으로 이동할 수 없는 위치일 경우 -- [] 퀸 (9) - - 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) +- [x] 퀸 (9) + - [x] 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) + - [x] ERROR : 퀸 이동 패턴으로 이동할 수 없는 위치일 경우 - [] 킹 - 모든 방향 1칸 이동 (상대의 공격 범위로는 이동 불가능) diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 43ca054..b45f853 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -6,6 +6,7 @@ import java.util.Set; import static chess.domain.piece.Color.WHITE; +import static java.lang.Math.abs; public abstract class Piece { private final Color color; @@ -37,8 +38,12 @@ protected boolean isStraight(final int fileGap, final int rankGap) { return fileGap == 0 || rankGap == 0; } + protected boolean isFiniteStraight(final int fileGap, final int rankGap) { + return (abs(fileGap) + abs(rankGap)) == 1; + } + protected boolean isDiagonal(final int fileGap, final int rankGap) { - return Math.abs(fileGap) == Math.abs(rankGap); + return abs(fileGap) == abs(rankGap); } public Set collectPositions(final Position source, final Position target, final Direction direction) { diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 0ca452f..8980158 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -8,6 +8,8 @@ public Queen(final Color color) { @Override protected void validatePattern(final int fileGap, final int rankGap) { - + if (!isDiagonal(fileGap, rankGap) && !isFiniteStraight(fileGap, rankGap)) { + throw new IllegalArgumentException("퀸이 이동할 수 없는 위치입니다."); + } } } diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java new file mode 100644 index 0000000..f5d51ab --- /dev/null +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -0,0 +1,76 @@ +package chess.domain.piece; + +import chess.domain.board.File; +import chess.domain.board.Position; +import chess.domain.board.Rank; +import org.assertj.core.groups.Tuple; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.groups.Tuple.tuple; + +class QueenTest { + + @ParameterizedTest + @MethodSource("createParametersForDiagonal") + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success_diagonal(String targetPosition, Tuple expected) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Queen(Color.WHITE); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).extracting("file", "rank") + .containsOnly(expected); + } + + @ParameterizedTest + @ValueSource(strings = {"d5", "c4", "e4", "d3"}) + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success_straight(String targetPosition) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Queen(Color.WHITE); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"d6", "d2", "b4", "f4", "f5"}) + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void find_paths_invalid_target(String invalidTarget) { + //given + Position source = Position.of("d4"); + Position target = Position.of(invalidTarget); + Piece piece = new Queen(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } + + private static Stream createParametersForDiagonal() { + return Stream.of( + Arguments.of("b6", tuple(File.c, Rank.R5)), + Arguments.of("b2", tuple(File.c, Rank.R3)), + Arguments.of("f2", tuple(File.e, Rank.R3)), + Arguments.of("f6", tuple(File.e, Rank.R5)) + ); + } +} From f3c877a28fc48bd3c0119fdc6122ad30b7705457 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 21:33:37 +0900 Subject: [PATCH 11/38] =?UTF-8?q?feat:=20=ED=82=B9=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=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 --- README.md | 6 ++- src/main/java/chess/domain/piece/King.java | 4 +- src/main/java/chess/domain/piece/Piece.java | 10 +++-- .../java/chess/domain/piece/KingTest.java | 43 +++++++++++++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/test/java/chess/domain/piece/KingTest.java diff --git a/README.md b/README.md index ba3a871..8f93cba 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,10 @@ - [x] 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) - [x] ERROR : 퀸 이동 패턴으로 이동할 수 없는 위치일 경우 -- [] 킹 - - 모든 방향 1칸 이동 (상대의 공격 범위로는 이동 불가능) +- [x] 킹 + - [x] 모든 방향 1칸 이동 + - [x] ERROR : 킹 이동 패턴으로 이동할 수 없는 위치일 경우 + - [ ] 상대의 공격 범위로는 이동 불가능 ## Advanced diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index cbe91fd..e5c8d08 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -8,6 +8,8 @@ public King(final Color color) { @Override protected void validatePattern(final int fileGap, final int rankGap) { - + if (!isFiniteStraight(fileGap, rankGap) && !isFiniteDiagonal(fileGap, rankGap)) { + throw new IllegalArgumentException("킹이 이동할 수 없는 위치입니다."); + } } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index b45f853..26ad653 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -28,7 +28,7 @@ public boolean hasSameColor(final Piece target) { public Set findPaths(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); - validatePattern(fileGap, rankGap); + validatePattern(abs(fileGap), abs(rankGap)); Direction direction = Direction.of(fileGap, rankGap); return collectPositions(source, target, direction); @@ -39,11 +39,15 @@ protected boolean isStraight(final int fileGap, final int rankGap) { } protected boolean isFiniteStraight(final int fileGap, final int rankGap) { - return (abs(fileGap) + abs(rankGap)) == 1; + return (fileGap + rankGap) == 1; } protected boolean isDiagonal(final int fileGap, final int rankGap) { - return abs(fileGap) == abs(rankGap); + return fileGap == rankGap; + } + + protected boolean isFiniteDiagonal(final int fileGap, final int rankGap) { + return (fileGap == 1) && (rankGap == 1); } public Set collectPositions(final Position source, final Position target, final Direction direction) { diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java new file mode 100644 index 0000000..abc6459 --- /dev/null +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -0,0 +1,43 @@ +package chess.domain.piece; + +import chess.domain.board.Position; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class KingTest { + + @ParameterizedTest + @ValueSource(strings = {"c3", "c4", "c5", "e3", "e4", "e5", "d3", "d5"}) + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success_straight(String targetPosition) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new King(Color.WHITE); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"c2", "d2", "e2", "f2", "b3", "b4", "b5"}) + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void find_paths_invalid_target(String invalidTarget) { + //given + Position source = Position.of("d4"); + Position target = Position.of(invalidTarget); + Piece piece = new King(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } +} From 12da4ca377bc27ec3af1ecad283841ed06ebc05b Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Tue, 17 Aug 2021 21:46:27 +0900 Subject: [PATCH 12/38] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++- src/main/java/chess/domain/piece/Knight.java | 21 +++++++++ .../java/chess/domain/piece/KnightTest.java | 43 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/test/java/chess/domain/piece/KnightTest.java diff --git a/README.md b/README.md index 8f93cba..dccfb2d 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,10 @@ - [x] 모든 직선 방향으로 원하는 만큼 이동 가능 - [x] ERROR : 룩 이동 패턴으로 이동할 수 없는 위치일 경우 -- [] 나이트 (2.5) - - 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 (진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다.) +- [x] 나이트 (2.5) + - [x] 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 + - [x] ERROR : 나이트 이동 패턴으로 이동할 수 없는 위치일 경우 + - [x] 진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다. - [x] 비숍 (3) - [x] 모든 대각선 방향으로 원하는 만큼 이동 가능 diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index 7482eed..ad801f4 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,13 +1,34 @@ package chess.domain.piece; +import chess.domain.board.Position; + +import java.util.Collections; +import java.util.Set; + +import static java.lang.Math.abs; + public class Knight extends Piece { public Knight(final Color color) { super(color); } + @Override + public Set findPaths(final Position source, final Position target) { + int fileGap = target.calculateFileGap(source); + int rankGap = target.calculateRankGap(source); + validatePattern(abs(fileGap), abs(rankGap)); + return Collections.emptySet(); + } + @Override protected void validatePattern(final int fileGap, final int rankGap) { + if (!isKnightPattern(fileGap, rankGap)) { + throw new IllegalArgumentException("나이트가 이동할 수 없는 위치입니다."); + } + } + private boolean isKnightPattern(final int fileGap, final int rankGap) { + return (fileGap == 2 && rankGap == 1) || (fileGap == 1 && rankGap == 2); } } diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java new file mode 100644 index 0000000..6f8f198 --- /dev/null +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -0,0 +1,43 @@ +package chess.domain.piece; + +import chess.domain.board.Position; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class KnightTest { + + @ParameterizedTest + @ValueSource(strings = {"c6", "e6", "c2", "e2", "f5", "f3", "b5", "b3"}) + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success_diagonal(String targetPosition) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Knight(Color.WHITE); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = {"c3", "c4", "c5", "e3", "e4", "e5", "d3", "d5"}) + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void find_paths_invalid_target(String invalidTarget) { + //given + Position source = Position.of("d4"); + Position target = Position.of(invalidTarget); + Piece piece = new Knight(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } +} From 44ecf395731fb8ddb88607a3e06340c2b85c7d5c Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Thu, 19 Aug 2021 14:58:41 +0900 Subject: [PATCH 13/38] =?UTF-8?q?feat:=20=ED=8F=B0=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=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 --- README.md | 13 ++- .../java/chess/domain/board/Position.java | 4 + src/main/java/chess/domain/piece/Bishop.java | 2 +- .../java/chess/domain/piece/Direction.java | 40 +++---- src/main/java/chess/domain/piece/King.java | 2 +- src/main/java/chess/domain/piece/Knight.java | 3 +- src/main/java/chess/domain/piece/Pawn.java | 77 ++++++++++++- src/main/java/chess/domain/piece/Piece.java | 7 +- src/main/java/chess/domain/piece/Queen.java | 2 +- src/main/java/chess/domain/piece/Rook.java | 2 +- .../java/chess/domain/piece/KingTest.java | 2 +- .../java/chess/domain/piece/KnightTest.java | 2 +- .../java/chess/domain/piece/PawnTest.java | 104 ++++++++++++++++++ 13 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 src/test/java/chess/domain/piece/PawnTest.java diff --git a/README.md b/README.md index dccfb2d..17a5216 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,16 @@ ## 이동 규칙 -- [] 폰 (1 or 0.5) - - 적 방향 직선 1칸 이동 (처음 이동 시에는 2칸 이동 가능) - - 공격 : 적 방향 좌, 우 대각선 1칸 +- [x] 폰 (1 or 0.5) + - [x] 적 방향 직선 1칸 이동 + - [x] ERROR : 직선 방향 2칸 이상일 경우 + - [x] ERROR : 좌, 우 이동이 포함될 경우 + - [x] 처음 이동 시에는 2칸 이동 가능 + - [x] ERROR : 직선 방향 3칸 이상일 경우 + - [x] ERROR : 좌, 우 이동이 포함될 경우 + + - [x] 공격 : 적 방향 좌, 우 대각선 1칸 + - [x] ERROR : 직선 1칸 && 좌 또는 우 1칸이 아닐 경우 - [x] 룩 (5) - [x] 모든 직선 방향으로 원하는 만큼 이동 가능 diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index b2f50dc..f2acc7e 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -59,4 +59,8 @@ public Position move(final Direction direction) { Rank rank = this.rank.add(direction.getRank()); return Position.from(file, rank); } + + public boolean hasSameRank(final Rank rank) { + return this.rank == rank; + } } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 46ab8f3..94c96c8 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -7,7 +7,7 @@ public Bishop(final Color color) { } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + protected void validatePattern(int fileGap, int rankGap) { if (!isDiagonal(fileGap, rankGap)) { throw new IllegalArgumentException("비숍이 이동할 수 없는 위치입니다."); } diff --git a/src/main/java/chess/domain/piece/Direction.java b/src/main/java/chess/domain/piece/Direction.java index b42d0fb..5ea3d1a 100644 --- a/src/main/java/chess/domain/piece/Direction.java +++ b/src/main/java/chess/domain/piece/Direction.java @@ -1,5 +1,7 @@ package chess.domain.piece; +import java.util.Arrays; + public enum Direction { NORTH_EAST(1, 1), SOUTH_EAST(1, -1), @@ -10,6 +12,8 @@ public enum Direction { EAST(1, 0), WEST(-1, 0); + private static final int ZERO = 0; + private final int file; private final int rank; @@ -19,28 +23,20 @@ public enum Direction { } public static Direction of(final int fileGap, final int rankGap) { - if (fileGap > 0 && rankGap > 0) { - return NORTH_EAST; - } - if (fileGap > 0 && rankGap < 0) { - return SOUTH_EAST; - } - if (fileGap < 0 && rankGap > 0) { - return NORTH_WEST; - } - if (fileGap < 0 && rankGap < 0) { - return SOUTH_WEST; - } - if (fileGap > 0) { - return EAST; - } - if (fileGap < 0) { - return WEST; - } - if (rankGap > 0) { - return NORTH; - } - return SOUTH; + int fileSign = Integer.compare(fileGap, ZERO); + int rankSign = Integer.compare(rankGap, ZERO); + return findByFileAndRankSign(fileSign, rankSign); + } + + private static Direction findByFileAndRankSign(final int fileSign, final int rankSign) { + return Arrays.stream(Direction.values()) + .filter(direction -> direction.hasSameSigns(fileSign, rankSign)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("매칭되는 방향이 없습니다.")); + } + + private boolean hasSameSigns(final int fileSign, final int rankSign) { + return this.file == fileSign && this.rank == rankSign; } public int getFile() { diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index e5c8d08..89ffd0b 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -7,7 +7,7 @@ public King(final Color color) { } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + protected void validatePattern(int fileGap, int rankGap) { if (!isFiniteStraight(fileGap, rankGap) && !isFiniteDiagonal(fileGap, rankGap)) { throw new IllegalArgumentException("킹이 이동할 수 없는 위치입니다."); } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index ad801f4..c94bae7 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -18,11 +18,12 @@ public Set findPaths(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); validatePattern(abs(fileGap), abs(rankGap)); + return Collections.emptySet(); } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + protected void validatePattern(int fileGap, int rankGap) { if (!isKnightPattern(fileGap, rankGap)) { throw new IllegalArgumentException("나이트가 이동할 수 없는 위치입니다."); } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index d26db1f..cf6ed66 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,13 +1,88 @@ package chess.domain.piece; +import chess.domain.board.Position; +import chess.domain.board.Rank; + +import java.util.Set; + +import static java.lang.Math.abs; + public class Pawn extends Piece { + private static final int VALID_GAP_ON_ATTACK = 1; + private static final int MINIMUM_MOVE_COUNT = 1; + private static final int MAXIMUM_MOVE_COUNT = 2; + private static final Rank WHITE_INITIAL_RANK = Rank.of(2); + private static final Rank BLACK_INITIAL_RANK = Rank.of(7); + + private boolean isInitialMove = true; + public Pawn(final Color color) { super(color); } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + public Set findPaths(final Position source, final Position target) { + setInitialMoveFlag(source); + + int fileGap = target.calculateFileGap(source); + int rankGap = target.calculateRankGap(source); + validatePattern(fileGap, rankGap); + + Direction direction = Direction.of(fileGap, rankGap); + return collectPositions(source, target, direction); + } + + private void setInitialMoveFlag(final Position source) { + if (isWhite() && !source.hasSameRank(WHITE_INITIAL_RANK)) { + isInitialMove = false; + } + + if (isBlack() && !source.hasSameRank(BLACK_INITIAL_RANK)) { + isInitialMove = false; + } + } + + @Override + protected void validatePattern(int fileGap, int rankGap) { + validateForward(rankGap); + + fileGap = abs(fileGap); + rankGap = abs(rankGap); + validateMoveCount(rankGap, fileGap); + } + + private void validateForward(final int rankGap) { + if (isWhite() && rankGap < MINIMUM_MOVE_COUNT) { + throw new IllegalArgumentException(); + } + + if (isBlack() && rankGap > MINIMUM_MOVE_COUNT * -1) { + throw new IllegalArgumentException(); + } + } + + private void validateMoveCount(final int rankGap, final int fileGap) { + if (fileGap != 0) { + validateAttackMoveCount(rankGap, fileGap); + } + + validateForwardMoveCount(rankGap); + } + + private void validateAttackMoveCount(final int rankGap, final int fileGap) { + if (rankGap > VALID_GAP_ON_ATTACK || fileGap > VALID_GAP_ON_ATTACK) { + throw new IllegalArgumentException(); + } + } + + private void validateForwardMoveCount(final int rankGap) { + if (isInitialMove && rankGap > MAXIMUM_MOVE_COUNT) { + throw new IllegalArgumentException(); + } + if (!isInitialMove && rankGap > MINIMUM_MOVE_COUNT) { + throw new IllegalArgumentException(); + } } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 26ad653..17848d2 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.Set; +import static chess.domain.piece.Color.BLACK; import static chess.domain.piece.Color.WHITE; import static java.lang.Math.abs; @@ -18,7 +19,11 @@ public abstract class Piece { protected abstract void validatePattern(final int fileGap, final int rankGap); public boolean isWhite() { - return WHITE == color; + return color == WHITE; + } + + public boolean isBlack() { + return color == BLACK; } public boolean hasSameColor(final Piece target) { diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 8980158..1219815 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -7,7 +7,7 @@ public Queen(final Color color) { } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + protected void validatePattern(int fileGap, int rankGap) { if (!isDiagonal(fileGap, rankGap) && !isFiniteStraight(fileGap, rankGap)) { throw new IllegalArgumentException("퀸이 이동할 수 없는 위치입니다."); } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index 31c3e7a..a09cf42 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -7,7 +7,7 @@ public Rook(final Color color) { } @Override - protected void validatePattern(final int fileGap, final int rankGap) { + protected void validatePattern(int fileGap, int rankGap) { if (!isStraight(fileGap, rankGap)) { throw new IllegalArgumentException("룩이 이동할 수 없는 위치입니다."); } diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java index abc6459..30c8686 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -15,7 +15,7 @@ class KingTest { @ParameterizedTest @ValueSource(strings = {"c3", "c4", "c5", "e3", "e4", "e5", "d3", "d5"}) @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success_straight(String targetPosition) { + void find_paths_success(String targetPosition) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java index 6f8f198..89a7b3f 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -15,7 +15,7 @@ class KnightTest { @ParameterizedTest @ValueSource(strings = {"c6", "e6", "c2", "e2", "f5", "f3", "b5", "b3"}) @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success_diagonal(String targetPosition) { + void find_paths_success(String targetPosition) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java new file mode 100644 index 0000000..ba8a55d --- /dev/null +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -0,0 +1,104 @@ +package chess.domain.piece; + +import chess.domain.board.Position; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class PawnTest { + + @ParameterizedTest + @CsvSource({"b2, b3, WHITE", "b2, a3, WHITE", "b2, c3, WHITE", + "b7, b6, BLACK", "b7, a6, BLACK", "b7, c6, BLACK"}) + @DisplayName("최초 이동 시 1칸 전진한다.") + void find_paths_success_move_count_one_on_initial_move(String sourcePosition, String targetPosition, Color color) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).isEmpty(); + } + + @ParameterizedTest + @CsvSource({"b2, b4, WHITE, b3", "b7, b5, BLACK, b6"}) + @DisplayName("최초 이동시 2칸 전진하면 지나가는 경로를 반환한다.") + void find_paths_success_move_count_two_on_initial_move(String sourcePosition, String targetPosition, Color color, String expected) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).containsOnly(Position.of(expected)); + } + + @ParameterizedTest + @CsvSource({"d4, d5, WHITE", "d4, c5, WHITE", "d4, e5, WHITE", + "d4, d3, BLACK", "d4, c3, BLACK", "d4, e3, BLACK"}) + @DisplayName("최초 이동이 아닌 경우 1칸 전진한다.") + void find_paths_success_move_count_one(String sourcePosition, String targetPosition, Color color) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when + Set paths = piece.findPaths(source, target); + + //then + assertThat(paths).isEmpty(); + } + + @ParameterizedTest + @CsvSource({"d2, d5, WHITE", "d7, d4, BLACK"}) + @DisplayName("최초 이동 시 2칸 초과 전진하면 예외가 발생한다.") + void find_paths_fail_move_invalid_count_on_initial_move(String sourcePosition, String targetPosition, Color color) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } + + @ParameterizedTest + @CsvSource({"d4, d6, WHITE", "d4, c6, WHITE", "d4, e6, WHITE", + "d4, d2, BLACK", "d4, c2, BLACK", "d4, e2, BLACK"}) + @DisplayName("최초 이동이 아닌 경우 1칸 초과 전진하면 예외가 발생한다.") + void find_paths_fail_move_invalid_count(String sourcePosition, String targetPosition, Color color) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } + + @ParameterizedTest + @CsvSource({"d2, d1, WHITE", "d7, d8, BLACK"}) + @DisplayName("후진 시 예외가 발생한다.") + void find_paths_fail_move_backward(String sourcePosition, String targetPosition, Color color) { + //given + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Piece piece = new Pawn(color); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + } +} From 4498862ec143de1caf6910f665840f0691853ddf Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Thu, 19 Aug 2021 15:40:08 +0900 Subject: [PATCH 14/38] =?UTF-8?q?feat:=20=EC=B2=B4=EC=8A=A4=ED=8C=90?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=94=BC=EC=8A=A4=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++-- src/main/java/chess/domain/board/Board.java | 50 ++++++++++---- .../java/chess/domain/board/BoardTest.java | 68 +++++++++++++++---- 3 files changed, 95 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 17a5216..bde670b 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,12 @@ - [x] ERROR : 다른 색상일 경우 - [x] 시작과 도착 위치의 기물이 다른 색상인지 확인 - [x] ERROR : 같은 색상일 경우 - - [] source 위치에서 target 위치로 기물 이동 - - [] ERROR : target 위치에 같은 색상의 기물이 있을 경우 + - [x] source 위치에서 target 위치로 기물 이동 + - [x] ERROR : source 위치에 기물이 없는 경우 + - [x] ERROR : 자신의 기물이 아닌 경우 + - [x] ERROR : source, target 위치의 기물 색상이 같을 경우 + - [x] ERROR : source, target 위치가 같을 경우 + - [x] ERROR : 이동 경로에 기물이 존재할 경우 - [x] 기물(Piece) - [x] 색상 @@ -85,9 +89,3 @@ - [x] ERROR : 킹 이동 패턴으로 이동할 수 없는 위치일 경우 - [ ] 상대의 공격 범위로는 이동 불가능 -## Advanced - -체스판(Board) - -- [] 위치가 체스판 범위 내인지 확인 - - [] ERROR : 범위 밖일 경우 diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index b825f4c..d2cb3e9 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -46,28 +46,27 @@ private void initializePawns() { public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) { Position source = moveParameters.getSource(); Position target = moveParameters.getTarget(); - - if (source.equals(target)) { - throw new IllegalArgumentException("출발 위치와 도착 위치가 같을 수 없습니다."); - } - Piece sourcePiece = findBy(source); - validateColor(isWhiteTurn, sourcePiece.isWhite()); + validateOwner(isWhiteTurn, sourcePiece.isWhite()); + validateSamePosition(source, target); + validateTarget(target, sourcePiece); - Piece targetPiece = findBy(target); - if (sourcePiece.hasSameColor(targetPiece)) { - throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); - } + Set paths = sourcePiece.findPaths(source, target); + validatePathsEmpty(paths); - Set passingPositions = sourcePiece.findPaths(source, target); board.put(target, sourcePiece); board.remove(source); } - private void validateColor(final boolean expectedColor, final boolean sourcePieceColor) { - if (sourcePieceColor != expectedColor) { - throw new IllegalArgumentException("같은 색상의 기물만 움직일 수 있습니다."); + private void validateTarget(final Position target, final Piece sourcePiece) { + if (isEmpty(target)) { + return; + } + + Piece targetPiece = board.get(target); + if (sourcePiece.hasSameColor(targetPiece)) { + throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); } } @@ -80,6 +79,27 @@ public Piece findBy(final Position position) { } public boolean isEmpty(Position position) { - return board.get(position) == null; + return !board.containsKey(position); + } + + private void validateOwner(final boolean expectedColor, final boolean sourcePieceColor) { + if (sourcePieceColor != expectedColor) { + throw new IllegalArgumentException("자신의 기물만 움직일 수 있습니다."); + } + } + + private void validateSamePosition(final Position source, final Position target) { + if (source.equals(target)) { + throw new IllegalArgumentException("출발 위치와 도착 위치가 같을 수 없습니다."); + } + } + + private void validatePathsEmpty(final Set paths) { + boolean isPresent = paths.stream() + .anyMatch(board::containsKey); + + if (isPresent) { + throw new IllegalArgumentException("기물을 통과하여 이동할 수 없습니다."); + } } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index e41d19d..00af2a3 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -27,7 +27,7 @@ void create(String key, boolean expected) { @Test @DisplayName("인자로 받은 시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") - void move_invalid_source_position() { + void move_source_position_empty() { //given Board board = new Board(); Position source = Position.of("b3"); @@ -35,32 +35,72 @@ void move_invalid_source_position() { MoveParameters moveParameters = new MoveParameters(source, target); //when, then - assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, true)); + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, true)) + .withMessage("해당 위치에 기물이 존재하지 않습니다."); } - @Test - @DisplayName("인자로 받은 색상과 일치하지 않을 경우 예외가 발생한다.") - void move_different_color() { + @ParameterizedTest + @CsvSource({"b2, b3, false", "a7, a6, true"}) + @DisplayName("자신의 기물이 아닌 기물을 선택할 경우 예외가 발생한다.") + void move_source_not_owner(String sourcePosition, String targetPosition, boolean isWhiteTurn) { //given Board board = new Board(); - Position source = Position.of("b2"); - Position target = Position.of("b3"); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); MoveParameters moveParameters = new MoveParameters(source, target); //when, then - assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, false)); + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .withMessage("자신의 기물만 움직일 수 있습니다."); } - @Test - @DisplayName("인자로 받은 시작과 도착 위치의 기물이 같은 색상일 경우 예외가 발생한다.") - void move_source_and_destination_same_color() { + @ParameterizedTest + @CsvSource({"a1, a2, true", "a8, a7, false"}) + @DisplayName("시작과 도착 위치의 기물이 같은 색상일 경우 예외가 발생한다.") + void move_source_and_target_same_color(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + //given + Board board = new Board(); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + MoveParameters moveParameters = new MoveParameters(source, target); + + //when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .withMessage("같은 색상의 기물은 공격할 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"a1, a1, true", "a8, a8, false"}) + @DisplayName("시작과 도착 위치가 같을 경우 예외가 발생한다.") + void move_source_and_target_same(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + //given + Board board = new Board(); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + MoveParameters moveParameters = new MoveParameters(source, target); + + //when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .withMessage("출발 위치와 도착 위치가 같을 수 없습니다."); + } + + @ParameterizedTest + @CsvSource({"a1, a3, true", "a8, a6, false"}) + @DisplayName("경로에 다른 기물이 존재하는 경우 예외가 발생한다.") + void move_invalid_paths(String sourcePosition, String targetPosition, boolean isWhiteTurn) { //given Board board = new Board(); - Position source = Position.of("a1"); - Position target = Position.of("a2"); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); MoveParameters moveParameters = new MoveParameters(source, target); //when, then - assertThatIllegalArgumentException().isThrownBy(() -> board.move(moveParameters, true)); + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .withMessage("기물을 통과하여 이동할 수 없습니다."); } } From a40508df22586f4247f5280ddcb4778af889ddac Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Thu, 19 Aug 2021 17:24:48 +0900 Subject: [PATCH 15/38] =?UTF-8?q?refactor:=20=ED=94=BC=EC=8A=A4=20?= =?UTF-8?q?=EC=83=89=EC=83=81=EB=B3=84=20=EC=B2=B4=EC=8A=A4=ED=8C=90=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++ src/main/java/chess/domain/board/Board.java | 111 ++++++++---------- .../java/chess/domain/piece/PieceFactory.java | 51 ++++++++ src/main/java/chess/domain/player/Player.java | 42 +++++++ .../java/chess/view/ConsoleOutputView.java | 1 - .../java/chess/domain/board/BoardTest.java | 14 ++- .../java/chess/domain/player/PlayerTest.java | 79 +++++++++++++ 7 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 src/main/java/chess/domain/piece/PieceFactory.java create mode 100644 src/main/java/chess/domain/player/Player.java create mode 100644 src/test/java/chess/domain/player/PlayerTest.java diff --git a/README.md b/README.md index bde670b..c6ee1b7 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,14 @@ - [x] 기물 이름 매퍼 - [x] 기물 종류에 따라 이름을 매핑 +- [ ] 플레이어(Player) + - [x] 자신의 기물 + - [ ] 자신이 공격 가능한 범위 + + - 기능 + - [x] 기물을 이동시킨다. + - [x] 기물의 이동 경로를 반환한다. + - [x] 입력받은 위치에 기물이 있는지 확인한다. ## 이동 규칙 diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index d2cb3e9..ad5ef27 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -1,89 +1,43 @@ package chess.domain.board; import chess.domain.command.MoveParameters; -import chess.domain.piece.Bishop; import chess.domain.piece.Color; -import chess.domain.piece.King; -import chess.domain.piece.Knight; -import chess.domain.piece.Pawn; import chess.domain.piece.Piece; -import chess.domain.piece.Queen; -import chess.domain.piece.Rook; +import chess.domain.player.Player; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.Set; public class Board { - private final Map board = new HashMap<>(); + private final Player white; + private final Player black; public Board() { - initializePawns(); - initializeOthers(Rank.R1, Color.WHITE); - initializeOthers(Rank.R8, Color.BLACK); - } - - private void initializeOthers(Rank rank, Color color) { - board.put(Position.from(File.a, rank), new Rook(color)); - board.put(Position.from(File.b, rank), new Knight(color)); - board.put(Position.from(File.c, rank), new Bishop(color)); - board.put(Position.from(File.d, rank), new Queen(color)); - board.put(Position.from(File.e, rank), new King(color)); - board.put(Position.from(File.f, rank), new Bishop(color)); - board.put(Position.from(File.g, rank), new Knight(color)); - board.put(Position.from(File.h, rank), new Rook(color)); - } - - private void initializePawns() { - Arrays.stream(File.values()) - .forEach(file -> { - board.put(Position.from(file, Rank.R2), new Pawn(Color.WHITE)); - board.put(Position.from(file, Rank.R7), new Pawn(Color.BLACK)); - }); + this.white = new Player(Color.WHITE); + this.black = new Player(Color.BLACK); } public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) { + Player player = currentPlayer(isWhiteTurn); + Player enemy = currentPlayer(!isWhiteTurn); Position source = moveParameters.getSource(); Position target = moveParameters.getTarget(); - Piece sourcePiece = findBy(source); - validateOwner(isWhiteTurn, sourcePiece.isWhite()); + validateSourceOwner(enemy, source); validateSamePosition(source, target); - validateTarget(target, sourcePiece); + validateTarget(player, target); - Set paths = sourcePiece.findPaths(source, target); - validatePathsEmpty(paths); - - board.put(target, sourcePiece); - board.remove(source); + movePiece(player, source, target); } - private void validateTarget(final Position target, final Piece sourcePiece) { - if (isEmpty(target)) { - return; - } - - Piece targetPiece = board.get(target); - if (sourcePiece.hasSameColor(targetPiece)) { - throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); + private Player currentPlayer(final boolean isWhiteTurn) { + if (isWhiteTurn) { + return white; } + return black; } - public Piece findBy(final Position position) { - if (isEmpty(position)) { - throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); - } - - return board.get(position); - } - - public boolean isEmpty(Position position) { - return !board.containsKey(position); - } - - private void validateOwner(final boolean expectedColor, final boolean sourcePieceColor) { - if (sourcePieceColor != expectedColor) { + private void validateSourceOwner(final Player enemy, final Position source) { + if (enemy.hasPieceOn(source)) { throw new IllegalArgumentException("자신의 기물만 움직일 수 있습니다."); } } @@ -94,12 +48,39 @@ private void validateSamePosition(final Position source, final Position target) } } + private void validateTarget(final Player player, final Position target) { + if (player.hasPieceOn(target)) { + throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); + } + } + + private void movePiece(final Player player, final Position source, final Position target) { + Set paths = player.findPaths(source, target); + validatePathsEmpty(paths); + + player.update(source, target); + } + private void validatePathsEmpty(final Set paths) { - boolean isPresent = paths.stream() - .anyMatch(board::containsKey); + boolean isWhiteBlocked = paths.stream() + .anyMatch(white::hasPieceOn); + boolean isBlackBlocked = paths.stream() + .anyMatch(black::hasPieceOn); - if (isPresent) { + if (isWhiteBlocked || isBlackBlocked) { throw new IllegalArgumentException("기물을 통과하여 이동할 수 없습니다."); } } + + public Piece findBy(final Position position) { + if (white.hasPieceOn(position)) { + return white.findPieceBy(position); + } + + return black.findPieceBy(position); + } + + public boolean isEmpty(Position position) { + return !white.hasPieceOn(position) && !black.hasPieceOn(position); + } } diff --git a/src/main/java/chess/domain/piece/PieceFactory.java b/src/main/java/chess/domain/piece/PieceFactory.java new file mode 100644 index 0000000..b3d23d3 --- /dev/null +++ b/src/main/java/chess/domain/piece/PieceFactory.java @@ -0,0 +1,51 @@ +package chess.domain.piece; + +import chess.domain.board.File; +import chess.domain.board.Position; +import chess.domain.board.Rank; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; + +public class PieceFactory { + + private PieceFactory() { + } + + public static Map createPieces(Color color) { + if (color == WHITE) { + return whitePieces(); + } + return blackPieces(); + } + + private static Map whitePieces() { + return initializePieces(Rank.R1, Rank.R2, WHITE); + } + + private static Map blackPieces() { + return initializePieces(Rank.R8, Rank.R7, BLACK); + } + + private static Map initializePieces(final Rank rank, final Rank pawnRank, final Color color) { + Map board = new HashMap<>(); + + board.put(Position.from(File.a, rank), new Rook(color)); + board.put(Position.from(File.b, rank), new Knight(color)); + board.put(Position.from(File.c, rank), new Bishop(color)); + board.put(Position.from(File.d, rank), new Queen(color)); + board.put(Position.from(File.e, rank), new King(color)); + board.put(Position.from(File.f, rank), new Bishop(color)); + board.put(Position.from(File.g, rank), new Knight(color)); + board.put(Position.from(File.h, rank), new Rook(color)); + + Arrays.stream(File.values()) + .forEach(file -> board.put(Position.from(file, pawnRank), new Pawn(color))); + + return board; + } +} diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java new file mode 100644 index 0000000..a0f247a --- /dev/null +++ b/src/main/java/chess/domain/player/Player.java @@ -0,0 +1,42 @@ +package chess.domain.player; + +import chess.domain.board.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class Player { + + private final Map board; + + public Player(final Color color) { + board = new HashMap<>(PieceFactory.createPieces(color)); + } + + public boolean hasPieceOn(final Position position) { + return board.containsKey(position); + } + + public Set findPaths(final Position source, final Position target) { + Piece sourcePiece = findPieceBy(source); + return sourcePiece.findPaths(source, target); + } + + public void update(final Position source, final Position target) { + Piece sourcePiece = findPieceBy(source); + board.put(target, sourcePiece); + board.remove(source); + } + + public Piece findPieceBy(final Position position) { + if (!hasPieceOn(position)) { + throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); + } + + return board.get(position); + } +} diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 8d08771..3733513 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -4,7 +4,6 @@ public class ConsoleOutputView implements OutputView { - @Override public void printBoard(final BoardDto boardDto) { boardDto.getPositionDtos() diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 00af2a3..71e6d55 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,28 +1,30 @@ package chess.domain.board; import chess.domain.command.MoveParameters; +import chess.domain.piece.Pawn; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class BoardTest { - @ParameterizedTest - @CsvSource({"a1, false", "a3, true"}) + @Test @DisplayName("객체를 생성한다.") - void create(String key, boolean expected) { + void create() { //given - Position position = Position.of(key); + Position pawnPosition = Position.of("b2"); + Position emptyPosition = Position.of("b3"); //when Board board = new Board(); //then - assertThat(board.isEmpty(position)).isEqualTo(expected); + assertThat(board.findBy(pawnPosition)).isInstanceOf(Pawn.class); + assertThat(board.isEmpty(emptyPosition)).isTrue(); } @Test diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java new file mode 100644 index 0000000..260e86c --- /dev/null +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -0,0 +1,79 @@ +package chess.domain.player; + +import chess.domain.board.Position; +import chess.domain.piece.Color; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class PlayerTest { + + @ParameterizedTest + @CsvSource({"b2, true", "b3, false"}) + @DisplayName("피스 색상을 넣어서 플레이어 객체를 생성한다.") + void create_with_color(String key, boolean expected) { + //given + Position position = Position.of(key); + Color color = Color.WHITE; + + // when + Player player = new Player(color); + + //then + assertThat(player.hasPieceOn(position)).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({"WHITE, b3, b4", "BLACK, b6, b5"}) + @DisplayName("시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") + void update_source_position_empty(Color color, String sourcePosition, String targetPosition) { + //given + Player player = new Player(color); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + + //when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> player.update(source, target)) + .withMessage("해당 위치에 기물이 존재하지 않습니다."); + } + + @ParameterizedTest + @CsvSource({"WHITE, b2, b3", "BLACK, d7, d6"}) + @DisplayName("기물을 움직인다.") + void update_board(Color color, String sourcePosition, String targetPosition) { + //given + Player player = new Player(color); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + + //when + player.update(source, target); + + //then + assertThat(player.hasPieceOn(source)).isFalse(); + assertThat(player.hasPieceOn(target)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"WHITE, b2, b4, b3", "BLACK, d7, d5, d6"}) + @DisplayName("이동 경로를 반환한다.") + void find_paths(Color color, String sourcePosition, String targetPosition, String expected) { + //given + Player player = new Player(color); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + Position path = Position.of(expected); + + //when + Set paths = player.findPaths(source, target); + + //then + assertThat(paths).containsOnly(path); + } +} From c30f2426777dc58504dd745292ec899dea2d3063 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Thu, 19 Aug 2021 19:45:58 +0900 Subject: [PATCH 16/38] =?UTF-8?q?feat:=20=EB=B9=84=EC=88=8D,=20=EB=A3=A9?= =?UTF-8?q?=20=EA=B3=B5=EA=B2=A9=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/ChessGame.java | 1 + src/main/java/chess/domain/board/File.java | 5 ++++ .../java/chess/domain/board/Position.java | 18 +++++++++++++++ src/main/java/chess/domain/board/Rank.java | 5 ++++ src/main/java/chess/domain/piece/Bishop.java | 12 +++++++++- src/main/java/chess/domain/piece/Piece.java | 17 ++++++++++---- src/main/java/chess/domain/piece/Rook.java | 12 +++++++++- .../java/chess/domain/piece/BishopTest.java | 23 +++++++++++++++++++ .../java/chess/domain/piece/RookTest.java | 23 +++++++++++++++++++ 9 files changed, 109 insertions(+), 7 deletions(-) diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index d1462a4..305244f 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -32,6 +32,7 @@ public void run(final Command command) { private void move(final MoveParameters moveParameters) { board.move(moveParameters, isWhiteTurn); + isWhiteTurn = !isWhiteTurn; } public boolean isRunning() { diff --git a/src/main/java/chess/domain/board/File.java b/src/main/java/chess/domain/board/File.java index 2cb247b..8a18a09 100644 --- a/src/main/java/chess/domain/board/File.java +++ b/src/main/java/chess/domain/board/File.java @@ -40,4 +40,9 @@ public int calculateGap(final File file) { public File add(final int amount) { return File.of(this.index + amount); } + + public boolean canMove(final int amount) { + int fileIndex = index + amount; + return fileIndex >= a.index && fileIndex <= h.index; + } } diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index f2acc7e..52ca177 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -3,6 +3,8 @@ import chess.domain.piece.Direction; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; @@ -63,4 +65,20 @@ public Position move(final Direction direction) { public boolean hasSameRank(final Rank rank) { return this.rank == rank; } + + public Collection findAvailablePositions(final Direction direction) { + Collection positions = new HashSet<>(); + + Position current = this; + while (current.isMovable(direction)) { + current = current.move(direction); + positions.add(current); + } + + return positions; + } + + private boolean isMovable(final Direction direction) { + return rank.canMove(direction.getRank()) && file.canMove(direction.getFile()); + } } diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java index c76d31c..549e78e 100644 --- a/src/main/java/chess/domain/board/Rank.java +++ b/src/main/java/chess/domain/board/Rank.java @@ -40,5 +40,10 @@ public int calculateGap(final Rank rank) { public Rank add(final int amount) { return Rank.of(this.index + amount); } + + public boolean canMove(final int amount) { + int rankIndex = index + amount; + return rankIndex >= R1.index && rankIndex <= R8.index; + } } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 94c96c8..9a7854f 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,9 +1,19 @@ package chess.domain.piece; +import java.util.Arrays; +import java.util.Collection; + +import static chess.domain.piece.Direction.NORTH_EAST; +import static chess.domain.piece.Direction.NORTH_WEST; +import static chess.domain.piece.Direction.SOUTH_EAST; +import static chess.domain.piece.Direction.SOUTH_WEST; + public class Bishop extends Piece { + private static final Collection DIRECTIONS = Arrays.asList(NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST); + public Bishop(final Color color) { - super(color); + super(DIRECTIONS, color); } @Override diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 17848d2..7e21a16 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -2,17 +2,21 @@ import chess.domain.board.Position; +import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; import static chess.domain.piece.Color.BLACK; import static chess.domain.piece.Color.WHITE; import static java.lang.Math.abs; public abstract class Piece { + private final Collection DIRECTIONS; private final Color color; - Piece(final Color color) { + Piece(final Collection DIRECTIONS, final Color color) { + this.DIRECTIONS = DIRECTIONS; this.color = color; } @@ -26,10 +30,6 @@ public boolean isBlack() { return color == BLACK; } - public boolean hasSameColor(final Piece target) { - return this.color == target.color; - } - public Set findPaths(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); @@ -67,4 +67,11 @@ public Set collectPositions(final Position source, final Position targ positions.remove(target); return positions; } + + public Collection findAvailableAttackPositions(Position position) { + return DIRECTIONS.stream() + .map(position::findAvailablePositions) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index a09cf42..e8eecfc 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,9 +1,19 @@ package chess.domain.piece; +import java.util.Arrays; +import java.util.Collection; + +import static chess.domain.piece.Direction.EAST; +import static chess.domain.piece.Direction.NORTH; +import static chess.domain.piece.Direction.SOUTH; +import static chess.domain.piece.Direction.WEST; + public class Rook extends Piece { + private static final Collection DIRECTIONS = Arrays.asList(EAST, WEST, NORTH, SOUTH); + public Rook(final Color color) { - super(color); + super(DIRECTIONS, color); } @Override diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index 2cd3c0d..8213b40 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import java.util.stream.Stream; @@ -48,6 +50,27 @@ void find_paths_invalid_target() { assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); } + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece bishop = new Bishop(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("a1"), Position.of("b2"), Position.of("c3"), Position.of("e5"), + Position.of("a7"), Position.of("b6"), Position.of("c5"), Position.of("e3"), + Position.of("f6"), Position.of("g7"), Position.of("h8"), + Position.of("f2"), Position.of("g1") + ); + + //when + Collection availableAttackPositions = bishop.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .containsAll(expected); + } + private static Stream createParameters() { return Stream.of( Arguments.of("b6", tuple(File.c, Rank.R5)), diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java index 2e564e3..6d17c21 100644 --- a/src/test/java/chess/domain/piece/RookTest.java +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import java.util.stream.Stream; @@ -48,6 +50,27 @@ void find_paths_invalid_target() { assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); } + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece rook = new Rook(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("d1"), Position.of("d2"), Position.of("d3"), Position.of("d5"), + Position.of("d6"), Position.of("d7"), Position.of("d8"), + Position.of("a4"), Position.of("b4"), Position.of("c4"), Position.of("e4"), + Position.of("f4"), Position.of("g4"), Position.of("h4") + ); + + //when + Collection availableAttackPositions = rook.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .containsAll(expected); + } + private static Stream createParameters() { return Stream.of( Arguments.of("d2", tuple(File.d, Rank.R3)), From 483c149337b8319d81dd8e00f92a3aced0f337a2 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Fri, 20 Aug 2021 20:48:37 +0900 Subject: [PATCH 17/38] =?UTF-8?q?feat:=20=ED=8F=B0=20=EC=A0=9C=EC=99=B8?= =?UTF-8?q?=ED=95=9C=20=EA=B8=B0=EB=AC=BC=20=ED=8C=A8=ED=84=B4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/chess/domain/board/Position.java | 49 ++++++-- src/main/java/chess/domain/piece/Bishop.java | 19 +-- src/main/java/chess/domain/piece/Color.java | 7 +- .../java/chess/domain/piece/Direction.java | 49 -------- src/main/java/chess/domain/piece/King.java | 11 +- src/main/java/chess/domain/piece/Knight.java | 29 +---- .../chess/domain/piece/MoveCoordinate.java | 72 +++++++++++ src/main/java/chess/domain/piece/Pawn.java | 76 +---------- src/main/java/chess/domain/piece/Piece.java | 60 +++------ src/main/java/chess/domain/piece/Queen.java | 11 +- src/main/java/chess/domain/piece/Rook.java | 19 +-- .../domain/piece/pattern/MovePattern.java | 118 ++++++++++++++++++ .../chess/domain/player/AttackPositions.java | 27 ++++ src/main/java/chess/domain/player/Player.java | 21 ++-- .../java/chess/domain/piece/BishopTest.java | 5 +- .../chess/domain/piece/DirectionTest.java | 21 ---- .../java/chess/domain/piece/KingTest.java | 28 ++++- .../java/chess/domain/piece/KnightTest.java | 27 +++- .../java/chess/domain/piece/PawnTest.java | 34 ++++- .../java/chess/domain/piece/QueenTest.java | 30 ++++- .../java/chess/domain/piece/RookTest.java | 4 +- .../domain/player/AttackPositionsTest.java | 27 ++++ 22 files changed, 447 insertions(+), 297 deletions(-) delete mode 100644 src/main/java/chess/domain/piece/Direction.java create mode 100644 src/main/java/chess/domain/piece/MoveCoordinate.java create mode 100644 src/main/java/chess/domain/piece/pattern/MovePattern.java create mode 100644 src/main/java/chess/domain/player/AttackPositions.java delete mode 100644 src/test/java/chess/domain/piece/DirectionTest.java create mode 100644 src/test/java/chess/domain/player/AttackPositionsTest.java diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index 52ca177..03c8607 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -1,12 +1,14 @@ package chess.domain.board; -import chess.domain.piece.Direction; +import chess.domain.piece.MoveCoordinate; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; public class Position { private static final Map POSITIONS = createPositions(); @@ -56,29 +58,56 @@ public int calculateRankGap(final Position source) { return rank.calculateGap(source.getRank()); } - public Position move(final Direction direction) { - File file = this.file.add(direction.getFile()); - Rank rank = this.rank.add(direction.getRank()); - return Position.from(file, rank); + public Set findPassingPositions(Position target, MoveCoordinate moveCoordinate) { + Set positions = new HashSet<>(); + Position current = this; + + while (!target.equals(current)) { + current = current.move(moveCoordinate); + positions.add(current); + } + + positions.remove(target); + return positions; } public boolean hasSameRank(final Rank rank) { return this.rank == rank; } - public Collection findAvailablePositions(final Direction direction) { + public Collection findAvailablePositions(final MoveCoordinate moveCoordinate, final boolean isFinite) { + if (isFinite) { + return getFinitePositions(moveCoordinate); + } + return getInfinitePositions(moveCoordinate); + } + + private Collection getFinitePositions(MoveCoordinate moveCoordinate) { + if (isMovable(moveCoordinate)) { + return Collections.singleton(move(moveCoordinate)); + } + return Collections.emptySet(); + } + + private Collection getInfinitePositions(MoveCoordinate moveCoordinate) { Collection positions = new HashSet<>(); Position current = this; - while (current.isMovable(direction)) { - current = current.move(direction); + while (current.isMovable(moveCoordinate)) { + current = current.move(moveCoordinate); positions.add(current); } return positions; } - private boolean isMovable(final Direction direction) { - return rank.canMove(direction.getRank()) && file.canMove(direction.getFile()); + private boolean isMovable(final MoveCoordinate moveCoordinate) { + return rank.canMove(moveCoordinate.getRank()) && file.canMove(moveCoordinate.getFile()); + } + + private Position move(final MoveCoordinate moveCoordinate) { + File file = this.file.add(moveCoordinate.getFile()); + Rank rank = this.rank.add(moveCoordinate.getRank()); + return Position.from(file, rank); } } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 9a7854f..6265e9c 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,25 +1,10 @@ package chess.domain.piece; -import java.util.Arrays; -import java.util.Collection; - -import static chess.domain.piece.Direction.NORTH_EAST; -import static chess.domain.piece.Direction.NORTH_WEST; -import static chess.domain.piece.Direction.SOUTH_EAST; -import static chess.domain.piece.Direction.SOUTH_WEST; +import chess.domain.piece.pattern.MovePattern; public class Bishop extends Piece { - private static final Collection DIRECTIONS = Arrays.asList(NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST); - public Bishop(final Color color) { - super(DIRECTIONS, color); - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - if (!isDiagonal(fileGap, rankGap)) { - throw new IllegalArgumentException("비숍이 이동할 수 없는 위치입니다."); - } + super(MovePattern.bishopPattern(), color); } } diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/piece/Color.java index 517538f..f61d576 100644 --- a/src/main/java/chess/domain/piece/Color.java +++ b/src/main/java/chess/domain/piece/Color.java @@ -2,5 +2,10 @@ public enum Color { WHITE, - BLACK + BLACK; + + public boolean isWhite() { + return this == WHITE; + + } } diff --git a/src/main/java/chess/domain/piece/Direction.java b/src/main/java/chess/domain/piece/Direction.java deleted file mode 100644 index 5ea3d1a..0000000 --- a/src/main/java/chess/domain/piece/Direction.java +++ /dev/null @@ -1,49 +0,0 @@ -package chess.domain.piece; - -import java.util.Arrays; - -public enum Direction { - NORTH_EAST(1, 1), - SOUTH_EAST(1, -1), - NORTH_WEST(-1, 1), - SOUTH_WEST(-1, -1), - NORTH(0, 1), - SOUTH(0, -1), - EAST(1, 0), - WEST(-1, 0); - - private static final int ZERO = 0; - - private final int file; - private final int rank; - - Direction(final int file, final int rank) { - this.file = file; - this.rank = rank; - } - - public static Direction of(final int fileGap, final int rankGap) { - int fileSign = Integer.compare(fileGap, ZERO); - int rankSign = Integer.compare(rankGap, ZERO); - return findByFileAndRankSign(fileSign, rankSign); - } - - private static Direction findByFileAndRankSign(final int fileSign, final int rankSign) { - return Arrays.stream(Direction.values()) - .filter(direction -> direction.hasSameSigns(fileSign, rankSign)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("매칭되는 방향이 없습니다.")); - } - - private boolean hasSameSigns(final int fileSign, final int rankSign) { - return this.file == fileSign && this.rank == rankSign; - } - - public int getFile() { - return file; - } - - public int getRank() { - return rank; - } -} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 89ffd0b..855d923 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -1,15 +1,10 @@ package chess.domain.piece; +import chess.domain.piece.pattern.MovePattern; + public class King extends Piece { public King(final Color color) { - super(color); - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - if (!isFiniteStraight(fileGap, rankGap) && !isFiniteDiagonal(fileGap, rankGap)) { - throw new IllegalArgumentException("킹이 이동할 수 없는 위치입니다."); - } + super(MovePattern.kingPattern(), color); } } diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index c94bae7..4ed9f31 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,35 +1,10 @@ package chess.domain.piece; -import chess.domain.board.Position; - -import java.util.Collections; -import java.util.Set; - -import static java.lang.Math.abs; +import chess.domain.piece.pattern.MovePattern; public class Knight extends Piece { public Knight(final Color color) { - super(color); - } - - @Override - public Set findPaths(final Position source, final Position target) { - int fileGap = target.calculateFileGap(source); - int rankGap = target.calculateRankGap(source); - validatePattern(abs(fileGap), abs(rankGap)); - - return Collections.emptySet(); - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - if (!isKnightPattern(fileGap, rankGap)) { - throw new IllegalArgumentException("나이트가 이동할 수 없는 위치입니다."); - } - } - - private boolean isKnightPattern(final int fileGap, final int rankGap) { - return (fileGap == 2 && rankGap == 1) || (fileGap == 1 && rankGap == 2); + super(MovePattern.knightPattern(), color); } } diff --git a/src/main/java/chess/domain/piece/MoveCoordinate.java b/src/main/java/chess/domain/piece/MoveCoordinate.java new file mode 100644 index 0000000..ef14d67 --- /dev/null +++ b/src/main/java/chess/domain/piece/MoveCoordinate.java @@ -0,0 +1,72 @@ +package chess.domain.piece; + +public enum MoveCoordinate { + NORTH_EAST(1, 1), + SOUTH_EAST(1, -1), + NORTH_WEST(-1, 1), + SOUTH_WEST(-1, -1), + NORTH(0, 1), + SOUTH(0, -1), + EAST(1, 0), + WEST(-1, 0), + + WHITE_PAWN_INITIAL_NORTH(0, 2), + BLACK_PAWN_INITIAL_SOUTH(0, -2), + + NORTH_EAST_LEFT(1, 2), + NORTH_EAST_RIGHT(2, 1), + NORTH_WEST_LEFT(-2, 1), + NORTH_WEST_RIGHT(-1, 2), + SOUTH_EAST_LEFT(2, -1), + SOUTH_EAST_RIGHT(1, -2), + SOUTH_WEST_LEFT(-1, -2), + SOUTH_WEST_RIGHT(-2, -1); + + private final int file; + private final int rank; + + MoveCoordinate(final int file, final int rank) { + this.file = file; + this.rank = rank; + } + + public boolean matches(final int fileGap, final int rankGap, boolean isFinite) { + if (isFinite) { + return this.file == fileGap && this.rank == rankGap; + } + + if (this.file == 0) { + return this.file == fileGap && (rankGap % this.rank == 0) && hasSameSign(fileGap, rankGap); + } + + if (this.rank == 0) { + return this.rank == rankGap && (fileGap % this.file == 0) && hasSameSign(fileGap, rankGap); + } + + return isMultiple(fileGap, rankGap) && hasSameSign(fileGap, rankGap); + } + + private boolean isMultiple(final int fileGap, final int rankGap) { + return hasSameRate(fileGap, rankGap) && isDivisible(fileGap, rankGap); + } + + private boolean isDivisible(final int fileGap, final int rankGap) { + return (fileGap % this.file == 0) && (rankGap % this.rank == 0); + } + + private boolean hasSameRate(final int fileGap, final int rankGap) { + return (fileGap / this.file) == (rankGap / this.rank); + } + + private boolean hasSameSign(final int fileGap, final int rankGap) { + return (file ^ fileGap) >= 0 && (rank ^ rankGap) >= 0; + } + + public int getFile() { + return file; + } + + public int getRank() { + return rank; + } +} diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index cf6ed66..e65c1ca 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,88 +1,16 @@ package chess.domain.piece; -import chess.domain.board.Position; -import chess.domain.board.Rank; - -import java.util.Set; - -import static java.lang.Math.abs; +import chess.domain.piece.pattern.MovePattern; public class Pawn extends Piece { private static final int VALID_GAP_ON_ATTACK = 1; private static final int MINIMUM_MOVE_COUNT = 1; private static final int MAXIMUM_MOVE_COUNT = 2; - private static final Rank WHITE_INITIAL_RANK = Rank.of(2); - private static final Rank BLACK_INITIAL_RANK = Rank.of(7); private boolean isInitialMove = true; public Pawn(final Color color) { - super(color); - } - - @Override - public Set findPaths(final Position source, final Position target) { - setInitialMoveFlag(source); - - int fileGap = target.calculateFileGap(source); - int rankGap = target.calculateRankGap(source); - validatePattern(fileGap, rankGap); - - Direction direction = Direction.of(fileGap, rankGap); - return collectPositions(source, target, direction); - } - - private void setInitialMoveFlag(final Position source) { - if (isWhite() && !source.hasSameRank(WHITE_INITIAL_RANK)) { - isInitialMove = false; - } - - if (isBlack() && !source.hasSameRank(BLACK_INITIAL_RANK)) { - isInitialMove = false; - } - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - validateForward(rankGap); - - fileGap = abs(fileGap); - rankGap = abs(rankGap); - validateMoveCount(rankGap, fileGap); - } - - private void validateForward(final int rankGap) { - if (isWhite() && rankGap < MINIMUM_MOVE_COUNT) { - throw new IllegalArgumentException(); - } - - if (isBlack() && rankGap > MINIMUM_MOVE_COUNT * -1) { - throw new IllegalArgumentException(); - } - } - - private void validateMoveCount(final int rankGap, final int fileGap) { - if (fileGap != 0) { - validateAttackMoveCount(rankGap, fileGap); - } - - validateForwardMoveCount(rankGap); - } - - private void validateAttackMoveCount(final int rankGap, final int fileGap) { - if (rankGap > VALID_GAP_ON_ATTACK || fileGap > VALID_GAP_ON_ATTACK) { - throw new IllegalArgumentException(); - } - } - - private void validateForwardMoveCount(final int rankGap) { - if (isInitialMove && rankGap > MAXIMUM_MOVE_COUNT) { - throw new IllegalArgumentException(); - } - - if (!isInitialMove && rankGap > MINIMUM_MOVE_COUNT) { - throw new IllegalArgumentException(); - } + super(MovePattern.pawnPattern(color), color); } } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 7e21a16..26aad3a 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -1,6 +1,7 @@ package chess.domain.piece; import chess.domain.board.Position; +import chess.domain.piece.pattern.MovePattern; import java.util.Collection; import java.util.HashSet; @@ -9,19 +10,17 @@ import static chess.domain.piece.Color.BLACK; import static chess.domain.piece.Color.WHITE; -import static java.lang.Math.abs; public abstract class Piece { - private final Collection DIRECTIONS; + + protected final MovePattern movePattern; private final Color color; - Piece(final Collection DIRECTIONS, final Color color) { - this.DIRECTIONS = DIRECTIONS; + Piece(final MovePattern movePattern, final Color color) { + this.movePattern = movePattern; this.color = color; } - protected abstract void validatePattern(final int fileGap, final int rankGap); - public boolean isWhite() { return color == WHITE; } @@ -30,48 +29,27 @@ public boolean isBlack() { return color == BLACK; } - public Set findPaths(final Position source, final Position target) { + public Set findPath(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); - validatePattern(abs(fileGap), abs(rankGap)); - - Direction direction = Direction.of(fileGap, rankGap); - return collectPositions(source, target, direction); - } - - protected boolean isStraight(final int fileGap, final int rankGap) { - return fileGap == 0 || rankGap == 0; - } - - protected boolean isFiniteStraight(final int fileGap, final int rankGap) { - return (fileGap + rankGap) == 1; - } - - protected boolean isDiagonal(final int fileGap, final int rankGap) { - return fileGap == rankGap; - } - protected boolean isFiniteDiagonal(final int fileGap, final int rankGap) { - return (fileGap == 1) && (rankGap == 1); + MoveCoordinate moveCoordinate = movePattern.findMoveCoordinate(fileGap, rankGap); + return source.findPassingPositions(target, moveCoordinate); } - public Set collectPositions(final Position source, final Position target, final Direction direction) { - Set positions = new HashSet<>(); - Position current = source; - - while (!target.equals(current)) { - current = current.move(direction); - positions.add(current); - } - - positions.remove(target); - return positions; - } + public Collection findAvailableAttackPositions(final Position position) { + Set finitePositions = movePattern.finiteMoveCoordinates().stream() + .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, true)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); - public Collection findAvailableAttackPositions(Position position) { - return DIRECTIONS.stream() - .map(position::findAvailablePositions) + Set infinitePositions = movePattern.infiniteMoveCoordinates().stream() + .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, false)) .flatMap(Collection::stream) .collect(Collectors.toSet()); + + Collection positions = new HashSet<>(finitePositions); + positions.addAll(infinitePositions); + return positions; } } diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 1219815..7389d31 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -1,15 +1,10 @@ package chess.domain.piece; +import chess.domain.piece.pattern.MovePattern; + public class Queen extends Piece { public Queen(final Color color) { - super(color); - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - if (!isDiagonal(fileGap, rankGap) && !isFiniteStraight(fileGap, rankGap)) { - throw new IllegalArgumentException("퀸이 이동할 수 없는 위치입니다."); - } + super(MovePattern.queenPattern(), color); } } diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index e8eecfc..2e9f2c6 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,25 +1,10 @@ package chess.domain.piece; -import java.util.Arrays; -import java.util.Collection; - -import static chess.domain.piece.Direction.EAST; -import static chess.domain.piece.Direction.NORTH; -import static chess.domain.piece.Direction.SOUTH; -import static chess.domain.piece.Direction.WEST; +import chess.domain.piece.pattern.MovePattern; public class Rook extends Piece { - private static final Collection DIRECTIONS = Arrays.asList(EAST, WEST, NORTH, SOUTH); - public Rook(final Color color) { - super(DIRECTIONS, color); - } - - @Override - protected void validatePattern(int fileGap, int rankGap) { - if (!isStraight(fileGap, rankGap)) { - throw new IllegalArgumentException("룩이 이동할 수 없는 위치입니다."); - } + super(MovePattern.rookPattern(), color); } } diff --git a/src/main/java/chess/domain/piece/pattern/MovePattern.java b/src/main/java/chess/domain/piece/pattern/MovePattern.java new file mode 100644 index 0000000..1fbd46b --- /dev/null +++ b/src/main/java/chess/domain/piece/pattern/MovePattern.java @@ -0,0 +1,118 @@ +package chess.domain.piece.pattern; + +import chess.domain.piece.Color; +import chess.domain.piece.MoveCoordinate; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; +import static chess.domain.piece.MoveCoordinate.EAST; +import static chess.domain.piece.MoveCoordinate.NORTH; +import static chess.domain.piece.MoveCoordinate.NORTH_EAST; +import static chess.domain.piece.MoveCoordinate.NORTH_EAST_LEFT; +import static chess.domain.piece.MoveCoordinate.NORTH_EAST_RIGHT; +import static chess.domain.piece.MoveCoordinate.NORTH_WEST; +import static chess.domain.piece.MoveCoordinate.NORTH_WEST_LEFT; +import static chess.domain.piece.MoveCoordinate.NORTH_WEST_RIGHT; +import static chess.domain.piece.MoveCoordinate.SOUTH; +import static chess.domain.piece.MoveCoordinate.SOUTH_EAST; +import static chess.domain.piece.MoveCoordinate.SOUTH_EAST_LEFT; +import static chess.domain.piece.MoveCoordinate.SOUTH_EAST_RIGHT; +import static chess.domain.piece.MoveCoordinate.SOUTH_WEST; +import static chess.domain.piece.MoveCoordinate.SOUTH_WEST_LEFT; +import static chess.domain.piece.MoveCoordinate.SOUTH_WEST_RIGHT; +import static chess.domain.piece.MoveCoordinate.WEST; +import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; + +public class MovePattern { + + private static final Collection CARDINAL_COORDINATES = Collections.unmodifiableList(Arrays.asList( + NORTH, SOUTH, WEST, EAST + )); + + private static final Collection DIAGONAL_COORDINATES = Collections.unmodifiableList(Arrays.asList( + NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST + )); + + private static final Collection WHITE_PAWN_COORDINATES = Collections.unmodifiableList(Arrays.asList( + WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH + )); + + private static final Collection BLACK_PAWN_COORDINATES = Collections.unmodifiableList(Arrays.asList( + BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH + )); + + private static final Collection KNIGHT_COORDINATES = Collections.unmodifiableList(Arrays.asList( + NORTH_EAST_LEFT, NORTH_EAST_RIGHT, NORTH_WEST_LEFT, NORTH_WEST_RIGHT, + SOUTH_EAST_LEFT, SOUTH_EAST_RIGHT, SOUTH_WEST_LEFT, SOUTH_WEST_RIGHT + )); + + private final Collection infiniteMoveCoordinates; + private final Collection finiteMoveCoordinates; + + private MovePattern(final Collection infiniteMoveCoordinates, final Collection finiteMoveCoordinates) { + this.infiniteMoveCoordinates = Collections.unmodifiableCollection(infiniteMoveCoordinates); + this.finiteMoveCoordinates = Collections.unmodifiableCollection(finiteMoveCoordinates); + } + + public static MovePattern queenPattern() { + return new MovePattern(DIAGONAL_COORDINATES, CARDINAL_COORDINATES); + } + + public static MovePattern kingPattern() { + List finiteMoveCoordinates = new ArrayList<>(CARDINAL_COORDINATES); + finiteMoveCoordinates.addAll(DIAGONAL_COORDINATES); + return new MovePattern(Collections.emptyList(), finiteMoveCoordinates); + } + + public static MovePattern knightPattern() { + return new MovePattern(Collections.emptyList(), KNIGHT_COORDINATES); + } + + public static MovePattern rookPattern() { + return new MovePattern(CARDINAL_COORDINATES, Collections.emptyList()); + } + + public static MovePattern bishopPattern() { + return new MovePattern(DIAGONAL_COORDINATES, Collections.emptyList()); + } + + public static MovePattern pawnPattern(final Color color) { + if (color.isWhite()) { + new MovePattern(Collections.emptyList(), WHITE_PAWN_COORDINATES); + } + return new MovePattern(Collections.emptyList(), BLACK_PAWN_COORDINATES); + } + + public MoveCoordinate findMoveCoordinate(int fileGap, int rankGap) { + List result = new ArrayList<>(); + + infiniteMoveCoordinates.stream() + .filter(moveCoordinate -> moveCoordinate.matches(fileGap, rankGap, false)) + .findAny() + .ifPresent(result::add); + + finiteMoveCoordinates.stream() + .filter(moveCoordinate -> moveCoordinate.matches(fileGap, rankGap, true)) + .findAny() + .ifPresent(result::add); + + if (result.isEmpty()) { + throw new IllegalArgumentException("매칭되는 방향이 없습니다."); + } + + return result.get(0); + } + + public Collection finiteMoveCoordinates() { + return finiteMoveCoordinates; + } + + public Collection infiniteMoveCoordinates() { + return infiniteMoveCoordinates; + } +} diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java new file mode 100644 index 0000000..ff817d8 --- /dev/null +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -0,0 +1,27 @@ +package chess.domain.player; + +import chess.domain.board.Position; +import chess.domain.piece.Piece; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class AttackPositions { + + private final Map counts = new HashMap<>(); + + public AttackPositions(final Map pieces) { + pieces.keySet() + .forEach(position -> { + Piece piece = pieces.get(position); + Collection positions = piece.findAvailableAttackPositions(position); + positions.forEach(target + -> counts.put(target, counts.getOrDefault(target, 0) + 1)); + }); + } + + public void update(final Position source, final Position target) { +// Collection positions = source.findAvailablePositions(); + } +} diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index a0f247a..1e041df 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -11,25 +11,32 @@ public class Player { - private final Map board; + private final Map pieces; + private final AttackPositions attackPositions; public Player(final Color color) { - board = new HashMap<>(PieceFactory.createPieces(color)); + pieces = new HashMap<>(PieceFactory.createPieces(color)); + attackPositions = new AttackPositions(pieces); } public boolean hasPieceOn(final Position position) { - return board.containsKey(position); + return pieces.containsKey(position); } public Set findPaths(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); - return sourcePiece.findPaths(source, target); + return sourcePiece.findPath(source, target); } public void update(final Position source, final Position target) { + movePiece(source, target); + attackPositions.update(source, target); + } + + private void movePiece(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); - board.put(target, sourcePiece); - board.remove(source); + pieces.put(target, sourcePiece); + pieces.remove(source); } public Piece findPieceBy(final Position position) { @@ -37,6 +44,6 @@ public Piece findPieceBy(final Position position) { throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); } - return board.get(position); + return pieces.get(position); } } diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index 8213b40..e8080f3 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -31,7 +31,7 @@ void find_paths_success(String targetPosition, Tuple expected) { Piece piece = new Bishop(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).extracting("file", "rank") @@ -47,7 +47,7 @@ void find_paths_invalid_target() { Piece piece = new Bishop(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); } @Test @@ -68,6 +68,7 @@ void find_available_attack_positions() { //then assertThat(availableAttackPositions) + .hasSize(expected.size()) .containsAll(expected); } diff --git a/src/test/java/chess/domain/piece/DirectionTest.java b/src/test/java/chess/domain/piece/DirectionTest.java deleted file mode 100644 index 14f355a..0000000 --- a/src/test/java/chess/domain/piece/DirectionTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package chess.domain.piece; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import static org.assertj.core.api.Assertions.assertThat; - -class DirectionTest { - - @ParameterizedTest - @CsvSource({"1,0,EAST", "-1,0,WEST", "0,1,NORTH", "0,-1,SOUTH", "1,1,NORTH_EAST", "1,-1,SOUTH_EAST", "-1,1,NORTH_WEST", "-1,-1,SOUTH_WEST"}) - @DisplayName("방향을 찾는다.") - void find_direction(int fileGap, int rankGap, Direction expected) { - //given, when - Direction direction = Direction.of(fileGap, rankGap); - - //then - assertThat(direction).isEqualTo(expected); - } -} diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java index 30c8686..ce71a38 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -2,9 +2,12 @@ import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +25,7 @@ void find_paths_success(String targetPosition) { Piece piece = new King(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).isEmpty(); @@ -38,6 +41,27 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new King(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + } + + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece king = new King(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("d3"), Position.of("d5"), + Position.of("c3"), Position.of("c4"), Position.of("c5"), + Position.of("e3"), Position.of("e4"), Position.of("e5") + ); + + //when + Collection availableAttackPositions = king.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .hasSize(expected.size()) + .containsAll(expected); } } diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java index 89a7b3f..3979525 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -2,9 +2,12 @@ import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +25,7 @@ void find_paths_success(String targetPosition) { Piece piece = new Knight(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).isEmpty(); @@ -38,6 +41,26 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new Knight(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + } + + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece knight = new Knight(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("c6"), Position.of("c2"), Position.of("e6"), Position.of("e2"), + Position.of("b5"), Position.of("b3"), Position.of("f5"), Position.of("f3") + ); + + //when + Collection availableAttackPositions = knight.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .hasSize(expected.size()) + .containsAll(expected); } } diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java index ba8a55d..16993f1 100644 --- a/src/test/java/chess/domain/piece/PawnTest.java +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -2,9 +2,12 @@ import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -23,7 +26,7 @@ void find_paths_success_move_count_one_on_initial_move(String sourcePosition, St Piece piece = new Pawn(color); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).isEmpty(); @@ -39,7 +42,7 @@ void find_paths_success_move_count_two_on_initial_move(String sourcePosition, St Piece piece = new Pawn(color); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).containsOnly(Position.of(expected)); @@ -56,7 +59,7 @@ void find_paths_success_move_count_one(String sourcePosition, String targetPosit Piece piece = new Pawn(color); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).isEmpty(); @@ -72,7 +75,7 @@ void find_paths_fail_move_invalid_count_on_initial_move(String sourcePosition, S Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); } @ParameterizedTest @@ -86,7 +89,7 @@ void find_paths_fail_move_invalid_count(String sourcePosition, String targetPosi Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); } @ParameterizedTest @@ -99,6 +102,25 @@ void find_paths_fail_move_backward(String sourcePosition, String targetPosition, Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + } + + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece pawn = new Pawn(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("c5"), Position.of("e5") + ); + + //when + Collection availableAttackPositions = pawn.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .hasSize(expected.size()) + .containsAll(expected); } } diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java index f5d51ab..b1f85e6 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -5,11 +5,14 @@ import chess.domain.board.Rank; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import java.util.Arrays; +import java.util.Collection; import java.util.Set; import java.util.stream.Stream; @@ -29,7 +32,7 @@ void find_paths_success_diagonal(String targetPosition, Tuple expected) { Piece piece = new Queen(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).extracting("file", "rank") @@ -46,7 +49,7 @@ void find_paths_success_straight(String targetPosition) { Piece piece = new Queen(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).isEmpty(); @@ -62,7 +65,28 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new Queen(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + } + + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece queen = new Queen(Color.WHITE); + Collection expected = Arrays.asList( + Position.of("a1"), Position.of("b2"), Position.of("c3"), Position.of("e5"), Position.of("f6"), Position.of("g7"), Position.of("h8"), + Position.of("a7"), Position.of("b6"), Position.of("c5"), Position.of("e3"), Position.of("f2"), Position.of("g1"), + Position.of("d3"), Position.of("d5"), Position.of("c4"), Position.of("e4") + ); + + //when + Collection availableAttackPositions = queen.findAvailableAttackPositions(position); + + //then + assertThat(availableAttackPositions) + .hasSize(expected.size()) + .containsAll(expected); } private static Stream createParametersForDiagonal() { diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java index 6d17c21..a787a58 100644 --- a/src/test/java/chess/domain/piece/RookTest.java +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -31,7 +31,7 @@ void find_paths_success(String targetPosition, Tuple expected) { Piece piece = new Rook(Color.WHITE); //when - Set paths = piece.findPaths(source, target); + Set paths = piece.findPath(source, target); //then assertThat(paths).extracting("file", "rank") @@ -47,7 +47,7 @@ void find_paths_invalid_target() { Piece piece = new Rook(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPaths(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); } @Test diff --git a/src/test/java/chess/domain/player/AttackPositionsTest.java b/src/test/java/chess/domain/player/AttackPositionsTest.java new file mode 100644 index 0000000..ab83b88 --- /dev/null +++ b/src/test/java/chess/domain/player/AttackPositionsTest.java @@ -0,0 +1,27 @@ +package chess.domain.player; + +import chess.domain.board.Position; +import chess.domain.piece.Color; +import chess.domain.piece.Piece; +import chess.domain.piece.PieceFactory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +class AttackPositionsTest { + + @Test + @DisplayName("기물들이 주어지면 공격 가능한 위치들이 초기화된다.") + void create() { + //given + Map pieces = PieceFactory.createPieces(Color.WHITE); + + //when + AttackPositions attackPositions = new AttackPositions(pieces); + + //then + + } + +} From c2f0deaacb4a0ddff1ceeae18868d129e1302371 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Fri, 20 Aug 2021 22:50:16 +0900 Subject: [PATCH 18/38] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=EC=8B=9C=20=EA=B3=B5=EA=B2=A9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EA=B0=B1=EC=8B=A0=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../java/chess/domain/board/Position.java | 8 +-- .../chess/domain/piece/MoveCoordinate.java | 6 ++ src/main/java/chess/domain/piece/Pawn.java | 69 +++++++++++++++++-- .../domain/piece/mapper/PieceMappers.java | 4 +- .../domain/piece/pattern/MovePattern.java | 17 ++++- .../chess/domain/player/AttackPositions.java | 24 +++++-- src/main/java/chess/domain/player/Player.java | 8 +-- .../domain/piece/pattern/MovePatternTest.java | 59 ++++++++++++++++ .../domain/player/AttackPositionsTest.java | 35 ++++++++-- 10 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 src/test/java/chess/domain/piece/pattern/MovePatternTest.java diff --git a/README.md b/README.md index c6ee1b7..bb1e4a7 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,9 @@ - [x] 기물 이름 매퍼 - [x] 기물 종류에 따라 이름을 매핑 -- [ ] 플레이어(Player) +- [x] 플레이어(Player) - [x] 자신의 기물 - - [ ] 자신이 공격 가능한 범위 + - [x] 자신이 공격 가능한 범위 - 기능 - [x] 기물을 이동시킨다. diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index 03c8607..8b32741 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -71,10 +71,6 @@ public Set findPassingPositions(Position target, MoveCoordinate moveCo return positions; } - public boolean hasSameRank(final Rank rank) { - return this.rank == rank; - } - public Collection findAvailablePositions(final MoveCoordinate moveCoordinate, final boolean isFinite) { if (isFinite) { return getFinitePositions(moveCoordinate); @@ -110,4 +106,8 @@ private Position move(final MoveCoordinate moveCoordinate) { Rank rank = this.rank.add(moveCoordinate.getRank()); return Position.from(file, rank); } + + public boolean hasSameRank(Rank rank) { + return this.rank == rank; + } } diff --git a/src/main/java/chess/domain/piece/MoveCoordinate.java b/src/main/java/chess/domain/piece/MoveCoordinate.java index ef14d67..7cc93d0 100644 --- a/src/main/java/chess/domain/piece/MoveCoordinate.java +++ b/src/main/java/chess/domain/piece/MoveCoordinate.java @@ -1,5 +1,7 @@ package chess.domain.piece; +import static java.lang.Math.abs; + public enum MoveCoordinate { NORTH_EAST(1, 1), SOUTH_EAST(1, -1), @@ -62,6 +64,10 @@ private boolean hasSameSign(final int fileGap, final int rankGap) { return (file ^ fileGap) >= 0 && (rank ^ rankGap) >= 0; } + public int countMove() { + return abs(this.file) + abs(this.rank); + } + public int getFile() { return file; } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index e65c1ca..f72e35c 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,16 +1,75 @@ package chess.domain.piece; +import chess.domain.board.Position; +import chess.domain.board.Rank; import chess.domain.piece.pattern.MovePattern; -public class Pawn extends Piece { +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; +import static chess.domain.piece.MoveCoordinate.NORTH; +import static chess.domain.piece.MoveCoordinate.SOUTH; +import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; - private static final int VALID_GAP_ON_ATTACK = 1; - private static final int MINIMUM_MOVE_COUNT = 1; - private static final int MAXIMUM_MOVE_COUNT = 2; +public class Pawn extends Piece { - private boolean isInitialMove = true; + private static final Rank WHITE_INITIAL_RANK = Rank.R2; + private static final Rank BLACK_INITIAL_RANK = Rank.R7; public Pawn(final Color color) { super(MovePattern.pawnPattern(color), color); } + + @Override + public Set findPath(final Position source, final Position target) { + int fileGap = target.calculateFileGap(source); + int rankGap = target.calculateRankGap(source); + + MoveCoordinate moveCoordinate = movePattern.findMoveCoordinate(fileGap, rankGap); + moveCoordinate = resetIfInitialMove(moveCoordinate, isInitialMove(source)); + + return source.findPassingPositions(target, moveCoordinate); + } + + private boolean isInitialMove(Position source) { + return source.hasSameRank(WHITE_INITIAL_RANK) || source.hasSameRank(BLACK_INITIAL_RANK); + } + + private MoveCoordinate resetIfInitialMove(MoveCoordinate moveCoordinate, boolean isInitialMove) { + validateMoveCount(moveCoordinate, isInitialMove); + + if (isWhiteInitialMove(moveCoordinate)) { + return NORTH; + } + + if (isBlackInitialMove(moveCoordinate)) { + return SOUTH; + } + + return moveCoordinate; + } + + private void validateMoveCount(MoveCoordinate moveCoordinate, boolean isInitialMove) { + if (!isInitialMove && (isBlackInitialMove(moveCoordinate) || isWhiteInitialMove(moveCoordinate))) { + throw new IllegalArgumentException("최초 이동 시에만 2칸 이동할 수 있습니다."); + } + } + + private boolean isWhiteInitialMove(final MoveCoordinate moveCoordinate) { + return moveCoordinate == WHITE_PAWN_INITIAL_NORTH; + } + + private boolean isBlackInitialMove(final MoveCoordinate moveCoordinate) { + return moveCoordinate == BLACK_PAWN_INITIAL_SOUTH; + } + + @Override + public Collection findAvailableAttackPositions(final Position position) { + return movePattern.pawnAttackMoveCoordinates(isWhite()).stream() + .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, true)) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/chess/domain/piece/mapper/PieceMappers.java b/src/main/java/chess/domain/piece/mapper/PieceMappers.java index be4d012..cc53e31 100644 --- a/src/main/java/chess/domain/piece/mapper/PieceMappers.java +++ b/src/main/java/chess/domain/piece/mapper/PieceMappers.java @@ -28,9 +28,7 @@ public static String findNameBy(final Piece piece) { .filter(mapper -> mapper.supports(piece)) .map(mapper -> mapper.findNameBy(piece)) .findAny() - .orElseThrow(() -> { - throw new NoSuchElementException("해당 기물에 해당하는 매퍼가 존재하지 않습니다."); - }); + .orElseThrow(() -> new NoSuchElementException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); } private PieceMappers() { diff --git a/src/main/java/chess/domain/piece/pattern/MovePattern.java b/src/main/java/chess/domain/piece/pattern/MovePattern.java index 1fbd46b..9f82c26 100644 --- a/src/main/java/chess/domain/piece/pattern/MovePattern.java +++ b/src/main/java/chess/domain/piece/pattern/MovePattern.java @@ -42,10 +42,18 @@ public class MovePattern { WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH )); + private static final Collection WHITE_PAWN_ATTACK_COORDINATES = Collections.unmodifiableList(Arrays.asList( + NORTH_EAST, NORTH_WEST + )); + private static final Collection BLACK_PAWN_COORDINATES = Collections.unmodifiableList(Arrays.asList( BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH )); + private static final Collection BLACK_PAWN_ATTACK_COORDINATES = Collections.unmodifiableList(Arrays.asList( + SOUTH_EAST, SOUTH_WEST + )); + private static final Collection KNIGHT_COORDINATES = Collections.unmodifiableList(Arrays.asList( NORTH_EAST_LEFT, NORTH_EAST_RIGHT, NORTH_WEST_LEFT, NORTH_WEST_RIGHT, SOUTH_EAST_LEFT, SOUTH_EAST_RIGHT, SOUTH_WEST_LEFT, SOUTH_WEST_RIGHT @@ -83,7 +91,7 @@ public static MovePattern bishopPattern() { public static MovePattern pawnPattern(final Color color) { if (color.isWhite()) { - new MovePattern(Collections.emptyList(), WHITE_PAWN_COORDINATES); + return new MovePattern(Collections.emptyList(), WHITE_PAWN_COORDINATES); } return new MovePattern(Collections.emptyList(), BLACK_PAWN_COORDINATES); } @@ -115,4 +123,11 @@ public Collection finiteMoveCoordinates() { public Collection infiniteMoveCoordinates() { return infiniteMoveCoordinates; } + + public Collection pawnAttackMoveCoordinates(boolean isWhite) { + if (isWhite) { + return WHITE_PAWN_ATTACK_COORDINATES; + } + return BLACK_PAWN_ATTACK_COORDINATES; + } } diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index ff817d8..b31ff84 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -9,6 +9,8 @@ public class AttackPositions { + private static final int EMPTY = 0; + private final Map counts = new HashMap<>(); public AttackPositions(final Map pieces) { @@ -16,12 +18,26 @@ public AttackPositions(final Map pieces) { .forEach(position -> { Piece piece = pieces.get(position); Collection positions = piece.findAvailableAttackPositions(position); - positions.forEach(target - -> counts.put(target, counts.getOrDefault(target, 0) + 1)); + positions.forEach(this::increase); }); } - public void update(final Position source, final Position target) { -// Collection positions = source.findAvailablePositions(); + public void update(final Position source, final Position target, final Piece piece) { + Collection previousAttackPositions = piece.findAvailableAttackPositions(source); + previousAttackPositions.forEach(this::decrease); + Collection currentAttackPositions = piece.findAvailableAttackPositions(target); + currentAttackPositions.forEach(this::increase); + } + + private Integer increase(final Position target) { + return counts.put(target, counts.getOrDefault(target, 0) + 1); + } + + private Integer decrease(final Position position) { + return counts.put(position, counts.get(position) - 1); + } + + public boolean isEmpty(final Position position) { + return !counts.containsKey(position) || (counts.get(position) == EMPTY); } } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index 1e041df..a62f368 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -29,12 +29,12 @@ public Set findPaths(final Position source, final Position target) { } public void update(final Position source, final Position target) { - movePiece(source, target); - attackPositions.update(source, target); + Piece sourcePiece = findPieceBy(source); + movePiece(source, target, sourcePiece); + attackPositions.update(source, target, sourcePiece); } - private void movePiece(final Position source, final Position target) { - Piece sourcePiece = findPieceBy(source); + private void movePiece(final Position source, final Position target, final Piece sourcePiece) { pieces.put(target, sourcePiece); pieces.remove(source); } diff --git a/src/test/java/chess/domain/piece/pattern/MovePatternTest.java b/src/test/java/chess/domain/piece/pattern/MovePatternTest.java new file mode 100644 index 0000000..d94db2b --- /dev/null +++ b/src/test/java/chess/domain/piece/pattern/MovePatternTest.java @@ -0,0 +1,59 @@ +package chess.domain.piece.pattern; + +import chess.domain.piece.Color; +import chess.domain.piece.MoveCoordinate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collection; + +import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; +import static chess.domain.piece.MoveCoordinate.NORTH; +import static chess.domain.piece.MoveCoordinate.NORTH_EAST; +import static chess.domain.piece.MoveCoordinate.NORTH_WEST; +import static chess.domain.piece.MoveCoordinate.SOUTH; +import static chess.domain.piece.MoveCoordinate.SOUTH_EAST; +import static chess.domain.piece.MoveCoordinate.SOUTH_WEST; +import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; +import static org.assertj.core.api.Assertions.assertThat; + +class MovePatternTest { + + @Test + @DisplayName("색상에 따라 폰 패턴을 반환한다.") + void pawn_pattern_black() { + // given + Color color = Color.BLACK; + + // when + MovePattern movePattern = MovePattern.pawnPattern(color); + Collection moveCoordinates = movePattern.finiteMoveCoordinates(); + + // then + assertThat(moveCoordinates) + .containsExactlyInAnyOrder( + BLACK_PAWN_INITIAL_SOUTH, + SOUTH_EAST, + SOUTH_WEST, + SOUTH); + } + + @Test + @DisplayName("색상에 따라 폰 패턴을 반환한다.") + void pawn_pattern_white() { + // given + Color color = Color.WHITE; + + // when + MovePattern movePattern = MovePattern.pawnPattern(color); + Collection moveCoordinates = movePattern.finiteMoveCoordinates(); + + // then + assertThat(moveCoordinates) + .containsExactlyInAnyOrder( + WHITE_PAWN_INITIAL_NORTH, + NORTH_EAST, + NORTH_WEST, + NORTH); + } +} diff --git a/src/test/java/chess/domain/player/AttackPositionsTest.java b/src/test/java/chess/domain/player/AttackPositionsTest.java index ab83b88..b639bfe 100644 --- a/src/test/java/chess/domain/player/AttackPositionsTest.java +++ b/src/test/java/chess/domain/player/AttackPositionsTest.java @@ -2,26 +2,53 @@ import chess.domain.board.Position; import chess.domain.piece.Color; +import chess.domain.piece.Knight; import chess.domain.piece.Piece; import chess.domain.piece.PieceFactory; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + class AttackPositionsTest { - @Test + @ParameterizedTest + @ValueSource(strings = {"a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3"}) @DisplayName("기물들이 주어지면 공격 가능한 위치들이 초기화된다.") - void create() { + void create(String key) { //given Map pieces = PieceFactory.createPieces(Color.WHITE); + AttackPositions attackPositions = new AttackPositions(pieces); + Position position = Position.of(key); //when - AttackPositions attackPositions = new AttackPositions(pieces); + boolean isEmpty = attackPositions.isEmpty(position); //then - + assertThat(isEmpty).isFalse(); } + @Test + @DisplayName("시작 위치에서의 공격 가능한 위치들을 없애고, 도착 위치에서의 공격 가능한 위치를 추가한다.") + void update() { + //given + Map pieces = PieceFactory.createPieces(Color.WHITE); + AttackPositions attackPositions = new AttackPositions(pieces); + Position before = Position.of("b1"); + Position current = Position.of("c3"); + + //when + attackPositions.update(before, current, new Knight(Color.WHITE)); + + //then + assertThat(attackPositions.isEmpty(Position.of("a3"))).isFalse(); + assertThat(attackPositions.isEmpty(Position.of("b1"))).isFalse(); + assertThat(attackPositions.isEmpty(Position.of("c3"))).isFalse(); + assertThat(attackPositions.isEmpty(Position.of("b5"))).isFalse(); + assertThat(attackPositions.isEmpty(Position.of("d5"))).isFalse(); + } } From c17c85297d7e6c7ea912a2b8729282ca39e1697e Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Fri, 20 Aug 2021 23:47:36 +0900 Subject: [PATCH 19/38] =?UTF-8?q?feat:=20=ED=82=B9=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=EC=A1=B0=EA=B1=B4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?,=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=8C=EC=9E=84=20=ED=9D=90=EB=A6=84=20=EC=A0=9C=EC=96=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++---- .../chess/controller/ChessController.java | 23 +++++++++++-- src/main/java/chess/domain/ChessGame.java | 1 + src/main/java/chess/domain/board/Board.java | 4 +++ src/main/java/chess/domain/piece/Piece.java | 9 ++---- .../domain/piece/mapper/PieceMappers.java | 3 +- .../chess/domain/player/AttackPositions.java | 4 +++ src/main/java/chess/domain/player/Player.java | 8 +++++ .../java/chess/view/ConsoleInputView.java | 4 +-- .../java/chess/view/ConsoleOutputView.java | 11 +++++++ src/main/java/chess/view/OutputView.java | 2 ++ .../java/chess/domain/board/BoardTest.java | 24 ++++++++++++++ .../java/chess/domain/piece/PieceTest.java | 14 +++++++- .../java/chess/domain/player/PlayerTest.java | 32 +++++++++++++++++++ 14 files changed, 132 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bb1e4a7..4cf8095 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,23 @@ ## 구현해야할 목록 -- [] 입력 - - [x] 시작 여부 입력 +- [x] 입력 + - [x] 명령어 입력 - [] 출력 + - [x] 시작 안내 문구 출력 - [x] 체스판 전체 출력 + - [ ] 점수와 결과 출력 -- [ ] 체스게임(ChessGame) +- [x] 체스게임(ChessGame) - [x] 체스판 - - [] 현재 플레이어 + - [x] 현재 플레이어 - [x] 게임 시작&종료 상태 제어 - 기능 - [x] 명령어 검증 및 처리 - [x] start : 게임 실행 - [x] end : 게임 종료 - - [] move : 인자로 전달받은 source 위치에서 target 위치로 기물 이동 + - [x] move : 인자로 전달받은 source 위치에서 target 위치로 기물 이동 - [x] 체스판(Board) - [x] 체스판 @@ -95,5 +97,5 @@ - [x] 킹 - [x] 모든 방향 1칸 이동 - [x] ERROR : 킹 이동 패턴으로 이동할 수 없는 위치일 경우 - - [ ] 상대의 공격 범위로는 이동 불가능 + - [x] 상대의 공격 범위로는 이동 불가능 diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ChessController.java index f9a587b..afab9a1 100644 --- a/src/main/java/chess/controller/ChessController.java +++ b/src/main/java/chess/controller/ChessController.java @@ -17,13 +17,30 @@ public ChessController(final InputView inputView, final OutputView outputView) { } public void run() { + outputView.printGuide(); ChessGame chessGame = new ChessGame(); while (chessGame.isRunning()) { - Command command = new Command(inputView.getCommand()); + try { + Command command = new Command(inputView.getCommand()); + runCommand(chessGame, command); + printBoard(chessGame); + } catch (UnsupportedOperationException e) { + System.out.println(e.getMessage()); + } + } + } + + private void runCommand(ChessGame chessGame, Command command) { + try { chessGame.run(command); - BoardDto boardDto = new BoardDto(chessGame.getBoard()); - outputView.printBoard(boardDto); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); } } + + private void printBoard(ChessGame chessGame) { + BoardDto boardDto = new BoardDto(chessGame.getBoard()); + outputView.printBoard(boardDto); + } } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index 305244f..656ed90 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -25,6 +25,7 @@ public void run(final Command command) { if (command.isMove()) { move(command.getMoveParameters()); + return; } throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index ad5ef27..8c3a513 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -26,6 +26,10 @@ public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) validateSamePosition(source, target); validateTarget(player, target); + if (player.hasKingOn(source) && enemy.canAttack(target)) { + throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); + } + movePiece(player, source, target); } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 26aad3a..6357690 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -8,9 +8,6 @@ import java.util.Set; import java.util.stream.Collectors; -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; - public abstract class Piece { protected final MovePattern movePattern; @@ -22,11 +19,11 @@ public abstract class Piece { } public boolean isWhite() { - return color == WHITE; + return color.isWhite(); } - public boolean isBlack() { - return color == BLACK; + public boolean isKing() { + return this instanceof King; } public Set findPath(final Position source, final Position target) { diff --git a/src/main/java/chess/domain/piece/mapper/PieceMappers.java b/src/main/java/chess/domain/piece/mapper/PieceMappers.java index cc53e31..25023de 100644 --- a/src/main/java/chess/domain/piece/mapper/PieceMappers.java +++ b/src/main/java/chess/domain/piece/mapper/PieceMappers.java @@ -5,7 +5,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.NoSuchElementException; public class PieceMappers { @@ -28,7 +27,7 @@ public static String findNameBy(final Piece piece) { .filter(mapper -> mapper.supports(piece)) .map(mapper -> mapper.findNameBy(piece)) .findAny() - .orElseThrow(() -> new NoSuchElementException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); + .orElseThrow(() -> new IllegalArgumentException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); } private PieceMappers() { diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index b31ff84..66a4ed8 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -40,4 +40,8 @@ private Integer decrease(final Position position) { public boolean isEmpty(final Position position) { return !counts.containsKey(position) || (counts.get(position) == EMPTY); } + + public boolean contains(Position target) { + return counts.containsKey(target); + } } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index a62f368..deaf4dc 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -46,4 +46,12 @@ public Piece findPieceBy(final Position position) { return pieces.get(position); } + + public boolean hasKingOn(Position position) { + return findPieceBy(position).isKing(); + } + + public boolean canAttack(Position position) { + return attackPositions.contains(position); + } } diff --git a/src/main/java/chess/view/ConsoleInputView.java b/src/main/java/chess/view/ConsoleInputView.java index 2f63d25..67a2f31 100644 --- a/src/main/java/chess/view/ConsoleInputView.java +++ b/src/main/java/chess/view/ConsoleInputView.java @@ -3,13 +3,11 @@ import java.util.Scanner; public class ConsoleInputView implements InputView { + private static final Scanner scanner = new Scanner(System.in); - private static final String HEADER = "> "; @Override public String getCommand() { - System.out.println(HEADER + "체스 게임을 시작합니다."); - System.out.println(HEADER + "게임 시작은 start, 종료는 end 명령을 입력하세요."); String input = scanner.nextLine(); validateNull(input); return input.trim(); diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 3733513..b20ba82 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -4,6 +4,16 @@ public class ConsoleOutputView implements OutputView { + private static final String HEADER = "> "; + + @Override + public void printGuide() { + System.out.println(HEADER + "체스 게임을 실행합니다."); + System.out.println(HEADER + "게임 시작 : start"); + System.out.println(HEADER + "게임 종료 : end"); + System.out.println(HEADER + "게임 이동 : move source위치 target위치 - 예. move b2 b3"); + } + @Override public void printBoard(final BoardDto boardDto) { boardDto.getPositionDtos() @@ -13,5 +23,6 @@ public void printBoard(final BoardDto boardDto) { System.out.println(); } }); + System.out.println(); } } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 1040482..7185de0 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -3,5 +3,7 @@ import chess.controller.dto.BoardDto; public interface OutputView { + void printGuide(); + void printBoard(BoardDto boardDto); } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 71e6d55..37d805b 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -105,4 +105,28 @@ void move_invalid_paths(String sourcePosition, String targetPosition, boolean is .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) .withMessage("기물을 통과하여 이동할 수 없습니다."); } + + @ParameterizedTest + @CsvSource({"e2, d2", "e2, e1"}) + @DisplayName("킹 도착지를 상대방이 공격 가능한 경우 예외가 발생한다.") + void move_king_invalid_target(String source, String target) { + //given + Board board = setBoardToAttackKing(); + MoveParameters moveParameters = new MoveParameters(Position.of(source), Position.of(target)); + + //when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> board.move(moveParameters, true)) + .withMessage("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); + } + + private Board setBoardToAttackKing() { + Board board = new Board(); + board.move(new MoveParameters(Position.of("e2"), Position.of("e4")), true); + board.move(new MoveParameters(Position.of("d2"), Position.of("d4")), true); + board.move(new MoveParameters(Position.of("e1"), Position.of("e2")), true); + board.move(new MoveParameters(Position.of("c7"), Position.of("c5")), false); + board.move(new MoveParameters(Position.of("d8"), Position.of("a5")), false); + return board; + } } diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java index cb607b1..345017a 100644 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -1,6 +1,7 @@ package chess.domain.piece; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -8,7 +9,6 @@ public class PieceTest { - @ParameterizedTest @CsvSource({"WHITE, true", "BLACK, false"}) @DisplayName("색상을 인자로 받아 객체를 생성한다.") @@ -19,4 +19,16 @@ void create(Color color, boolean expected) { //then assertThat(piece.isWhite()).isEqualTo(expected); } + + @Test + @DisplayName("킹인지 확인한다.") + void is_king() { + // given + Piece king = new King(Color.WHITE); + Piece queen = new Queen(Color.WHITE); + + // when, then + assertThat(king.isKing()).isTrue(); + assertThat(queen.isKing()).isFalse(); + } } diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index 260e86c..9689e09 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -76,4 +76,36 @@ void find_paths(Color color, String sourcePosition, String targetPosition, Strin //then assertThat(paths).containsOnly(path); } + + @ParameterizedTest + @CsvSource({"WHITE, e1, e2", "BLACK, e8, e7"}) + @DisplayName("주어진 위치에 킹이 있는지 확인한다.") + void has_king_on(Color color, String kingPosition, String notKingPosition) { + // given + Player player = new Player(color); + + // when + boolean isKing = player.hasKingOn(Position.of(kingPosition)); + boolean isNotKing = player.hasKingOn(Position.of(notKingPosition)); + + // then + assertThat(isKing).isTrue(); + assertThat(isNotKing).isFalse(); + } + + @ParameterizedTest + @CsvSource({"WHITE, b3, e6", "BLACK, e6, b3"}) + @DisplayName("주어진 위치를 공격할 수 있는지 확인한다.") + void can_attack(Color color, String attackPosition, String notAttackPosition) { + // given + Player player = new Player(color); + + // when + boolean can = player.canAttack(Position.of(attackPosition)); + boolean cannot = player.canAttack(Position.of(notAttackPosition)); + + // then + assertThat(can).isTrue(); + assertThat(cannot).isFalse(); + } } From 5d69a6766fe5b9c80383dbf418d6bc238ee3ab36 Mon Sep 17 00:00:00 2001 From: Beenie93 Date: Sat, 21 Aug 2021 01:30:26 +0900 Subject: [PATCH 20/38] =?UTF-8?q?feat:=20=EC=A0=90=EC=88=98=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=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/domain/board/Board.java | 21 +++++++-- src/main/java/chess/domain/board/Status.java | 19 ++++++++ src/main/java/chess/domain/piece/Piece.java | 5 ++ .../domain/piece/mapper/BishopMapper.java | 3 +- .../chess/domain/piece/mapper/KingMapper.java | 3 +- .../domain/piece/mapper/KnightMapper.java | 3 +- .../chess/domain/piece/mapper/PawnMapper.java | 13 +++++- .../domain/piece/mapper/PieceMapper.java | 8 +++- .../domain/piece/mapper/PieceMappers.java | 8 ++++ .../domain/piece/mapper/QueenMapper.java | 3 +- .../chess/domain/piece/mapper/RookMapper.java | 3 +- .../chess/domain/player/AttackPositions.java | 24 ++++++---- src/main/java/chess/domain/player/Player.java | 46 +++++++++++++++++++ .../java/chess/domain/board/BoardTest.java | 23 ++++++++++ .../domain/piece/mapper/PieceMappersTest.java | 30 ++++++++++-- .../java/chess/domain/player/PlayerTest.java | 14 ++++++ 16 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 src/main/java/chess/domain/board/Status.java diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 8c3a513..68153c5 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -25,11 +25,9 @@ public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) validateSourceOwner(enemy, source); validateSamePosition(source, target); validateTarget(player, target); + validateKingMovable(player, enemy, source, target); - if (player.hasKingOn(source) && enemy.canAttack(target)) { - throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); - } - + enemy.attacked(target); movePiece(player, source, target); } @@ -58,6 +56,12 @@ private void validateTarget(final Player player, final Position target) { } } + private void validateKingMovable(final Player player, final Player enemy, final Position source, final Position target) { + if (player.hasKingOn(source) && enemy.canAttack(target)) { + throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); + } + } + private void movePiece(final Player player, final Position source, final Position target) { Set paths = player.findPaths(source, target); validatePathsEmpty(paths); @@ -84,7 +88,14 @@ public Piece findBy(final Position position) { return black.findPieceBy(position); } - public boolean isEmpty(Position position) { + public boolean isEmpty(final Position position) { return !white.hasPieceOn(position) && !black.hasPieceOn(position); } + + public Status getStatus() { + double whiteScore = white.sumScores(); + double blackScore = black.sumScores(); + + return new Status(whiteScore, blackScore); + } } diff --git a/src/main/java/chess/domain/board/Status.java b/src/main/java/chess/domain/board/Status.java new file mode 100644 index 0000000..730c931 --- /dev/null +++ b/src/main/java/chess/domain/board/Status.java @@ -0,0 +1,19 @@ +package chess.domain.board; + +public class Status { + private final double whiteScore; + private final double blackScore; + + public Status(final double whiteScore, final double blackScore) { + this.whiteScore = whiteScore; + this.blackScore = blackScore; + } + + public double getWhiteScore() { + return whiteScore; + } + + public double getBlackScore() { + return blackScore; + } +} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 6357690..abfbd5f 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -26,6 +26,10 @@ public boolean isKing() { return this instanceof King; } + public boolean isPawn() { + return this instanceof Pawn; + } + public Set findPath(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); @@ -50,3 +54,4 @@ public Collection findAvailableAttackPositions(final Position position return positions; } } + diff --git a/src/main/java/chess/domain/piece/mapper/BishopMapper.java b/src/main/java/chess/domain/piece/mapper/BishopMapper.java index f7512df..6b06dc8 100644 --- a/src/main/java/chess/domain/piece/mapper/BishopMapper.java +++ b/src/main/java/chess/domain/piece/mapper/BishopMapper.java @@ -6,9 +6,10 @@ public class BishopMapper extends PieceMapper { private static final String NAME = "b"; + private static final double SCORE = 3; public BishopMapper() { - super(NAME); + super(NAME, SCORE); } @Override diff --git a/src/main/java/chess/domain/piece/mapper/KingMapper.java b/src/main/java/chess/domain/piece/mapper/KingMapper.java index 9b38e06..e8d9904 100644 --- a/src/main/java/chess/domain/piece/mapper/KingMapper.java +++ b/src/main/java/chess/domain/piece/mapper/KingMapper.java @@ -6,9 +6,10 @@ public class KingMapper extends PieceMapper { private static final String NAME = "k"; + private static final double SCORE = 0; public KingMapper() { - super(NAME); + super(NAME, SCORE); } @Override diff --git a/src/main/java/chess/domain/piece/mapper/KnightMapper.java b/src/main/java/chess/domain/piece/mapper/KnightMapper.java index af07be6..6937fbd 100644 --- a/src/main/java/chess/domain/piece/mapper/KnightMapper.java +++ b/src/main/java/chess/domain/piece/mapper/KnightMapper.java @@ -6,9 +6,10 @@ public class KnightMapper extends PieceMapper { private static final String NAME = "n"; + private static final double SCORE = 2.5; public KnightMapper() { - super(NAME); + super(NAME, SCORE); } @Override diff --git a/src/main/java/chess/domain/piece/mapper/PawnMapper.java b/src/main/java/chess/domain/piece/mapper/PawnMapper.java index 7b416aa..4beffec 100644 --- a/src/main/java/chess/domain/piece/mapper/PawnMapper.java +++ b/src/main/java/chess/domain/piece/mapper/PawnMapper.java @@ -6,9 +6,20 @@ public class PawnMapper extends PieceMapper { private static final String NAME = "p"; + private static final int DUPLICATION_THRESHOLD = 1; + private static final double SCORE = 1; + private static final double SCORE_ON_DUPLICATION = 0.5; public PawnMapper() { - super(NAME); + super(NAME, SCORE); + } + + public static double calculate(final int count) { + if (count > DUPLICATION_THRESHOLD) { + return count * SCORE_ON_DUPLICATION; + } + + return SCORE; } @Override diff --git a/src/main/java/chess/domain/piece/mapper/PieceMapper.java b/src/main/java/chess/domain/piece/mapper/PieceMapper.java index 1880424..700aa07 100644 --- a/src/main/java/chess/domain/piece/mapper/PieceMapper.java +++ b/src/main/java/chess/domain/piece/mapper/PieceMapper.java @@ -4,9 +4,11 @@ public abstract class PieceMapper { private final String name; + private final double score; - PieceMapper(final String name) { + PieceMapper(final String name, final double score) { this.name = name; + this.score = score; } abstract boolean supports(Piece piece); @@ -18,4 +20,8 @@ public String findNameBy(Piece piece) { return name.toUpperCase(); } + + public double getScore() { + return score; + } } diff --git a/src/main/java/chess/domain/piece/mapper/PieceMappers.java b/src/main/java/chess/domain/piece/mapper/PieceMappers.java index 25023de..f69c56b 100644 --- a/src/main/java/chess/domain/piece/mapper/PieceMappers.java +++ b/src/main/java/chess/domain/piece/mapper/PieceMappers.java @@ -30,6 +30,14 @@ public static String findNameBy(final Piece piece) { .orElseThrow(() -> new IllegalArgumentException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); } + public static double findScoreBy(final Piece piece) { + return pieceMappers.stream() + .filter(mapper -> mapper.supports(piece)) + .map(PieceMapper::getScore) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); + } + private PieceMappers() { } } diff --git a/src/main/java/chess/domain/piece/mapper/QueenMapper.java b/src/main/java/chess/domain/piece/mapper/QueenMapper.java index bf20c2a..1251829 100644 --- a/src/main/java/chess/domain/piece/mapper/QueenMapper.java +++ b/src/main/java/chess/domain/piece/mapper/QueenMapper.java @@ -6,9 +6,10 @@ public class QueenMapper extends PieceMapper { private static final String NAME = "q"; + private static final double SCORE = 9; public QueenMapper() { - super(NAME); + super(NAME, SCORE); } @Override diff --git a/src/main/java/chess/domain/piece/mapper/RookMapper.java b/src/main/java/chess/domain/piece/mapper/RookMapper.java index 9f38c6d..c7239b2 100644 --- a/src/main/java/chess/domain/piece/mapper/RookMapper.java +++ b/src/main/java/chess/domain/piece/mapper/RookMapper.java @@ -6,9 +6,10 @@ public class RookMapper extends PieceMapper { private static final String NAME = "r"; + private static final double SCORE = 5; public RookMapper() { - super(NAME); + super(NAME, SCORE); } @Override diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index 66a4ed8..b02d974 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -23,25 +23,33 @@ public AttackPositions(final Map pieces) { } public void update(final Position source, final Position target, final Piece piece) { - Collection previousAttackPositions = piece.findAvailableAttackPositions(source); + remove(source, piece); + add(target, piece); + } + + public void remove(final Position position, final Piece piece) { + Collection previousAttackPositions = piece.findAvailableAttackPositions(position); previousAttackPositions.forEach(this::decrease); - Collection currentAttackPositions = piece.findAvailableAttackPositions(target); + } + + private void add(final Position position, final Piece piece) { + Collection currentAttackPositions = piece.findAvailableAttackPositions(position); currentAttackPositions.forEach(this::increase); } - private Integer increase(final Position target) { - return counts.put(target, counts.getOrDefault(target, 0) + 1); + private void decrease(final Position position) { + counts.put(position, counts.get(position) - 1); } - private Integer decrease(final Position position) { - return counts.put(position, counts.get(position) - 1); + private void increase(final Position position) { + counts.put(position, counts.getOrDefault(position, 0) + 1); } public boolean isEmpty(final Position position) { return !counts.containsKey(position) || (counts.get(position) == EMPTY); } - public boolean contains(Position target) { - return counts.containsKey(target); + public boolean contains(final Position position) { + return counts.containsKey(position); } } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index deaf4dc..d6ef108 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -1,13 +1,19 @@ package chess.domain.player; +import chess.domain.board.File; import chess.domain.board.Position; import chess.domain.piece.Color; import chess.domain.piece.Piece; import chess.domain.piece.PieceFactory; +import chess.domain.piece.mapper.PawnMapper; +import chess.domain.piece.mapper.PieceMappers; +import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class Player { @@ -19,6 +25,37 @@ public Player(final Color color) { attackPositions = new AttackPositions(pieces); } + public double sumScores() { + List pawnPositions = pieces.keySet() + .stream() + .filter(position -> { + Piece piece = pieces.get(position); + return piece.isPawn(); + }) + .collect(Collectors.toList()); + + double pawnScores = calculatePawnScores(pawnPositions); + double sum = pieces.values() + .stream() + .filter(piece -> !piece.isPawn()) + .mapToDouble(PieceMappers::findScoreBy) + .sum(); + return pawnScores + sum; + } + + private double calculatePawnScores(final List pawnPositions) { + Map pawnCount = new EnumMap<>(File.class); + + pawnPositions.stream() + .map(Position::getFile) + .forEach(file -> pawnCount.put(file, pawnCount.getOrDefault(file, 0) + 1)); + + return pawnCount.values() + .stream() + .mapToDouble(PawnMapper::calculate) + .sum(); + } + public boolean hasPieceOn(final Position position) { return pieces.containsKey(position); } @@ -54,4 +91,13 @@ public boolean hasKingOn(Position position) { public boolean canAttack(Position position) { return attackPositions.contains(position); } + + public void attacked(final Position target) { + if (!hasPieceOn(target)) { + return; + } + + attackPositions.remove(target, pieces.get(target)); + pieces.remove(target); + } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 37d805b..cbe225a 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -120,6 +120,29 @@ void move_king_invalid_target(String source, String target) { .withMessage("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } + @Test + @DisplayName("모든 플레이어의 점수를 반환한다.") + void get_status() { + //given + Board board = setBoardToGetStatus(); + + //when + Status status = board.getStatus(); + + //then + assertThat(status.getWhiteScore()).isEqualTo(37); + assertThat(status.getBlackScore()).isEqualTo(37); + } + + private Board setBoardToGetStatus() { + Board board = new Board(); + board.move(new MoveParameters(Position.of("e7"), Position.of("e5")), false); + board.move(new MoveParameters(Position.of("e5"), Position.of("e4")), false); + board.move(new MoveParameters(Position.of("e4"), Position.of("e3")), false); + board.move(new MoveParameters(Position.of("d2"), Position.of("e3")), true); + return board; + } + private Board setBoardToAttackKing() { Board board = new Board(); board.move(new MoveParameters(Position.of("e2"), Position.of("e4")), true); diff --git a/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java b/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java index 6582ff8..b04265f 100644 --- a/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java +++ b/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java @@ -1,8 +1,12 @@ package chess.domain.piece.mapper; +import chess.domain.piece.Bishop; import chess.domain.piece.Color; +import chess.domain.piece.Knight; import chess.domain.piece.Pawn; import chess.domain.piece.Piece; +import chess.domain.piece.Queen; +import chess.domain.piece.Rook; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -15,21 +19,41 @@ public class PieceMappersTest { @ParameterizedTest - @MethodSource("createParams") + @MethodSource("createParamsForName") @DisplayName("피스가 주어지면 해당 피스와 색상에 맞는 이름을 반환한다.") - void find(Piece piece, String expected) { + void find_name_by(Piece piece, String expected) { //given, when String name = PieceMappers.findNameBy(piece); //then assertThat(name).isEqualTo(expected); + } + @ParameterizedTest + @MethodSource("createParamsForScore") + @DisplayName("피스가 주어지면 해당 피스의 점수를 반환한다.") + void get_score(Piece piece, double expected) { + //given, when + double score = PieceMappers.findScoreBy(piece); + + //then + assertThat(score).isEqualTo(expected); } - private static Stream createParams() { + private static Stream createParamsForName() { return Stream.of( Arguments.of(new Pawn(Color.WHITE), "p"), Arguments.of(new Pawn(Color.BLACK), "P") ); } + + private static Stream createParamsForScore() { + return Stream.of( + Arguments.of(new Pawn(Color.WHITE), 1), + Arguments.of(new Knight(Color.WHITE), 2.5), + Arguments.of(new Bishop(Color.WHITE), 3), + Arguments.of(new Rook(Color.WHITE), 5), + Arguments.of(new Queen(Color.WHITE), 9) + ); + } } diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index 9689e09..ce1d887 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -3,6 +3,7 @@ import chess.domain.board.Position; import chess.domain.piece.Color; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -108,4 +109,17 @@ void can_attack(Color color, String attackPosition, String notAttackPosition) { assertThat(can).isTrue(); assertThat(cannot).isFalse(); } + + @Test + @DisplayName("현재 남아있는 피스의 점수 합을 구한다.") + void sum_scores() { + //given + Player player = new Player(Color.WHITE); + + //when + double sum = player.sumScores(); + + //then + assertThat(sum).isEqualTo(38); + } } From be1d67e9234de3c2fba475571a211b91da1225cd Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 21 Aug 2021 02:11:30 +0900 Subject: [PATCH 21/38] =?UTF-8?q?feat:=20=EC=A0=90=EC=88=98=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=8C=EC=9E=84=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/controller/ChessController.java | 36 +++++++++++-- src/main/java/chess/domain/ChessGame.java | 30 ++++++----- src/main/java/chess/domain/board/Board.java | 6 ++- src/main/java/chess/domain/board/Status.java | 14 +++++- .../java/chess/domain/command/Command.java | 5 ++ src/main/java/chess/domain/player/Player.java | 11 ++-- .../java/chess/view/ConsoleOutputView.java | 29 +++++++++++ src/main/java/chess/view/OutputView.java | 5 ++ src/test/java/chess/domain/ChessGameTest.java | 50 ------------------- .../java/chess/domain/player/PlayerTest.java | 14 ++++++ 10 files changed, 124 insertions(+), 76 deletions(-) delete mode 100644 src/test/java/chess/domain/ChessGameTest.java diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ChessController.java index afab9a1..68dce81 100644 --- a/src/main/java/chess/controller/ChessController.java +++ b/src/main/java/chess/controller/ChessController.java @@ -2,6 +2,7 @@ import chess.controller.dto.BoardDto; import chess.domain.ChessGame; +import chess.domain.board.Status; import chess.domain.command.Command; import chess.view.InputView; import chess.view.OutputView; @@ -22,6 +23,7 @@ public void run() { while (chessGame.isRunning()) { try { + outputView.printTurn(chessGame.isWhiteTurn()); Command command = new Command(inputView.getCommand()); runCommand(chessGame, command); printBoard(chessGame); @@ -29,18 +31,46 @@ public void run() { System.out.println(e.getMessage()); } } + + printBoard(chessGame); + printStatus(chessGame); } - private void runCommand(ChessGame chessGame, Command command) { + private void runCommand(final ChessGame chessGame, final Command command) { try { - chessGame.run(command); + if (command.isStart()) { + return; + } + + if (command.isEnd()) { + chessGame.end(); + return; + } + + if (command.isMove()) { + chessGame.move(command.getMoveParameters()); + return; + } + + if (command.isStatus()) { + printStatus(chessGame); + return; + } } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); + return; } + + throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); } - private void printBoard(ChessGame chessGame) { + private void printBoard(final ChessGame chessGame) { BoardDto boardDto = new BoardDto(chessGame.getBoard()); outputView.printBoard(boardDto); } + + private void printStatus(final ChessGame chessGame) { + Status status = chessGame.getStatus(); + outputView.printStatus(status); + } } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index 656ed90..0828ad7 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -1,7 +1,7 @@ package chess.domain; import chess.domain.board.Board; -import chess.domain.command.Command; +import chess.domain.board.Status; import chess.domain.command.MoveParameters; public class ChessGame { @@ -13,27 +13,21 @@ public class ChessGame { public ChessGame() { } - public void run(final Command command) { - if (command.isStart()) { - return; - } + public void move(final MoveParameters moveParameters) { + board.move(moveParameters, isWhiteTurn); + isWhiteTurn = !isWhiteTurn; - if (command.isEnd()) { + if (board.isEnd()) { isRunning = false; - return; - } - - if (command.isMove()) { - move(command.getMoveParameters()); - return; } + } - throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); + public void end() { + isRunning = false; } - private void move(final MoveParameters moveParameters) { - board.move(moveParameters, isWhiteTurn); - isWhiteTurn = !isWhiteTurn; + public Status getStatus() { + return board.getStatus(); } public boolean isRunning() { @@ -43,4 +37,8 @@ public boolean isRunning() { public Board getBoard() { return board; } + + public boolean isWhiteTurn() { + return isWhiteTurn; + } } diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 68153c5..e8b91a3 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -96,6 +96,10 @@ public Status getStatus() { double whiteScore = white.sumScores(); double blackScore = black.sumScores(); - return new Status(whiteScore, blackScore); + return new Status(whiteScore, blackScore, white.isKingDead(), black.isKingDead()); + } + + public boolean isEnd() { + return white.isKingDead() || black.isKingDead(); } } diff --git a/src/main/java/chess/domain/board/Status.java b/src/main/java/chess/domain/board/Status.java index 730c931..b48f253 100644 --- a/src/main/java/chess/domain/board/Status.java +++ b/src/main/java/chess/domain/board/Status.java @@ -3,10 +3,14 @@ public class Status { private final double whiteScore; private final double blackScore; + private final boolean isWhiteKingDead; + private final boolean isBlackKingDead; - public Status(final double whiteScore, final double blackScore) { + public Status(final double whiteScore, final double blackScore, boolean isWhiteKingDead, boolean isBlackKingDead) { this.whiteScore = whiteScore; this.blackScore = blackScore; + this.isWhiteKingDead = isWhiteKingDead; + this.isBlackKingDead = isBlackKingDead; } public double getWhiteScore() { @@ -16,4 +20,12 @@ public double getWhiteScore() { public double getBlackScore() { return blackScore; } + + public boolean isWhiteKingDead() { + return isWhiteKingDead; + } + + public boolean isBlackKingDead() { + return isBlackKingDead; + } } diff --git a/src/main/java/chess/domain/command/Command.java b/src/main/java/chess/domain/command/Command.java index ddea82a..5aff42b 100644 --- a/src/main/java/chess/domain/command/Command.java +++ b/src/main/java/chess/domain/command/Command.java @@ -12,6 +12,7 @@ public class Command { private static final String START = "start"; private static final String END = "end"; private static final String MOVE = "move"; + private static final String STATUS = "status"; private final String command; private final List parameters; @@ -42,6 +43,10 @@ public boolean isMove() { return command.equals(MOVE); } + public boolean isStatus() { + return command.equals(STATUS); + } + public MoveParameters getMoveParameters() { if (parameters.size() != 2) { throw new IllegalArgumentException(); diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index d6ef108..c2acd82 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -8,11 +8,7 @@ import chess.domain.piece.mapper.PawnMapper; import chess.domain.piece.mapper.PieceMappers; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class Player { @@ -88,6 +84,11 @@ public boolean hasKingOn(Position position) { return findPieceBy(position).isKing(); } + public boolean isKingDead() { + return pieces.values().stream() + .noneMatch(Piece::isKing); + } + public boolean canAttack(Position position) { return attackPositions.contains(position); } diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index b20ba82..4720828 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,10 +1,13 @@ package chess.view; import chess.controller.dto.BoardDto; +import chess.domain.board.Status; public class ConsoleOutputView implements OutputView { private static final String HEADER = "> "; + private static final String TURN_FORMAT = HEADER + "%s의 차례입니다.%n"; + private static final String WINNER_FORMAT = HEADER + "%s의 승리입니다. 축하합니다.%n"; @Override public void printGuide() { @@ -25,4 +28,30 @@ public void printBoard(final BoardDto boardDto) { }); System.out.println(); } + + @Override + public void printStatus(final Status status) { + if (status.isWhiteKingDead()) { + System.out.printf(WINNER_FORMAT, "BLACK"); + return; + } + + if (status.isBlackKingDead()) { + System.out.printf(WINNER_FORMAT, "WHITE"); + return; + } + + System.out.println(HEADER + "WHITE 점수: " + status.getWhiteScore()); + System.out.println(HEADER + "BLACK 점수: " + status.getBlackScore()); + } + + @Override + public void printTurn(boolean isWhiteTurn) { + if (isWhiteTurn) { + System.out.printf(TURN_FORMAT, "WHITE"); + return; + } + + System.out.printf(TURN_FORMAT, "BLACK"); + } } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 7185de0..50c37bf 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,9 +1,14 @@ package chess.view; import chess.controller.dto.BoardDto; +import chess.domain.board.Status; public interface OutputView { void printGuide(); void printBoard(BoardDto boardDto); + + void printStatus(Status status); + + void printTurn(boolean isWhiteTurn); } diff --git a/src/test/java/chess/domain/ChessGameTest.java b/src/test/java/chess/domain/ChessGameTest.java deleted file mode 100644 index 54a43d9..0000000 --- a/src/test/java/chess/domain/ChessGameTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package chess.domain; - -import chess.domain.command.Command; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class ChessGameTest { - - @Test - @DisplayName("게임 시작 명령어를 입력받을 경우 게임을 시작한다.") - void run_start_command() { - //given - ChessGame chessGame = new ChessGame(); - Command command = new Command("start"); - - //when - chessGame.run(command); - - //then - assertThat(chessGame.isRunning()).isTrue(); - } - - @Test - @DisplayName("게임 종료 명령어를 입력받을 경우 게임을 종료한다.") - void run_end_command() { - //given - ChessGame chessGame = new ChessGame(); - Command command = new Command("end"); - - //when - chessGame.run(command); - - //then - assertThat(chessGame.isRunning()).isFalse(); - } - - @Test - @DisplayName("유효하지 않은 명령어를 입력받을 경우 예외를 던진다.") - void run_invalid_command() { - //given - ChessGame chessGame = new ChessGame(); - Command command = new Command("invalid_command"); - - //when, then - assertThrows(UnsupportedOperationException.class, () -> chessGame.run(command)); - } -} diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index ce1d887..75882b6 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -122,4 +122,18 @@ void sum_scores() { //then assertThat(sum).isEqualTo(38); } + + @Test + @DisplayName("킹이 존재하는지 반환한다.") + void is_king_dead() { + //given + Player player = new Player(Color.WHITE); + player.attacked(Position.of("e1")); + + //when + boolean kingDead = player.isKingDead(); + + //then + assertThat(kingDead).isTrue(); + } } From aba4a3ad04d7a3f8ec5f3aff9d4a74937ab964cf Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 21 Aug 2021 03:18:21 +0900 Subject: [PATCH 22/38] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../java/chess/controller/dto/BoardDto.java | 6 +- src/main/java/chess/domain/board/Board.java | 1 + .../chess/domain/command/MoveParameters.java | 2 +- src/main/java/chess/domain/piece/Bishop.java | 2 +- src/main/java/chess/domain/piece/Color.java | 1 - src/main/java/chess/domain/piece/King.java | 2 +- src/main/java/chess/domain/piece/Knight.java | 2 +- src/main/java/chess/domain/piece/Pawn.java | 10 +- src/main/java/chess/domain/piece/Piece.java | 13 +-- .../java/chess/domain/piece/PieceFactory.java | 2 +- src/main/java/chess/domain/piece/Queen.java | 2 +- src/main/java/chess/domain/piece/Rook.java | 2 +- .../domain/piece/mapper/BishopMapper.java | 19 ---- .../chess/domain/piece/mapper/KingMapper.java | 19 ---- .../domain/piece/mapper/KnightMapper.java | 19 ---- .../chess/domain/piece/mapper/PawnMapper.java | 29 ----- .../domain/piece/mapper/PieceMapper.java | 27 ----- .../domain/piece/mapper/PieceMappers.java | 43 ------- .../domain/piece/mapper/QueenMapper.java | 19 ---- .../chess/domain/piece/mapper/RookMapper.java | 19 ---- .../piece/{pattern => type}/MovePattern.java | 33 +----- .../chess/domain/piece/type/PieceType.java | 74 ++++++++++++ .../chess/domain/player/AttackPositions.java | 1 - .../{piece => player}/MoveCoordinate.java | 8 +- src/main/java/chess/domain/player/Player.java | 39 ++++--- .../domain/{board => player}/Position.java | 40 +++---- .../java/chess/domain/board/BoardTest.java | 8 +- .../java/chess/domain/board/PositionTest.java | 40 ------- .../java/chess/domain/piece/BishopTest.java | 2 +- .../java/chess/domain/piece/KingTest.java | 5 +- .../java/chess/domain/piece/KnightTest.java | 5 +- .../java/chess/domain/piece/PawnTest.java | 17 +-- .../java/chess/domain/piece/PieceTest.java | 13 --- .../java/chess/domain/piece/QueenTest.java | 7 +- .../java/chess/domain/piece/RookTest.java | 2 +- .../{pattern => type}/MovePatternTest.java | 25 +---- .../PieceTypeTest.java} | 56 +++++++-- .../domain/player/AttackPositionsTest.java | 5 +- .../java/chess/domain/player/PlayerTest.java | 1 - .../chess/domain/player/PositionTest.java | 106 ++++++++++++++++++ 41 files changed, 323 insertions(+), 407 deletions(-) delete mode 100644 src/main/java/chess/domain/piece/mapper/BishopMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/KingMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/KnightMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/PawnMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/PieceMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/PieceMappers.java delete mode 100644 src/main/java/chess/domain/piece/mapper/QueenMapper.java delete mode 100644 src/main/java/chess/domain/piece/mapper/RookMapper.java rename src/main/java/chess/domain/piece/{pattern => type}/MovePattern.java (75%) create mode 100644 src/main/java/chess/domain/piece/type/PieceType.java rename src/main/java/chess/domain/{piece => player}/MoveCoordinate.java (92%) rename src/main/java/chess/domain/{board => player}/Position.java (81%) delete mode 100644 src/test/java/chess/domain/board/PositionTest.java rename src/test/java/chess/domain/piece/{pattern => type}/MovePatternTest.java (50%) rename src/test/java/chess/domain/piece/{mapper/PieceMappersTest.java => type/PieceTypeTest.java} (56%) create mode 100644 src/test/java/chess/domain/player/PositionTest.java diff --git a/README.md b/README.md index 4cf8095..3cb26bf 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ - [x] 입력 - [x] 명령어 입력 -- [] 출력 +- [x] 출력 - [x] 시작 안내 문구 출력 - [x] 체스판 전체 출력 - - [ ] 점수와 결과 출력 + - [x] 점수와 결과 출력 - [x] 체스게임(ChessGame) - [x] 체스판 diff --git a/src/main/java/chess/controller/dto/BoardDto.java b/src/main/java/chess/controller/dto/BoardDto.java index 31dd52c..a014e34 100644 --- a/src/main/java/chess/controller/dto/BoardDto.java +++ b/src/main/java/chess/controller/dto/BoardDto.java @@ -2,10 +2,10 @@ import chess.domain.board.Board; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.board.Rank; import chess.domain.piece.Piece; -import chess.domain.piece.mapper.PieceMappers; +import chess.domain.piece.type.PieceType; +import chess.domain.player.Position; import java.util.ArrayList; import java.util.Arrays; @@ -30,7 +30,7 @@ private void addPositionDto(final File file, final Rank rank, final Board board) } Piece piece = board.findBy(position); - String name = PieceMappers.findNameBy(piece); + String name = PieceType.findNameBy(piece); PositionDto positionDto = new PositionDto(position.getFile(), name); positionDtos.add(positionDto); } diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index e8b91a3..f0f7109 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -4,6 +4,7 @@ import chess.domain.piece.Color; import chess.domain.piece.Piece; import chess.domain.player.Player; +import chess.domain.player.Position; import java.util.Set; diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java index 198b266..c8a607a 100644 --- a/src/main/java/chess/domain/command/MoveParameters.java +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -1,6 +1,6 @@ package chess.domain.command; -import chess.domain.board.Position; +import chess.domain.player.Position; import java.util.List; diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java index 6265e9c..e6a7a6a 100644 --- a/src/main/java/chess/domain/piece/Bishop.java +++ b/src/main/java/chess/domain/piece/Bishop.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; public class Bishop extends Piece { diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/piece/Color.java index f61d576..c83450c 100644 --- a/src/main/java/chess/domain/piece/Color.java +++ b/src/main/java/chess/domain/piece/Color.java @@ -6,6 +6,5 @@ public enum Color { public boolean isWhite() { return this == WHITE; - } } diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java index 855d923..f3c7b73 100644 --- a/src/main/java/chess/domain/piece/King.java +++ b/src/main/java/chess/domain/piece/King.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; public class King extends Piece { diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java index 4ed9f31..14e65ee 100644 --- a/src/main/java/chess/domain/piece/Knight.java +++ b/src/main/java/chess/domain/piece/Knight.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; public class Knight extends Piece { diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index f72e35c..0d23f38 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,17 +1,15 @@ package chess.domain.piece; -import chess.domain.board.Position; import chess.domain.board.Rank; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; +import chess.domain.player.MoveCoordinate; +import chess.domain.player.Position; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; -import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; -import static chess.domain.piece.MoveCoordinate.NORTH; -import static chess.domain.piece.MoveCoordinate.SOUTH; -import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; +import static chess.domain.player.MoveCoordinate.*; public class Pawn extends Piece { diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index abfbd5f..1f72110 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -1,7 +1,8 @@ package chess.domain.piece; -import chess.domain.board.Position; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; +import chess.domain.player.MoveCoordinate; +import chess.domain.player.Position; import java.util.Collection; import java.util.HashSet; @@ -22,14 +23,6 @@ public boolean isWhite() { return color.isWhite(); } - public boolean isKing() { - return this instanceof King; - } - - public boolean isPawn() { - return this instanceof Pawn; - } - public Set findPath(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); diff --git a/src/main/java/chess/domain/piece/PieceFactory.java b/src/main/java/chess/domain/piece/PieceFactory.java index b3d23d3..4ee6f22 100644 --- a/src/main/java/chess/domain/piece/PieceFactory.java +++ b/src/main/java/chess/domain/piece/PieceFactory.java @@ -1,8 +1,8 @@ package chess.domain.piece; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.board.Rank; +import chess.domain.player.Position; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java index 7389d31..9c68242 100644 --- a/src/main/java/chess/domain/piece/Queen.java +++ b/src/main/java/chess/domain/piece/Queen.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; public class Queen extends Piece { diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java index 2e9f2c6..2a7bba0 100644 --- a/src/main/java/chess/domain/piece/Rook.java +++ b/src/main/java/chess/domain/piece/Rook.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.piece.pattern.MovePattern; +import chess.domain.piece.type.MovePattern; public class Rook extends Piece { diff --git a/src/main/java/chess/domain/piece/mapper/BishopMapper.java b/src/main/java/chess/domain/piece/mapper/BishopMapper.java deleted file mode 100644 index 6b06dc8..0000000 --- a/src/main/java/chess/domain/piece/mapper/BishopMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Bishop; -import chess.domain.piece.Piece; - -public class BishopMapper extends PieceMapper { - - private static final String NAME = "b"; - private static final double SCORE = 3; - - public BishopMapper() { - super(NAME, SCORE); - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof Bishop; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/KingMapper.java b/src/main/java/chess/domain/piece/mapper/KingMapper.java deleted file mode 100644 index e8d9904..0000000 --- a/src/main/java/chess/domain/piece/mapper/KingMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.King; -import chess.domain.piece.Piece; - -public class KingMapper extends PieceMapper { - - private static final String NAME = "k"; - private static final double SCORE = 0; - - public KingMapper() { - super(NAME, SCORE); - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof King; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/KnightMapper.java b/src/main/java/chess/domain/piece/mapper/KnightMapper.java deleted file mode 100644 index 6937fbd..0000000 --- a/src/main/java/chess/domain/piece/mapper/KnightMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Knight; -import chess.domain.piece.Piece; - -public class KnightMapper extends PieceMapper { - - private static final String NAME = "n"; - private static final double SCORE = 2.5; - - public KnightMapper() { - super(NAME, SCORE); - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof Knight; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/PawnMapper.java b/src/main/java/chess/domain/piece/mapper/PawnMapper.java deleted file mode 100644 index 4beffec..0000000 --- a/src/main/java/chess/domain/piece/mapper/PawnMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Pawn; -import chess.domain.piece.Piece; - -public class PawnMapper extends PieceMapper { - - private static final String NAME = "p"; - private static final int DUPLICATION_THRESHOLD = 1; - private static final double SCORE = 1; - private static final double SCORE_ON_DUPLICATION = 0.5; - - public PawnMapper() { - super(NAME, SCORE); - } - - public static double calculate(final int count) { - if (count > DUPLICATION_THRESHOLD) { - return count * SCORE_ON_DUPLICATION; - } - - return SCORE; - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof Pawn; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/PieceMapper.java b/src/main/java/chess/domain/piece/mapper/PieceMapper.java deleted file mode 100644 index 700aa07..0000000 --- a/src/main/java/chess/domain/piece/mapper/PieceMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Piece; - -public abstract class PieceMapper { - private final String name; - private final double score; - - PieceMapper(final String name, final double score) { - this.name = name; - this.score = score; - } - - abstract boolean supports(Piece piece); - - public String findNameBy(Piece piece) { - if (piece.isWhite()) { - return name; - } - - return name.toUpperCase(); - } - - public double getScore() { - return score; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/PieceMappers.java b/src/main/java/chess/domain/piece/mapper/PieceMappers.java deleted file mode 100644 index f69c56b..0000000 --- a/src/main/java/chess/domain/piece/mapper/PieceMappers.java +++ /dev/null @@ -1,43 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Piece; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -public class PieceMappers { - - private static final Collection pieceMappers = createMappers(); - - private static Collection createMappers() { - return Collections.unmodifiableCollection( - Arrays.asList( - new PawnMapper(), - new KnightMapper(), - new BishopMapper(), - new RookMapper(), - new QueenMapper(), - new KingMapper() - )); - } - - public static String findNameBy(final Piece piece) { - return pieceMappers.stream() - .filter(mapper -> mapper.supports(piece)) - .map(mapper -> mapper.findNameBy(piece)) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); - } - - public static double findScoreBy(final Piece piece) { - return pieceMappers.stream() - .filter(mapper -> mapper.supports(piece)) - .map(PieceMapper::getScore) - .findAny() - .orElseThrow(() -> new IllegalArgumentException("해당 기물에 해당하는 매퍼가 존재하지 않습니다.")); - } - - private PieceMappers() { - } -} diff --git a/src/main/java/chess/domain/piece/mapper/QueenMapper.java b/src/main/java/chess/domain/piece/mapper/QueenMapper.java deleted file mode 100644 index 1251829..0000000 --- a/src/main/java/chess/domain/piece/mapper/QueenMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Piece; -import chess.domain.piece.Queen; - -public class QueenMapper extends PieceMapper { - - private static final String NAME = "q"; - private static final double SCORE = 9; - - public QueenMapper() { - super(NAME, SCORE); - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof Queen; - } -} diff --git a/src/main/java/chess/domain/piece/mapper/RookMapper.java b/src/main/java/chess/domain/piece/mapper/RookMapper.java deleted file mode 100644 index c7239b2..0000000 --- a/src/main/java/chess/domain/piece/mapper/RookMapper.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Piece; -import chess.domain.piece.Rook; - -public class RookMapper extends PieceMapper { - - private static final String NAME = "r"; - private static final double SCORE = 5; - - public RookMapper() { - super(NAME, SCORE); - } - - @Override - public boolean supports(final Piece piece) { - return piece instanceof Rook; - } -} diff --git a/src/main/java/chess/domain/piece/pattern/MovePattern.java b/src/main/java/chess/domain/piece/type/MovePattern.java similarity index 75% rename from src/main/java/chess/domain/piece/pattern/MovePattern.java rename to src/main/java/chess/domain/piece/type/MovePattern.java index 9f82c26..9c926da 100644 --- a/src/main/java/chess/domain/piece/pattern/MovePattern.java +++ b/src/main/java/chess/domain/piece/type/MovePattern.java @@ -1,32 +1,11 @@ -package chess.domain.piece.pattern; +package chess.domain.piece.type; import chess.domain.piece.Color; -import chess.domain.piece.MoveCoordinate; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; -import static chess.domain.piece.MoveCoordinate.EAST; -import static chess.domain.piece.MoveCoordinate.NORTH; -import static chess.domain.piece.MoveCoordinate.NORTH_EAST; -import static chess.domain.piece.MoveCoordinate.NORTH_EAST_LEFT; -import static chess.domain.piece.MoveCoordinate.NORTH_EAST_RIGHT; -import static chess.domain.piece.MoveCoordinate.NORTH_WEST; -import static chess.domain.piece.MoveCoordinate.NORTH_WEST_LEFT; -import static chess.domain.piece.MoveCoordinate.NORTH_WEST_RIGHT; -import static chess.domain.piece.MoveCoordinate.SOUTH; -import static chess.domain.piece.MoveCoordinate.SOUTH_EAST; -import static chess.domain.piece.MoveCoordinate.SOUTH_EAST_LEFT; -import static chess.domain.piece.MoveCoordinate.SOUTH_EAST_RIGHT; -import static chess.domain.piece.MoveCoordinate.SOUTH_WEST; -import static chess.domain.piece.MoveCoordinate.SOUTH_WEST_LEFT; -import static chess.domain.piece.MoveCoordinate.SOUTH_WEST_RIGHT; -import static chess.domain.piece.MoveCoordinate.WEST; -import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; +import chess.domain.player.MoveCoordinate; + +import java.util.*; + +import static chess.domain.player.MoveCoordinate.*; public class MovePattern { diff --git a/src/main/java/chess/domain/piece/type/PieceType.java b/src/main/java/chess/domain/piece/type/PieceType.java new file mode 100644 index 0000000..e62961b --- /dev/null +++ b/src/main/java/chess/domain/piece/type/PieceType.java @@ -0,0 +1,74 @@ +package chess.domain.piece.type; + +import chess.domain.piece.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public enum PieceType { + + KING("k", 0), + QUEEN("q", 9), + ROOK("r", 5), + BISHOP("b", 3), + KNIGHT("n", 2.5), + PAWN("p", 1); + + private static final Map, PieceType> PIECE_TYPE_MAP = createPieceTypeMap(); + private static final int DUPLICATION_THRESHOLD = 1; + private static final double PAWN_SCORE_ON_DUPLICATION = 0.5; + private final String name; + private final double score; + + PieceType(final String name, final double score) { + this.name = name; + this.score = score; + } + + private static Map, PieceType> createPieceTypeMap() { + Map, PieceType> map = new HashMap<>(); + map.put(King.class, KING); + map.put(Queen.class, QUEEN); + map.put(Rook.class, ROOK); + map.put(Bishop.class, BISHOP); + map.put(Knight.class, KNIGHT); + map.put(Pawn.class, PAWN); + return Collections.unmodifiableMap(map); + } + + public static PieceType of(Piece piece) { + return PIECE_TYPE_MAP.get(piece.getClass()); + } + + public static String findNameBy(Piece piece) { + PieceType pieceType = PieceType.of(piece); + + if (piece.isWhite()) { + return pieceType.name; + } + + return pieceType.name.toUpperCase(); + } + + public static double findScoreBy(Piece piece) { + PieceType pieceType = PieceType.of(piece); + return pieceType.score; + } + + public static boolean isKing(Piece piece) { + return PieceType.of(piece) == KING; + } + + public static boolean isPawn(Piece piece) { + return PieceType.of(piece) == PAWN; + } + + public static double sumPawnScores(final int count) { + if (count > DUPLICATION_THRESHOLD) { + return count * PAWN_SCORE_ON_DUPLICATION; + } + + return PAWN.score; + } +} diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index b02d974..2007382 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -1,6 +1,5 @@ package chess.domain.player; -import chess.domain.board.Position; import chess.domain.piece.Piece; import java.util.Collection; diff --git a/src/main/java/chess/domain/piece/MoveCoordinate.java b/src/main/java/chess/domain/player/MoveCoordinate.java similarity index 92% rename from src/main/java/chess/domain/piece/MoveCoordinate.java rename to src/main/java/chess/domain/player/MoveCoordinate.java index 7cc93d0..59545f0 100644 --- a/src/main/java/chess/domain/piece/MoveCoordinate.java +++ b/src/main/java/chess/domain/player/MoveCoordinate.java @@ -1,6 +1,4 @@ -package chess.domain.piece; - -import static java.lang.Math.abs; +package chess.domain.player; public enum MoveCoordinate { NORTH_EAST(1, 1), @@ -64,10 +62,6 @@ private boolean hasSameSign(final int fileGap, final int rankGap) { return (file ^ fileGap) >= 0 && (rank ^ rankGap) >= 0; } - public int countMove() { - return abs(this.file) + abs(this.rank); - } - public int getFile() { return file; } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index c2acd82..4710c52 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -1,12 +1,10 @@ package chess.domain.player; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.piece.Color; import chess.domain.piece.Piece; import chess.domain.piece.PieceFactory; -import chess.domain.piece.mapper.PawnMapper; -import chess.domain.piece.mapper.PieceMappers; +import chess.domain.piece.type.PieceType; import java.util.*; import java.util.stream.Collectors; @@ -22,33 +20,40 @@ public Player(final Color color) { } public double sumScores() { - List pawnPositions = pieces.keySet() + List pawnPositions = findPawnPositions(); + + double pawnScores = calculatePawnScores(pawnPositions); + double scoresExceptPawn = calculateScoresExceptPawn(); + return pawnScores + scoresExceptPawn; + } + + private List findPawnPositions() { + return pieces.keySet() .stream() .filter(position -> { Piece piece = pieces.get(position); - return piece.isPawn(); + return PieceType.isPawn(piece); }) .collect(Collectors.toList()); - - double pawnScores = calculatePawnScores(pawnPositions); - double sum = pieces.values() - .stream() - .filter(piece -> !piece.isPawn()) - .mapToDouble(PieceMappers::findScoreBy) - .sum(); - return pawnScores + sum; } private double calculatePawnScores(final List pawnPositions) { Map pawnCount = new EnumMap<>(File.class); - pawnPositions.stream() .map(Position::getFile) .forEach(file -> pawnCount.put(file, pawnCount.getOrDefault(file, 0) + 1)); return pawnCount.values() .stream() - .mapToDouble(PawnMapper::calculate) + .mapToDouble(PieceType::sumPawnScores) + .sum(); + } + + private double calculateScoresExceptPawn() { + return pieces.values() + .stream() + .filter(piece -> !PieceType.isPawn(piece)) + .mapToDouble(PieceType::findScoreBy) .sum(); } @@ -81,12 +86,12 @@ public Piece findPieceBy(final Position position) { } public boolean hasKingOn(Position position) { - return findPieceBy(position).isKing(); + return PieceType.isKing(findPieceBy(position)); } public boolean isKingDead() { return pieces.values().stream() - .noneMatch(Piece::isKing); + .noneMatch(PieceType::isKing); } public boolean canAttack(Position position) { diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/player/Position.java similarity index 81% rename from src/main/java/chess/domain/board/Position.java rename to src/main/java/chess/domain/player/Position.java index 8b32741..ee973b6 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/player/Position.java @@ -1,14 +1,9 @@ -package chess.domain.board; +package chess.domain.player; -import chess.domain.piece.MoveCoordinate; +import chess.domain.board.File; +import chess.domain.board.Rank; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; public class Position { private static final Map POSITIONS = createPositions(); @@ -17,7 +12,8 @@ private static Map createPositions() { Map positions = new LinkedHashMap<>(); Arrays.stream(File.values()) - .forEach(file -> Arrays.stream(Rank.values()).forEach(rank -> positions.put(createKey(file, rank), new Position(file, rank)))); + .forEach(file -> Arrays.stream(Rank.values()) + .forEach(rank -> positions.put(createKey(file, rank), new Position(file, rank)))); return positions; } @@ -42,20 +38,12 @@ private Position(final File file, final Rank rank) { this.rank = rank; } - public File getFile() { - return file; - } - - public Rank getRank() { - return rank; - } - - public int calculateFileGap(final Position source) { - return file.calculateGap(source.getFile()); + public int calculateFileGap(final Position position) { + return file.calculateGap(position.getFile()); } - public int calculateRankGap(final Position source) { - return rank.calculateGap(source.getRank()); + public int calculateRankGap(final Position position) { + return rank.calculateGap(position.getRank()); } public Set findPassingPositions(Position target, MoveCoordinate moveCoordinate) { @@ -110,4 +98,12 @@ private Position move(final MoveCoordinate moveCoordinate) { public boolean hasSameRank(Rank rank) { return this.rank == rank; } + + public File getFile() { + return file; + } + + public Rank getRank() { + return rank; + } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index cbe225a..2585f02 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,7 +1,7 @@ package chess.domain.board; import chess.domain.command.MoveParameters; -import chess.domain.piece.Pawn; +import chess.domain.player.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -23,12 +23,12 @@ void create() { Board board = new Board(); //then - assertThat(board.findBy(pawnPosition)).isInstanceOf(Pawn.class); + assertThat(board.isEmpty(pawnPosition)).isFalse(); assertThat(board.isEmpty(emptyPosition)).isTrue(); } @Test - @DisplayName("인자로 받은 시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") + @DisplayName("시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") void move_source_position_empty() { //given Board board = new Board(); @@ -108,7 +108,7 @@ void move_invalid_paths(String sourcePosition, String targetPosition, boolean is @ParameterizedTest @CsvSource({"e2, d2", "e2, e1"}) - @DisplayName("킹 도착지를 상대방이 공격 가능한 경우 예외가 발생한다.") + @DisplayName("상대방이 킹의 목적지를 공격 가능한 경우 예외가 발생한다.") void move_king_invalid_target(String source, String target) { //given Board board = setBoardToAttackKing(); diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java deleted file mode 100644 index e30fada..0000000 --- a/src/test/java/chess/domain/board/PositionTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package chess.domain.board; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class PositionTest { - - @Test - @DisplayName("가로, 세로 인자에 해당하는 위치를 반환한다.") - void from_file_and_rank() { - //given - File file = File.a; - Rank rank = Rank.R1; - - //when - Position position = Position.from(file, rank); - - //then - assertThat(position).extracting("file", "rank") - .containsOnly(file, rank); - } - - @Test - @DisplayName("문자열 키에 해당하는 위치를 반환한다.") - void from_key() { - //given - File file = File.a; - Rank rank = Rank.R1; - String key = file.name() + rank.getIndex(); - - //when - Position position = Position.of(key); - - //then - assertThat(position).extracting("file", "rank") - .containsOnly(file, rank); - } -} diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index e8080f3..64eb8b5 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -1,8 +1,8 @@ package chess.domain.piece; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.board.Rank; +import chess.domain.player.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java index ce71a38..2dc762e 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.board.Position; +import chess.domain.player.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -41,7 +41,8 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new King(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @Test diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java index 3979525..2629bcc 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.board.Position; +import chess.domain.player.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -41,7 +41,8 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new Knight(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @Test diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java index 16993f1..3d702ed 100644 --- a/src/test/java/chess/domain/piece/PawnTest.java +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.board.Position; +import chess.domain.player.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -16,8 +16,7 @@ class PawnTest { @ParameterizedTest - @CsvSource({"b2, b3, WHITE", "b2, a3, WHITE", "b2, c3, WHITE", - "b7, b6, BLACK", "b7, a6, BLACK", "b7, c6, BLACK"}) + @CsvSource({"b2, b3, WHITE", "b7, b6, BLACK"}) @DisplayName("최초 이동 시 1칸 전진한다.") void find_paths_success_move_count_one_on_initial_move(String sourcePosition, String targetPosition, Color color) { //given @@ -75,12 +74,12 @@ void find_paths_fail_move_invalid_count_on_initial_move(String sourcePosition, S Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @ParameterizedTest - @CsvSource({"d4, d6, WHITE", "d4, c6, WHITE", "d4, e6, WHITE", - "d4, d2, BLACK", "d4, c2, BLACK", "d4, e2, BLACK"}) + @CsvSource({"d4, d6, WHITE", "d4, d2, BLACK"}) @DisplayName("최초 이동이 아닌 경우 1칸 초과 전진하면 예외가 발생한다.") void find_paths_fail_move_invalid_count(String sourcePosition, String targetPosition, Color color) { //given @@ -89,7 +88,8 @@ void find_paths_fail_move_invalid_count(String sourcePosition, String targetPosi Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @ParameterizedTest @@ -102,7 +102,8 @@ void find_paths_fail_move_backward(String sourcePosition, String targetPosition, Piece piece = new Pawn(color); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @Test diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java index 345017a..e4659fe 100644 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ b/src/test/java/chess/domain/piece/PieceTest.java @@ -1,7 +1,6 @@ package chess.domain.piece; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -19,16 +18,4 @@ void create(Color color, boolean expected) { //then assertThat(piece.isWhite()).isEqualTo(expected); } - - @Test - @DisplayName("킹인지 확인한다.") - void is_king() { - // given - Piece king = new King(Color.WHITE); - Piece queen = new Queen(Color.WHITE); - - // when, then - assertThat(king.isKing()).isTrue(); - assertThat(queen.isKing()).isFalse(); - } } diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java index b1f85e6..67b1ca9 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -1,8 +1,8 @@ package chess.domain.piece; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.board.Rank; +import chess.domain.player.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -42,7 +42,7 @@ void find_paths_success_diagonal(String targetPosition, Tuple expected) { @ParameterizedTest @ValueSource(strings = {"d5", "c4", "e4", "d3"}) @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success_straight(String targetPosition) { + void find_paths_success_cardinal(String targetPosition) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); @@ -65,7 +65,8 @@ void find_paths_invalid_target(String invalidTarget) { Piece piece = new Queen(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException() + .isThrownBy(() -> piece.findPath(source, target)); } @Test diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java index a787a58..4331349 100644 --- a/src/test/java/chess/domain/piece/RookTest.java +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -1,8 +1,8 @@ package chess.domain.piece; import chess.domain.board.File; -import chess.domain.board.Position; import chess.domain.board.Rank; +import chess.domain.player.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/piece/pattern/MovePatternTest.java b/src/test/java/chess/domain/piece/type/MovePatternTest.java similarity index 50% rename from src/test/java/chess/domain/piece/pattern/MovePatternTest.java rename to src/test/java/chess/domain/piece/type/MovePatternTest.java index d94db2b..a45bb0e 100644 --- a/src/test/java/chess/domain/piece/pattern/MovePatternTest.java +++ b/src/test/java/chess/domain/piece/type/MovePatternTest.java @@ -1,20 +1,13 @@ -package chess.domain.piece.pattern; +package chess.domain.piece.type; import chess.domain.piece.Color; -import chess.domain.piece.MoveCoordinate; +import chess.domain.player.MoveCoordinate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Collection; -import static chess.domain.piece.MoveCoordinate.BLACK_PAWN_INITIAL_SOUTH; -import static chess.domain.piece.MoveCoordinate.NORTH; -import static chess.domain.piece.MoveCoordinate.NORTH_EAST; -import static chess.domain.piece.MoveCoordinate.NORTH_WEST; -import static chess.domain.piece.MoveCoordinate.SOUTH; -import static chess.domain.piece.MoveCoordinate.SOUTH_EAST; -import static chess.domain.piece.MoveCoordinate.SOUTH_WEST; -import static chess.domain.piece.MoveCoordinate.WHITE_PAWN_INITIAL_NORTH; +import static chess.domain.player.MoveCoordinate.*; import static org.assertj.core.api.Assertions.assertThat; class MovePatternTest { @@ -31,11 +24,7 @@ void pawn_pattern_black() { // then assertThat(moveCoordinates) - .containsExactlyInAnyOrder( - BLACK_PAWN_INITIAL_SOUTH, - SOUTH_EAST, - SOUTH_WEST, - SOUTH); + .containsExactlyInAnyOrder(BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH); } @Test @@ -50,10 +39,6 @@ void pawn_pattern_white() { // then assertThat(moveCoordinates) - .containsExactlyInAnyOrder( - WHITE_PAWN_INITIAL_NORTH, - NORTH_EAST, - NORTH_WEST, - NORTH); + .containsExactlyInAnyOrder(WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH); } } diff --git a/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java b/src/test/java/chess/domain/piece/type/PieceTypeTest.java similarity index 56% rename from src/test/java/chess/domain/piece/mapper/PieceMappersTest.java rename to src/test/java/chess/domain/piece/type/PieceTypeTest.java index b04265f..8c1acd7 100644 --- a/src/test/java/chess/domain/piece/mapper/PieceMappersTest.java +++ b/src/test/java/chess/domain/piece/type/PieceTypeTest.java @@ -1,13 +1,8 @@ -package chess.domain.piece.mapper; - -import chess.domain.piece.Bishop; -import chess.domain.piece.Color; -import chess.domain.piece.Knight; -import chess.domain.piece.Pawn; -import chess.domain.piece.Piece; -import chess.domain.piece.Queen; -import chess.domain.piece.Rook; +package chess.domain.piece.type; + +import chess.domain.piece.*; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -16,14 +11,27 @@ import static org.assertj.core.api.Assertions.assertThat; -public class PieceMappersTest { +public class PieceTypeTest { + + @Test + @DisplayName("피스에 해당하는 피스 타입을 반환한다.") + void of() { + // given + Piece piece = new Pawn(Color.WHITE); + + // when + PieceType pieceType = PieceType.of(piece); + + // then + assertThat(pieceType).isSameAs(PieceType.PAWN); + } @ParameterizedTest @MethodSource("createParamsForName") @DisplayName("피스가 주어지면 해당 피스와 색상에 맞는 이름을 반환한다.") void find_name_by(Piece piece, String expected) { //given, when - String name = PieceMappers.findNameBy(piece); + String name = PieceType.findNameBy(piece); //then assertThat(name).isEqualTo(expected); @@ -34,12 +42,36 @@ void find_name_by(Piece piece, String expected) { @DisplayName("피스가 주어지면 해당 피스의 점수를 반환한다.") void get_score(Piece piece, double expected) { //given, when - double score = PieceMappers.findScoreBy(piece); + double score = PieceType.findScoreBy(piece); //then assertThat(score).isEqualTo(expected); } + @Test + @DisplayName("킹인지 확인한다.") + void is_king() { + // given + Piece king = new King(Color.WHITE); + Piece queen = new Queen(Color.WHITE); + + // when, then + assertThat(PieceType.isKing(king)).isTrue(); + assertThat(PieceType.isKing(queen)).isFalse(); + } + + @Test + @DisplayName("폰인지 확인한다.") + void is_pawn() { + // given + Piece pawn = new Pawn(Color.WHITE); + Piece queen = new Queen(Color.WHITE); + + // when, then + assertThat(PieceType.isPawn(pawn)).isTrue(); + assertThat(PieceType.isPawn(queen)).isFalse(); + } + private static Stream createParamsForName() { return Stream.of( Arguments.of(new Pawn(Color.WHITE), "p"), diff --git a/src/test/java/chess/domain/player/AttackPositionsTest.java b/src/test/java/chess/domain/player/AttackPositionsTest.java index b639bfe..bc9dc11 100644 --- a/src/test/java/chess/domain/player/AttackPositionsTest.java +++ b/src/test/java/chess/domain/player/AttackPositionsTest.java @@ -1,6 +1,5 @@ package chess.domain.player; -import chess.domain.board.Position; import chess.domain.piece.Color; import chess.domain.piece.Knight; import chess.domain.piece.Piece; @@ -18,7 +17,7 @@ class AttackPositionsTest { @ParameterizedTest @ValueSource(strings = {"a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3"}) - @DisplayName("기물들이 주어지면 공격 가능한 위치들이 초기화된다.") + @DisplayName("공격 가능한 위치들을 표시한다.") void create(String key) { //given Map pieces = PieceFactory.createPieces(Color.WHITE); @@ -33,7 +32,7 @@ void create(String key) { } @Test - @DisplayName("시작 위치에서의 공격 가능한 위치들을 없애고, 도착 위치에서의 공격 가능한 위치를 추가한다.") + @DisplayName("공격 가능한 위치들을 갱신한다.") void update() { //given Map pieces = PieceFactory.createPieces(Color.WHITE); diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index 75882b6..56d64aa 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -1,6 +1,5 @@ package chess.domain.player; -import chess.domain.board.Position; import chess.domain.piece.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/player/PositionTest.java b/src/test/java/chess/domain/player/PositionTest.java new file mode 100644 index 0000000..8c83bbc --- /dev/null +++ b/src/test/java/chess/domain/player/PositionTest.java @@ -0,0 +1,106 @@ +package chess.domain.player; + +import chess.domain.board.File; +import chess.domain.board.Rank; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class PositionTest { + + @Test + @DisplayName("가로, 세로 인자에 해당하는 위치를 반환한다.") + void from_file_and_rank() { + //given + File file = File.a; + Rank rank = Rank.R1; + + //when + Position position = Position.from(file, rank); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } + + @Test + @DisplayName("문자열 키에 해당하는 위치를 반환한다.") + void from_key() { + //given + File file = File.a; + Rank rank = Rank.R1; + String key = file.name() + rank.getIndex(); + + //when + Position position = Position.of(key); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } + + @Test + @DisplayName("거쳐가는 모든 위치를 반환한다.") + void find_passing_positions() { + // given + Position source = Position.of("d4"); + Position target = Position.of("d7"); + MoveCoordinate moveCoordinate = MoveCoordinate.NORTH; + + // when + Set positions = source.findPassingPositions(target, moveCoordinate); + + // then + assertThat(positions) + .hasSize(2) + .containsExactlyInAnyOrder(Position.of("d5"), Position.of("d6")); + } + + @ParameterizedTest + @MethodSource("createParams") + @DisplayName("이동 가능한 위치를 반환한다.") + void find_available_positions(boolean isFinite, Collection expected) { + // given + Position position = Position.of("d4"); + MoveCoordinate moveCoordinate = MoveCoordinate.EAST; + + // when + Collection positions = position.findAvailablePositions(moveCoordinate, isFinite); + + // then + assertThat(positions) + .hasSize(expected.size()) + .containsAll(expected); + } + + @Test + @DisplayName("랭크가 동일한지 확인한다.") + void has_same_rank() { + // given + Rank rank = Rank.R1; + Position position = Position.of("a1"); + + // when + boolean hasSameRank = position.hasSameRank(rank); + + // then + assertThat(hasSameRank).isTrue(); + } + + private static Stream createParams() { + return Stream.of( + Arguments.of(false, Arrays.asList(Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4"))), + Arguments.of(true, Collections.singletonList(Position.of("e4"))) + ); + } +} From 0401fb605e5493519c6089cf354eefdedba0b622 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Mon, 30 Aug 2021 01:51:07 +0900 Subject: [PATCH 23/38] =?UTF-8?q?feat:=20=EC=9B=B9=20=EC=B2=B4=EC=8A=A4=20?= =?UTF-8?q?MVC=20=EA=B5=AC=EC=A1=B0=20=EB=B0=8F=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tion.java => ConsoleChessApplication.java} | 8 +- src/main/java/chess/WebChessApplication.java | 15 +++ .../java/chess/WebUIChessApplication.java | 24 ----- ...oller.java => ConsoleChessController.java} | 6 +- .../chess/controller/WebChessController.java | 44 ++++++++ src/main/java/chess/domain/ChessGame.java | 2 +- src/main/java/chess/domain/player/Player.java | 76 ++++++------- .../java/chess/domain/player/Position.java | 6 ++ .../dto => dto/console}/BoardDto.java | 2 +- .../dto => dto/console}/PositionDto.java | 2 +- src/main/java/chess/dto/web/BoardDto.java | 40 +++++++ src/main/java/chess/dto/web/TurnDto.java | 19 ++++ src/main/java/chess/service/ChessService.java | 26 +++++ .../java/chess/view/ConsoleOutputView.java | 2 +- src/main/java/chess/view/OutputView.java | 2 +- src/main/resources/public/css/chessBoard.css | 23 ++++ src/main/resources/public/css/index.css | 54 ++++++++++ src/main/resources/public/js/game.js | 26 +++++ src/main/resources/public/js/start.js | 7 ++ src/main/resources/templates/game.html | 101 ++++++++++++++++++ src/main/resources/templates/index.html | 26 +++-- 21 files changed, 431 insertions(+), 80 deletions(-) rename src/main/java/chess/{ConsoleApplication.java => ConsoleChessApplication.java} (57%) create mode 100644 src/main/java/chess/WebChessApplication.java delete mode 100644 src/main/java/chess/WebUIChessApplication.java rename src/main/java/chess/controller/{ChessController.java => ConsoleChessController.java} (92%) create mode 100644 src/main/java/chess/controller/WebChessController.java rename src/main/java/chess/{controller/dto => dto/console}/BoardDto.java (97%) rename src/main/java/chess/{controller/dto => dto/console}/PositionDto.java (94%) create mode 100644 src/main/java/chess/dto/web/BoardDto.java create mode 100644 src/main/java/chess/dto/web/TurnDto.java create mode 100644 src/main/java/chess/service/ChessService.java create mode 100644 src/main/resources/public/css/chessBoard.css create mode 100644 src/main/resources/public/css/index.css create mode 100644 src/main/resources/public/js/game.js create mode 100644 src/main/resources/public/js/start.js create mode 100644 src/main/resources/templates/game.html diff --git a/src/main/java/chess/ConsoleApplication.java b/src/main/java/chess/ConsoleChessApplication.java similarity index 57% rename from src/main/java/chess/ConsoleApplication.java rename to src/main/java/chess/ConsoleChessApplication.java index d5f97f2..73a0402 100644 --- a/src/main/java/chess/ConsoleApplication.java +++ b/src/main/java/chess/ConsoleChessApplication.java @@ -1,17 +1,17 @@ package chess; -import chess.controller.ChessController; +import chess.controller.ConsoleChessController; import chess.view.ConsoleInputView; import chess.view.ConsoleOutputView; import chess.view.InputView; import chess.view.OutputView; -public class ConsoleApplication { +public class ConsoleChessApplication { public static void main(String[] args) { OutputView outputView = new ConsoleOutputView(); InputView inputView = new ConsoleInputView(); - ChessController chessController = new ChessController(inputView, outputView); - chessController.run(); + ConsoleChessController consoleChessController = new ConsoleChessController(inputView, outputView); + consoleChessController.run(); } } diff --git a/src/main/java/chess/WebChessApplication.java b/src/main/java/chess/WebChessApplication.java new file mode 100644 index 0000000..20ed161 --- /dev/null +++ b/src/main/java/chess/WebChessApplication.java @@ -0,0 +1,15 @@ +package chess; + +import chess.controller.WebChessController; + +import static spark.Spark.staticFiles; + +public class WebChessApplication { + + public static void main(String[] args) { + staticFiles.location("/public"); + + WebChessController webChessController = new WebChessController(); + webChessController.run(); + } +} diff --git a/src/main/java/chess/WebUIChessApplication.java b/src/main/java/chess/WebUIChessApplication.java deleted file mode 100644 index dacaab9..0000000 --- a/src/main/java/chess/WebUIChessApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -package chess; - -import spark.ModelAndView; -import spark.template.handlebars.HandlebarsTemplateEngine; - -import java.util.HashMap; -import java.util.Map; - -import static spark.Spark.get; - -public class WebUIChessApplication { - - public static void main(String[] args) { - get("/", (req, res) -> { - Map model = new HashMap<>(); - return render(model, "index.html"); - }); - } - - private static String render(Map model, String templatePath) { - return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath)); - } - -} diff --git a/src/main/java/chess/controller/ChessController.java b/src/main/java/chess/controller/ConsoleChessController.java similarity index 92% rename from src/main/java/chess/controller/ChessController.java rename to src/main/java/chess/controller/ConsoleChessController.java index 68dce81..1cb97f5 100644 --- a/src/main/java/chess/controller/ChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,18 +1,18 @@ package chess.controller; -import chess.controller.dto.BoardDto; +import chess.dto.console.BoardDto; import chess.domain.ChessGame; import chess.domain.board.Status; import chess.domain.command.Command; import chess.view.InputView; import chess.view.OutputView; -public class ChessController { +public class ConsoleChessController { private final InputView inputView; private final OutputView outputView; - public ChessController(final InputView inputView, final OutputView outputView) { + public ConsoleChessController(final InputView inputView, final OutputView outputView) { this.inputView = inputView; this.outputView = outputView; } diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java new file mode 100644 index 0000000..051c3b4 --- /dev/null +++ b/src/main/java/chess/controller/WebChessController.java @@ -0,0 +1,44 @@ +package chess.controller; + +import chess.service.ChessService; +import spark.ModelAndView; +import spark.Request; +import spark.Response; +import spark.template.handlebars.HandlebarsTemplateEngine; + +import java.util.HashMap; +import java.util.Map; + +import static spark.Spark.get; +import static spark.Spark.post; + +public class WebChessController { + + private final ChessService chessService; + + public WebChessController() { + this.chessService = new ChessService(); + } + + public void run() { + get("/", this::landingPage); + + post("/start", this::startGame); + } + + private String landingPage(Request request, Response response) { + Map model = new HashMap<>(); + return render(model, "index.html"); + } + + private Object startGame(Request request, Response response) { + Map model = new HashMap<>(); + model.put("board", chessService.getBoardView()); + model.put("currentTurn", chessService.getCurrentTurn()); + return render(model, "game.html"); + } + + private String render(Map model, String templatePath) { + return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath)); + } +} diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index 0828ad7..ec76522 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -18,7 +18,7 @@ public void move(final MoveParameters moveParameters) { isWhiteTurn = !isWhiteTurn; if (board.isEnd()) { - isRunning = false; + end(); } } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index 4710c52..92ebdc3 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -19,44 +19,6 @@ public Player(final Color color) { attackPositions = new AttackPositions(pieces); } - public double sumScores() { - List pawnPositions = findPawnPositions(); - - double pawnScores = calculatePawnScores(pawnPositions); - double scoresExceptPawn = calculateScoresExceptPawn(); - return pawnScores + scoresExceptPawn; - } - - private List findPawnPositions() { - return pieces.keySet() - .stream() - .filter(position -> { - Piece piece = pieces.get(position); - return PieceType.isPawn(piece); - }) - .collect(Collectors.toList()); - } - - private double calculatePawnScores(final List pawnPositions) { - Map pawnCount = new EnumMap<>(File.class); - pawnPositions.stream() - .map(Position::getFile) - .forEach(file -> pawnCount.put(file, pawnCount.getOrDefault(file, 0) + 1)); - - return pawnCount.values() - .stream() - .mapToDouble(PieceType::sumPawnScores) - .sum(); - } - - private double calculateScoresExceptPawn() { - return pieces.values() - .stream() - .filter(piece -> !PieceType.isPawn(piece)) - .mapToDouble(PieceType::findScoreBy) - .sum(); - } - public boolean hasPieceOn(final Position position) { return pieces.containsKey(position); } @@ -106,4 +68,42 @@ public void attacked(final Position target) { attackPositions.remove(target, pieces.get(target)); pieces.remove(target); } + + public double sumScores() { + List pawnPositions = findPawnPositions(); + + double pawnScores = calculatePawnScores(pawnPositions); + double scoresExceptPawn = calculateScoresExceptPawn(); + return pawnScores + scoresExceptPawn; + } + + private List findPawnPositions() { + return pieces.keySet() + .stream() + .filter(position -> { + Piece piece = pieces.get(position); + return PieceType.isPawn(piece); + }) + .collect(Collectors.toList()); + } + + private double calculatePawnScores(final List pawnPositions) { + Map pawnCount = new EnumMap<>(File.class); + pawnPositions.stream() + .map(Position::getFile) + .forEach(file -> pawnCount.put(file, pawnCount.getOrDefault(file, 0) + 1)); + + return pawnCount.values() + .stream() + .mapToDouble(PieceType::sumPawnScores) + .sum(); + } + + private double calculateScoresExceptPawn() { + return pieces.values() + .stream() + .filter(piece -> !PieceType.isPawn(piece)) + .mapToDouble(PieceType::findScoreBy) + .sum(); + } } diff --git a/src/main/java/chess/domain/player/Position.java b/src/main/java/chess/domain/player/Position.java index ee973b6..437e992 100644 --- a/src/main/java/chess/domain/player/Position.java +++ b/src/main/java/chess/domain/player/Position.java @@ -6,6 +6,7 @@ import java.util.*; public class Position { + private static final Map POSITIONS = createPositions(); private static Map createPositions() { @@ -30,7 +31,12 @@ public static Position from(final File file, final Rank rank) { return of(createKey(file, rank)); } + public static Collection names() { + return POSITIONS.keySet(); + } + private final File file; + private final Rank rank; private Position(final File file, final Rank rank) { diff --git a/src/main/java/chess/controller/dto/BoardDto.java b/src/main/java/chess/dto/console/BoardDto.java similarity index 97% rename from src/main/java/chess/controller/dto/BoardDto.java rename to src/main/java/chess/dto/console/BoardDto.java index a014e34..efac96e 100644 --- a/src/main/java/chess/controller/dto/BoardDto.java +++ b/src/main/java/chess/dto/console/BoardDto.java @@ -1,4 +1,4 @@ -package chess.controller.dto; +package chess.dto.console; import chess.domain.board.Board; import chess.domain.board.File; diff --git a/src/main/java/chess/controller/dto/PositionDto.java b/src/main/java/chess/dto/console/PositionDto.java similarity index 94% rename from src/main/java/chess/controller/dto/PositionDto.java rename to src/main/java/chess/dto/console/PositionDto.java index 384a91d..5a0d5ae 100644 --- a/src/main/java/chess/controller/dto/PositionDto.java +++ b/src/main/java/chess/dto/console/PositionDto.java @@ -1,4 +1,4 @@ -package chess.controller.dto; +package chess.dto.console; import chess.domain.board.File; diff --git a/src/main/java/chess/dto/web/BoardDto.java b/src/main/java/chess/dto/web/BoardDto.java new file mode 100644 index 0000000..875ea9a --- /dev/null +++ b/src/main/java/chess/dto/web/BoardDto.java @@ -0,0 +1,40 @@ +package chess.dto.web; + +import chess.domain.board.Board; +import chess.domain.piece.Piece; +import chess.domain.piece.type.PieceType; +import chess.domain.player.Position; + +import java.util.HashMap; +import java.util.Map; + +public class BoardDto { + + private BoardDto() { + } + + private static final String EMPTY_PIECE = "."; + + public static Map of(Board board) { + Map pieceOnPositions = new HashMap<>(); + + Position.names() + .forEach(positionKey -> { + String pieceName = findPieceName(positionKey, board); + pieceOnPositions.put(positionKey, pieceName); + }); + + return pieceOnPositions; + } + + private static String findPieceName(String positionKey, Board board) { + Position position = Position.of(positionKey); + + if (board.isEmpty(position)) { + return EMPTY_PIECE; + } + + Piece piece = board.findBy(position); + return PieceType.findNameBy(piece); + } +} diff --git a/src/main/java/chess/dto/web/TurnDto.java b/src/main/java/chess/dto/web/TurnDto.java new file mode 100644 index 0000000..33e8390 --- /dev/null +++ b/src/main/java/chess/dto/web/TurnDto.java @@ -0,0 +1,19 @@ +package chess.dto.web; + +import chess.domain.ChessGame; + +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; + +public class TurnDto { + + private TurnDto() { + } + + public static String of(ChessGame chessGame) { + if (chessGame.isWhiteTurn()) { + return WHITE.name(); + } + return BLACK.name(); + } +} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java new file mode 100644 index 0000000..38fcf75 --- /dev/null +++ b/src/main/java/chess/service/ChessService.java @@ -0,0 +1,26 @@ +package chess.service; + +import chess.domain.ChessGame; +import chess.domain.board.Board; +import chess.dto.web.BoardDto; +import chess.dto.web.TurnDto; + +import java.util.Map; + +public class ChessService { + + private final ChessGame chessGame; + + public ChessService() { + this.chessGame = new ChessGame(); + } + + public Map getBoardView() { + Board board = chessGame.getBoard(); + return BoardDto.of(board); + } + + public String getCurrentTurn() { + return TurnDto.of(chessGame); + } +} diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 4720828..dca3ee3 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.controller.dto.BoardDto; +import chess.dto.console.BoardDto; import chess.domain.board.Status; public class ConsoleOutputView implements OutputView { diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 50c37bf..377f4af 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.controller.dto.BoardDto; +import chess.dto.console.BoardDto; import chess.domain.board.Status; public interface OutputView { diff --git a/src/main/resources/public/css/chessBoard.css b/src/main/resources/public/css/chessBoard.css new file mode 100644 index 0000000..4d48623 --- /dev/null +++ b/src/main/resources/public/css/chessBoard.css @@ -0,0 +1,23 @@ +.chessboard { + width: 640px; + height: 640px; + border: 20px solid #1E201F; +} + +.cell { + width: 80px; + height: 80px; + display: table-cell; + vertical-align: middle; + float: left; + text-align: center; + font-size: 60px; +} + +.dark { + background-color: #8D6F59; +} + +.light { + background-color: #E4CBB3; +} \ No newline at end of file diff --git a/src/main/resources/public/css/index.css b/src/main/resources/public/css/index.css new file mode 100644 index 0000000..824b76a --- /dev/null +++ b/src/main/resources/public/css/index.css @@ -0,0 +1,54 @@ +body { + width: 100%; + margin: 0 auto; + font-family: "배달의민족 한나는 열한살", system-ui; + text-align: center; +} + +header { + padding-top: 1em; +} + +.main-title { + height: 3em; + line-height: 3em; + font-size: 1.7em; + font-weight: bolder; + color: snow; + background-color: #333333; +} + +.mini-title { + font-size: 1.5em; + padding-top: 2em; + color: #333333; +} + +.guide { + font-size: 1.1em; + padding-top: 1.5em; + color: #333333; +} + +section { + padding: 35px; + width: 640px; + height: 640px; + display: flex; + margin: auto; + place-content: center; +} + +.start-button { + width: 150px; + height: 2.5em; + line-height: 2.5em; + font-size: 1.3em; + font-family: "배달의민족 한나는 열한살", system-ui; +} + +.box-border { + border: 2px solid; + border-radius: 5px; + background-color: rgba(0, 0, 0, 0); +} \ No newline at end of file diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js new file mode 100644 index 0000000..8495dd7 --- /dev/null +++ b/src/main/resources/public/js/game.js @@ -0,0 +1,26 @@ +const nameSymbolMap = new Map( + [ + ["k", "♔"], + ["q", "♕"], + ["r", "♖"], + ["b", "♗"], + ["n", "♘"], + ["p", "♙"], + + ["K", "♚"], + ["Q", "♛"], + ["R", "♜"], + ["B", "♝"], + ["N", "♞"], + ["P", "♟"], + + [".", ""] + ] +); + +window.onload = function () { + document.querySelectorAll(".cell") + .forEach(element => { + element.innerText = nameSymbolMap.get(element.innerText); + }); +} \ No newline at end of file diff --git a/src/main/resources/public/js/start.js b/src/main/resources/public/js/start.js new file mode 100644 index 0000000..99ad88b --- /dev/null +++ b/src/main/resources/public/js/start.js @@ -0,0 +1,7 @@ +const startGame = document.getElementById("start-game"); +const startButton = document.getElementById("start-button"); + +startButton.onclick = () => { + alert("게임 시작"); + startGame.submit(); +}; \ No newline at end of file diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html new file mode 100644 index 0000000..4dafec5 --- /dev/null +++ b/src/main/resources/templates/game.html @@ -0,0 +1,101 @@ + + + + + + Web Chess Game + + + + + + + + + +
+
Web Chess Game
+
♟️ 체스 게임 ♟️
+
{{currentTurn}}의 차례입니다.
+
+ +
+
+
{{board.a8}}
+
{{board.b8}}
+
{{board.c8}}
+
{{board.d8}}
+
{{board.e8}}
+
{{board.f8}}
+
{{board.g8}}
+
{{board.h8}}
+ +
{{board.a7}}
+
{{board.b7}}
+
{{board.c7}}
+
{{board.d7}}
+
{{board.e7}}
+
{{board.f7}}
+
{{board.g7}}
+
{{board.h7}}
+ +
{{board.a6}}
+
{{board.b6}}
+
{{board.c6}}
+
{{board.d6}}
+
{{board.e6}}
+
{{board.f6}}
+
{{board.g6}}
+
{{board.h6}}
+ +
{{board.a5}}
+
{{board.b5}}
+
{{board.c5}}
+
{{board.d5}}
+
{{board.e5}}
+
{{board.f5}}
+
{{board.g5}}
+
{{board.h5}}
+ +
{{board.a4}}
+
{{board.b4}}
+
{{board.c4}}
+
{{board.d4}}
+
{{board.e4}}
+
{{board.f4}}
+
{{board.g4}}
+
{{board.h4}}
+ +
{{board.a3}}
+
{{board.b3}}
+
{{board.c3}}
+
{{board.d3}}
+
{{board.e3}}
+
{{board.f3}}
+
{{board.g3}}
+
{{board.h3}}
+ +
{{board.a2}}
+
{{board.b2}}
+
{{board.c2}}
+
{{board.d2}}
+
{{board.e2}}
+
{{board.f2}}
+
{{board.g2}}
+
{{board.h2}}
+ +
{{board.a1}}
+
{{board.b1}}
+
{{board.c1}}
+
{{board.d1}}
+
{{board.e1}}
+
{{board.f1}}
+
{{board.g1}}
+
{{board.h1}}
+
+ +
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 7c0febd..0f73d8b 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,12 +1,26 @@ + - - 체스 + + Web Chess Game + + + -

- Hello, world! -

+ +
+
Web Chess Game
+
♟️ 새로운 체스 게임 시작하기 ♟️
+
+ +
+ +
+
+ + - + + \ No newline at end of file From bdb972996b8e6c0037a73405fcdc713f75b3d386 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Tue, 31 Aug 2021 03:04:17 +0900 Subject: [PATCH 24/38] =?UTF-8?q?feat:=20=EA=B8=B0=EB=AC=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99,=20=ED=98=84=EC=9E=AC=20=EC=A0=90=EC=88=98,=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/controller/WebChessController.java | 64 +++++- src/main/java/chess/domain/ChessGame.java | 16 +- .../chess/domain/command/MoveParameters.java | 4 + .../java/chess/domain/player/Position.java | 8 +- src/main/java/chess/service/ChessService.java | 14 ++ src/main/resources/public/css/chessBoard.css | 21 +- src/main/resources/public/css/index.css | 34 ++-- src/main/resources/public/css/menu.css | 73 +++++++ src/main/resources/public/js/game.js | 8 +- src/main/resources/public/js/menu.js | 14 ++ src/main/resources/public/js/result.js | 7 + src/main/resources/templates/game.html | 185 +++++++++++------- src/main/resources/templates/index.html | 9 +- src/main/resources/templates/result.html | 26 +++ 14 files changed, 366 insertions(+), 117 deletions(-) create mode 100644 src/main/resources/public/css/menu.css create mode 100644 src/main/resources/public/js/menu.js create mode 100644 src/main/resources/public/js/result.js create mode 100644 src/main/resources/templates/result.html diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java index 051c3b4..67e09b5 100644 --- a/src/main/java/chess/controller/WebChessController.java +++ b/src/main/java/chess/controller/WebChessController.java @@ -1,5 +1,6 @@ package chess.controller; +import chess.domain.command.MoveParameters; import chess.service.ChessService; import spark.ModelAndView; import spark.Request; @@ -14,6 +15,10 @@ public class WebChessController { + private static final String LANDING_PAGE = "index.html"; + private static final String GAME_PAGE = "game.html"; + private static final String RESULT_PAGE = "result.html"; + private final ChessService chessService; public WebChessController() { @@ -24,18 +29,71 @@ public void run() { get("/", this::landingPage); post("/start", this::startGame); + + post("/status", this::calculateStatus); + + post("/move", this::movePiece); + + post("/result", this::finishGame); } private String landingPage(Request request, Response response) { Map model = new HashMap<>(); - return render(model, "index.html"); + return render(model, LANDING_PAGE); + } + + private String startGame(Request request, Response response) { + return render(getGameInfo(), GAME_PAGE); + } + + private String calculateStatus(Request request, Response response) { + return render(getGameInfoWithStatus(), GAME_PAGE); + } + + private String movePiece(Request request, Response response) { + try { + MoveParameters moveParameters = new MoveParameters(request.queryParams("source"), request.queryParams("target")); + chessService.movePiece(moveParameters); + } catch (IllegalArgumentException e) { + return render(getGameInfoWithMessage(e.getMessage()), GAME_PAGE); + } + + return nextPage(); } - private Object startGame(Request request, Response response) { + private String finishGame(Request request, Response response) { + return render(getGameResult(), RESULT_PAGE); + } + + private String nextPage() { + if (chessService.isGameFinished()) { + return render(getGameResult(), RESULT_PAGE); + } + return render(getGameInfo(), GAME_PAGE); + } + + private Map getGameInfo() { Map model = new HashMap<>(); model.put("board", chessService.getBoardView()); model.put("currentTurn", chessService.getCurrentTurn()); - return render(model, "game.html"); + return model; + } + + private Map getGameInfoWithStatus() { + Map model = getGameInfo(); + model.put("status", chessService.getStatus()); + return model; + } + + private Map getGameInfoWithMessage(String message) { + Map model = getGameInfo(); + model.put("message", message); + return model; + } + + private Map getGameResult() { + Map model = new HashMap<>(); + return model; } private String render(Map model, String templatePath) { diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index ec76522..1852386 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -7,7 +7,7 @@ public class ChessGame { private final Board board = new Board(); - private boolean isRunning = true; + private boolean isFinished = false; private boolean isWhiteTurn = true; public ChessGame() { @@ -23,17 +23,13 @@ public void move(final MoveParameters moveParameters) { } public void end() { - isRunning = false; + isFinished = true; } public Status getStatus() { return board.getStatus(); } - public boolean isRunning() { - return isRunning; - } - public Board getBoard() { return board; } @@ -41,4 +37,12 @@ public Board getBoard() { public boolean isWhiteTurn() { return isWhiteTurn; } + + public boolean isFinished() { + return isFinished; + } + + public boolean isRunning() { + return !isFinished; + } } diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java index c8a607a..cb78afd 100644 --- a/src/main/java/chess/domain/command/MoveParameters.java +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -16,6 +16,10 @@ public MoveParameters(final List parameters) { this(Position.of(parameters.get(SOURCE_INDEX)), Position.of(parameters.get(TARGET_INDEX))); } + public MoveParameters(String source, String target) { + this(Position.of(source), Position.of(target)); + } + public MoveParameters(final Position source, final Position target) { this.source = source; this.target = target; diff --git a/src/main/java/chess/domain/player/Position.java b/src/main/java/chess/domain/player/Position.java index 437e992..244fa86 100644 --- a/src/main/java/chess/domain/player/Position.java +++ b/src/main/java/chess/domain/player/Position.java @@ -23,7 +23,13 @@ private static String createKey(final File file, final Rank rank) { return file.name() + rank.getIndex(); } - public static Position of(final String key) { + public static Position of(String key) { + key = key.toLowerCase(); + + if (!POSITIONS.containsKey(key)) { + throw new IllegalArgumentException("위치 입력값이 잘못되었습니다."); + } + return POSITIONS.get(key); } diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 38fcf75..4a85475 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -2,6 +2,8 @@ import chess.domain.ChessGame; import chess.domain.board.Board; +import chess.domain.board.Status; +import chess.domain.command.MoveParameters; import chess.dto.web.BoardDto; import chess.dto.web.TurnDto; @@ -23,4 +25,16 @@ public Map getBoardView() { public String getCurrentTurn() { return TurnDto.of(chessGame); } + + public Status getStatus() { + return chessGame.getStatus(); + } + + public boolean isGameFinished() { + return chessGame.isFinished(); + } + + public void movePiece(MoveParameters moveParameters) { + chessGame.move(moveParameters); + } } diff --git a/src/main/resources/public/css/chessBoard.css b/src/main/resources/public/css/chessBoard.css index 4d48623..efd67f4 100644 --- a/src/main/resources/public/css/chessBoard.css +++ b/src/main/resources/public/css/chessBoard.css @@ -1,17 +1,24 @@ -.chessboard { - width: 640px; - height: 640px; - border: 20px solid #1E201F; +.chess-board-wrapper { + width: 430px; + height: 430px; + display: flex; + place-content: center; +} + +.chess-board { + width: 400px; + height: 400px; + border: 15px solid #1E201F; } .cell { - width: 80px; - height: 80px; + width: 50px; + height: 50px; display: table-cell; vertical-align: middle; float: left; text-align: center; - font-size: 60px; + font-size: 40px; } .dark { diff --git a/src/main/resources/public/css/index.css b/src/main/resources/public/css/index.css index 824b76a..e825396 100644 --- a/src/main/resources/public/css/index.css +++ b/src/main/resources/public/css/index.css @@ -5,14 +5,10 @@ body { text-align: center; } -header { - padding-top: 1em; -} - .main-title { - height: 3em; - line-height: 3em; - font-size: 1.7em; + height: 2em; + line-height: 2em; + font-size: 1.3em; font-weight: bolder; color: snow; background-color: #333333; @@ -20,30 +16,25 @@ header { .mini-title { font-size: 1.5em; - padding-top: 2em; + padding-top: 1em; color: #333333; } .guide { - font-size: 1.1em; - padding-top: 1.5em; + font-size: 0.8em; + padding-top: 0.4em; color: #333333; } section { - padding: 35px; - width: 640px; - height: 640px; - display: flex; - margin: auto; - place-content: center; + margin-top: 20px; } -.start-button { - width: 150px; +.button { + width: 120px; height: 2.5em; line-height: 2.5em; - font-size: 1.3em; + font-size: 1.0em; font-family: "배달의민족 한나는 열한살", system-ui; } @@ -51,4 +42,9 @@ section { border: 2px solid; border-radius: 5px; background-color: rgba(0, 0, 0, 0); +} + +footer { + padding-bottom: 30px; + place-content: center; } \ No newline at end of file diff --git a/src/main/resources/public/css/menu.css b/src/main/resources/public/css/menu.css new file mode 100644 index 0000000..a2e5e81 --- /dev/null +++ b/src/main/resources/public/css/menu.css @@ -0,0 +1,73 @@ +.game-wrapper { + width: 630px; + height: 430px; + padding: 15px; + background: #eeeeee; + border-radius: 10px; + display: flex; + margin: auto; + place-content: center; +} + +.menu-wrapper { + width: 180px; + margin-left: 15px; + place-content: center; +} + +.move-form-wrapper { + width: 150px; + height: 135px; + padding: 15px; + background: white; + border-radius: 10px; +} + +.input-label { + height: auto; + font-size: 1em; +} + +.input-box { + width: 50px; + margin-left: 5px; + margin-top: 15px; +} + +.move-button { + width: 90px; + height: 1.8em; + margin-top: 20px; + margin-bottom: 10px; + line-height: 1.8em; + font-size: 1.1em; + font-family: "배달의민족 한나는 열한살", system-ui; +} + +.message-wrapper { + width: 150px; + height: 90px; + padding: 15px; + margin-top: 15px; + background: white; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.message-content { + font-size: 18px; +} + +.menu-button { + width: 180px; + height: 2.3em; + line-height: 2.3em; + margin-top: 15px; + background: white; + border-radius: 10px; + border: 0px solid; + font-size: 1.3em; + font-family: "배달의민족 한나는 열한살", system-ui; +} \ No newline at end of file diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js index 8495dd7..8b7bf48 100644 --- a/src/main/resources/public/js/game.js +++ b/src/main/resources/public/js/game.js @@ -19,8 +19,8 @@ const nameSymbolMap = new Map( ); window.onload = function () { - document.querySelectorAll(".cell") - .forEach(element => { - element.innerText = nameSymbolMap.get(element.innerText); - }); + document.querySelectorAll(".cell") + .forEach(element => { + element.innerText = nameSymbolMap.get(element.innerText); + }); } \ No newline at end of file diff --git a/src/main/resources/public/js/menu.js b/src/main/resources/public/js/menu.js new file mode 100644 index 0000000..ce4f0fb --- /dev/null +++ b/src/main/resources/public/js/menu.js @@ -0,0 +1,14 @@ +const endGame = document.getElementById("end-game"); +const endButton = document.getElementById("end-button"); +const status = document.getElementById("status"); +const statusButton = document.getElementById("status-button"); + +endButton.onclick = () => { + alert("게임 종료"); + endGame.submit(); +}; + +statusButton.onclick = () => { + alert("현재 스코어"); + status.submit(); +}; \ No newline at end of file diff --git a/src/main/resources/public/js/result.js b/src/main/resources/public/js/result.js new file mode 100644 index 0000000..69a0e45 --- /dev/null +++ b/src/main/resources/public/js/result.js @@ -0,0 +1,7 @@ +const returnToMain = document.getElementById("return-to-main"); +const returnButton = document.getElementById("return-button"); + +returnButton.onclick = () => { + alert("초기 화면으로 돌아갑니다"); + returnToMain.submit(); +}; \ No newline at end of file diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html index 4dafec5..48cbe1b 100644 --- a/src/main/resources/templates/game.html +++ b/src/main/resources/templates/game.html @@ -6,96 +6,135 @@ Web Chess Game + +
-
Web Chess Game
-
♟️ 체스 게임 ♟️
-
{{currentTurn}}의 차례입니다.
+
♟️ 체스 게임이 진행 중입니다 ♟️
-
-
{{board.a8}}
-
{{board.b8}}
-
{{board.c8}}
-
{{board.d8}}
-
{{board.e8}}
-
{{board.f8}}
-
{{board.g8}}
-
{{board.h8}}
+
-
{{board.a7}}
-
{{board.b7}}
-
{{board.c7}}
-
{{board.d7}}
-
{{board.e7}}
-
{{board.f7}}
-
{{board.g7}}
-
{{board.h7}}
+
+
+
{{board.a8}}
+
{{board.b8}}
+
{{board.c8}}
+
{{board.d8}}
+
{{board.e8}}
+
{{board.f8}}
+
{{board.g8}}
+
{{board.h8}}
+
{{board.a7}}
+
{{board.b7}}
+
{{board.c7}}
+
{{board.d7}}
+
{{board.e7}}
+
{{board.f7}}
+
{{board.g7}}
+
{{board.h7}}
+
{{board.a6}}
+
{{board.b6}}
+
{{board.c6}}
+
{{board.d6}}
+
{{board.e6}}
+
{{board.f6}}
+
{{board.g6}}
+
{{board.h6}}
+
{{board.a5}}
+
{{board.b5}}
+
{{board.c5}}
+
{{board.d5}}
+
{{board.e5}}
+
{{board.f5}}
+
{{board.g5}}
+
{{board.h5}}
+
{{board.a4}}
+
{{board.b4}}
+
{{board.c4}}
+
{{board.d4}}
+
{{board.e4}}
+
{{board.f4}}
+
{{board.g4}}
+
{{board.h4}}
+
{{board.a3}}
+
{{board.b3}}
+
{{board.c3}}
+
{{board.d3}}
+
{{board.e3}}
+
{{board.f3}}
+
{{board.g3}}
+
{{board.h3}}
+
{{board.a2}}
+
{{board.b2}}
+
{{board.c2}}
+
{{board.d2}}
+
{{board.e2}}
+
{{board.f2}}
+
{{board.g2}}
+
{{board.h2}}
+
{{board.a1}}
+
{{board.b1}}
+
{{board.c1}}
+
{{board.d1}}
+
{{board.e1}}
+
{{board.f1}}
+
{{board.g1}}
+
{{board.h1}}
+
+
-
{{board.a6}}
-
{{board.b6}}
-
{{board.c6}}
-
{{board.d6}}
-
{{board.e6}}
-
{{board.f6}}
-
{{board.g6}}
-
{{board.h6}}
+
-
- +
+
+ - \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 0f73d8b..306a7ea 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -11,13 +11,14 @@
-
Web Chess Game
-
♟️ 새로운 체스 게임 시작하기 ♟️
+
♟️ 체스 게임 시작하기 ♟️
+
새로운 체스 게임을 시작합니다.
- -
+
+ +
diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html new file mode 100644 index 0000000..a691bf7 --- /dev/null +++ b/src/main/resources/templates/result.html @@ -0,0 +1,26 @@ + + + + + + Web Chess Game + + + + + + +
+
♟️ 체스 게임이 종료되었습니다 ♟️
+
게임 결과
+
+ +
+ +
+
+ + + + + \ No newline at end of file From ffec341b4f5077aa077b5760c5370aa84c1e5385 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Tue, 31 Aug 2021 16:36:50 +0900 Subject: [PATCH 25/38] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C,=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chess/controller/WebChessController.java | 10 +++- src/main/java/chess/domain/ChessGame.java | 29 +++++++--- src/main/java/chess/domain/board/Board.java | 31 ++++++++--- .../chess/domain/piece/type/MovePattern.java | 2 +- src/main/java/chess/dto/web/TurnDto.java | 19 ------- src/main/java/chess/service/ChessService.java | 55 +++++++++++++++---- src/main/resources/public/css/index.css | 6 +- src/main/resources/public/css/result.css | 16 ++++++ src/main/resources/templates/game.html | 6 +- src/main/resources/templates/result.html | 25 ++++++++- .../java/chess/domain/board/BoardTest.java | 49 +++++++++-------- 11 files changed, 169 insertions(+), 79 deletions(-) delete mode 100644 src/main/java/chess/dto/web/TurnDto.java create mode 100644 src/main/resources/public/css/result.css diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java index 67e09b5..8f184a4 100644 --- a/src/main/java/chess/controller/WebChessController.java +++ b/src/main/java/chess/controller/WebChessController.java @@ -30,7 +30,7 @@ public void run() { post("/start", this::startGame); - post("/status", this::calculateStatus); + post("/status", this::getStatus); post("/move", this::movePiece); @@ -46,7 +46,7 @@ private String startGame(Request request, Response response) { return render(getGameInfo(), GAME_PAGE); } - private String calculateStatus(Request request, Response response) { + private String getStatus(Request request, Response response) { return render(getGameInfoWithStatus(), GAME_PAGE); } @@ -93,6 +93,12 @@ private Map getGameInfoWithMessage(String message) { private Map getGameResult() { Map model = new HashMap<>(); + model.put("status", chessService.getStatus()); + try { + model.put("winner", chessService.getWinner()); + } catch (IllegalStateException e) { + model.put("message", e.getMessage()); + } return model; } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index 1852386..72264a3 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -3,25 +3,40 @@ import chess.domain.board.Board; import chess.domain.board.Status; import chess.domain.command.MoveParameters; +import chess.domain.piece.Color; + +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; public class ChessGame { private final Board board = new Board(); + private Color currentTurn = WHITE; private boolean isFinished = false; - private boolean isWhiteTurn = true; public ChessGame() { } public void move(final MoveParameters moveParameters) { - board.move(moveParameters, isWhiteTurn); - isWhiteTurn = !isWhiteTurn; + board.move(moveParameters, currentTurn); + changeTurn(); - if (board.isEnd()) { + if (board.isKingDead()) { end(); } } + private void changeTurn() { + if (currentTurn == WHITE) { + currentTurn = BLACK; + return; + } + + if (currentTurn == BLACK) { + currentTurn = WHITE; + } + } + public void end() { isFinished = true; } @@ -35,11 +50,7 @@ public Board getBoard() { } public boolean isWhiteTurn() { - return isWhiteTurn; - } - - public boolean isFinished() { - return isFinished; + return currentTurn.isWhite(); } public boolean isRunning() { diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index f0f7109..5ad918f 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -8,18 +8,21 @@ import java.util.Set; +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; + public class Board { private final Player white; private final Player black; public Board() { this.white = new Player(Color.WHITE); - this.black = new Player(Color.BLACK); + this.black = new Player(BLACK); } - public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) { - Player player = currentPlayer(isWhiteTurn); - Player enemy = currentPlayer(!isWhiteTurn); + public void move(final MoveParameters moveParameters, final Color currentTurn) { + Player player = currentPlayer(currentTurn); + Player enemy = enemyPlayer(currentTurn); Position source = moveParameters.getSource(); Position target = moveParameters.getTarget(); @@ -32,13 +35,20 @@ public void move(final MoveParameters moveParameters, final boolean isWhiteTurn) movePiece(player, source, target); } - private Player currentPlayer(final boolean isWhiteTurn) { - if (isWhiteTurn) { + private Player currentPlayer(final Color currentTurn) { + if (currentTurn == Color.WHITE) { return white; } return black; } + private Player enemyPlayer(final Color currentTurn) { + if (currentTurn == Color.WHITE) { + return black; + } + return white; + } + private void validateSourceOwner(final Player enemy, final Position source) { if (enemy.hasPieceOn(source)) { throw new IllegalArgumentException("자신의 기물만 움직일 수 있습니다."); @@ -100,7 +110,14 @@ public Status getStatus() { return new Status(whiteScore, blackScore, white.isKingDead(), black.isKingDead()); } - public boolean isEnd() { + public boolean isKingDead() { return white.isKingDead() || black.isKingDead(); } + + public Color getWinner() { + if (white.isKingDead()) { + return BLACK; + } + return WHITE; + } } diff --git a/src/main/java/chess/domain/piece/type/MovePattern.java b/src/main/java/chess/domain/piece/type/MovePattern.java index 9c926da..45a4ce7 100644 --- a/src/main/java/chess/domain/piece/type/MovePattern.java +++ b/src/main/java/chess/domain/piece/type/MovePattern.java @@ -89,7 +89,7 @@ public MoveCoordinate findMoveCoordinate(int fileGap, int rankGap) { .ifPresent(result::add); if (result.isEmpty()) { - throw new IllegalArgumentException("매칭되는 방향이 없습니다."); + throw new IllegalArgumentException("해당 방향으로 이동할 수 없습니다."); } return result.get(0); diff --git a/src/main/java/chess/dto/web/TurnDto.java b/src/main/java/chess/dto/web/TurnDto.java deleted file mode 100644 index 33e8390..0000000 --- a/src/main/java/chess/dto/web/TurnDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package chess.dto.web; - -import chess.domain.ChessGame; - -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; - -public class TurnDto { - - private TurnDto() { - } - - public static String of(ChessGame chessGame) { - if (chessGame.isWhiteTurn()) { - return WHITE.name(); - } - return BLACK.name(); - } -} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 4a85475..c5e257c 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,40 +1,73 @@ package chess.service; -import chess.domain.ChessGame; import chess.domain.board.Board; import chess.domain.board.Status; import chess.domain.command.MoveParameters; +import chess.domain.piece.Color; import chess.dto.web.BoardDto; -import chess.dto.web.TurnDto; import java.util.Map; +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; + public class ChessService { - private final ChessGame chessGame; + private final Board board; + private Color currentTurn; + private boolean isFinished; public ChessService() { - this.chessGame = new ChessGame(); + this.board = new Board(); + this.currentTurn = WHITE; + this.isFinished = false; + } + + public boolean isGameFinished() { + return isFinished; } public Map getBoardView() { - Board board = chessGame.getBoard(); return BoardDto.of(board); } public String getCurrentTurn() { - return TurnDto.of(chessGame); + return currentTurn.name(); } public Status getStatus() { - return chessGame.getStatus(); + return board.getStatus(); } - public boolean isGameFinished() { - return chessGame.isFinished(); + public void movePiece(MoveParameters moveParameters) { + board.move(moveParameters, currentTurn); + changeTurn(); + + if (board.isKingDead()) { + finish(); + } } - public void movePiece(MoveParameters moveParameters) { - chessGame.move(moveParameters); + private void changeTurn() { + if (currentTurn == WHITE) { + currentTurn = BLACK; + return; + } + + if (currentTurn == BLACK) { + currentTurn = WHITE; + } + } + + private void finish() { + isFinished = true; + } + + public String getWinner() { + if (isFinished) { + Color color = board.getWinner(); + return color.name(); + } + throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); } } diff --git a/src/main/resources/public/css/index.css b/src/main/resources/public/css/index.css index e825396..de82f50 100644 --- a/src/main/resources/public/css/index.css +++ b/src/main/resources/public/css/index.css @@ -31,9 +31,9 @@ section { } .button { - width: 120px; - height: 2.5em; - line-height: 2.5em; + width: 160px; + height: 3em; + line-height: 3em; font-size: 1.0em; font-family: "배달의민족 한나는 열한살", system-ui; } diff --git a/src/main/resources/public/css/result.css b/src/main/resources/public/css/result.css new file mode 100644 index 0000000..f8c8b72 --- /dev/null +++ b/src/main/resources/public/css/result.css @@ -0,0 +1,16 @@ +.result-wrapper { + width: 400px; + height: 150px; + padding: 20px; + margin: 30px auto; + background: #EEEEEE; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; +} + +.result-content { + font-size: 20px; + line-height: 25px; +} diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html index 48cbe1b..b38d28d 100644 --- a/src/main/resources/templates/game.html +++ b/src/main/resources/templates/game.html @@ -108,16 +108,18 @@
+ {{currentTurn}} 차례입니다. {{#status}} - WHITE: {{status.whiteScore}}점
+

+ WHITE: {{status.whiteScore}}점 BLACK: {{status.blackScore}}점 {{/status}} {{^status}} {{#message}} +

{{message}} {{/message}} {{^message}} - {{currentTurn}} 차례입니다. {{/message}} {{/status}}
diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html index a691bf7..f59b4fe 100644 --- a/src/main/resources/templates/result.html +++ b/src/main/resources/templates/result.html @@ -5,7 +5,9 @@ Web Chess Game + + @@ -16,8 +18,27 @@
- -
+
+
+ {{#winner}} + {{winner}}가 승리했습니다.
축하합니다! + {{/winner}} + {{^winner}} + {{#message}} + {{message}} + {{#status}} +

+ WHITE: {{status.whiteScore}}점
+ BLACK: {{status.blackScore}}점 + {{/status}} + {{/message}} + {{/winner}} +
+
+ +
+ +
diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 2585f02..ce70efb 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,12 +1,15 @@ package chess.domain.board; import chess.domain.command.MoveParameters; +import chess.domain.piece.Color; import chess.domain.player.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import static chess.domain.piece.Color.BLACK; +import static chess.domain.piece.Color.WHITE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -38,14 +41,14 @@ void move_source_position_empty() { //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, true)) + .isThrownBy(() -> board.move(moveParameters, WHITE)) .withMessage("해당 위치에 기물이 존재하지 않습니다."); } @ParameterizedTest - @CsvSource({"b2, b3, false", "a7, a6, true"}) + @CsvSource({"b2, b3, BLACK", "a7, a6, WHITE"}) @DisplayName("자신의 기물이 아닌 기물을 선택할 경우 예외가 발생한다.") - void move_source_not_owner(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + void move_source_not_owner(String sourcePosition, String targetPosition, Color currentTurn) { //given Board board = new Board(); Position source = Position.of(sourcePosition); @@ -54,14 +57,14 @@ void move_source_not_owner(String sourcePosition, String targetPosition, boolean //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .isThrownBy(() -> board.move(moveParameters, currentTurn)) .withMessage("자신의 기물만 움직일 수 있습니다."); } @ParameterizedTest - @CsvSource({"a1, a2, true", "a8, a7, false"}) + @CsvSource({"a1, a2, WHITE", "a8, a7, BLACK"}) @DisplayName("시작과 도착 위치의 기물이 같은 색상일 경우 예외가 발생한다.") - void move_source_and_target_same_color(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + void move_source_and_target_same_color(String sourcePosition, String targetPosition, Color currentTurn) { //given Board board = new Board(); Position source = Position.of(sourcePosition); @@ -70,14 +73,14 @@ void move_source_and_target_same_color(String sourcePosition, String targetPosit //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .isThrownBy(() -> board.move(moveParameters, currentTurn)) .withMessage("같은 색상의 기물은 공격할 수 없습니다."); } @ParameterizedTest - @CsvSource({"a1, a1, true", "a8, a8, false"}) + @CsvSource({"a1, a1, WHITE", "a8, a8, BLACK"}) @DisplayName("시작과 도착 위치가 같을 경우 예외가 발생한다.") - void move_source_and_target_same(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + void move_source_and_target_same(String sourcePosition, String targetPosition, Color currentTurn) { //given Board board = new Board(); Position source = Position.of(sourcePosition); @@ -86,14 +89,14 @@ void move_source_and_target_same(String sourcePosition, String targetPosition, b //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .isThrownBy(() -> board.move(moveParameters, currentTurn)) .withMessage("출발 위치와 도착 위치가 같을 수 없습니다."); } @ParameterizedTest - @CsvSource({"a1, a3, true", "a8, a6, false"}) + @CsvSource({"a1, a3, WHITE", "a8, a6, BLACK"}) @DisplayName("경로에 다른 기물이 존재하는 경우 예외가 발생한다.") - void move_invalid_paths(String sourcePosition, String targetPosition, boolean isWhiteTurn) { + void move_invalid_paths(String sourcePosition, String targetPosition, Color currentTurn) { //given Board board = new Board(); Position source = Position.of(sourcePosition); @@ -102,7 +105,7 @@ void move_invalid_paths(String sourcePosition, String targetPosition, boolean is //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, isWhiteTurn)) + .isThrownBy(() -> board.move(moveParameters, currentTurn)) .withMessage("기물을 통과하여 이동할 수 없습니다."); } @@ -116,7 +119,7 @@ void move_king_invalid_target(String source, String target) { //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, true)) + .isThrownBy(() -> board.move(moveParameters, WHITE)) .withMessage("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } @@ -136,20 +139,20 @@ void get_status() { private Board setBoardToGetStatus() { Board board = new Board(); - board.move(new MoveParameters(Position.of("e7"), Position.of("e5")), false); - board.move(new MoveParameters(Position.of("e5"), Position.of("e4")), false); - board.move(new MoveParameters(Position.of("e4"), Position.of("e3")), false); - board.move(new MoveParameters(Position.of("d2"), Position.of("e3")), true); + board.move(new MoveParameters(Position.of("e7"), Position.of("e5")), BLACK); + board.move(new MoveParameters(Position.of("e5"), Position.of("e4")), BLACK); + board.move(new MoveParameters(Position.of("e4"), Position.of("e3")), BLACK); + board.move(new MoveParameters(Position.of("d2"), Position.of("e3")), WHITE); return board; } private Board setBoardToAttackKing() { Board board = new Board(); - board.move(new MoveParameters(Position.of("e2"), Position.of("e4")), true); - board.move(new MoveParameters(Position.of("d2"), Position.of("d4")), true); - board.move(new MoveParameters(Position.of("e1"), Position.of("e2")), true); - board.move(new MoveParameters(Position.of("c7"), Position.of("c5")), false); - board.move(new MoveParameters(Position.of("d8"), Position.of("a5")), false); + board.move(new MoveParameters(Position.of("e2"), Position.of("e4")), WHITE); + board.move(new MoveParameters(Position.of("d2"), Position.of("d4")), WHITE); + board.move(new MoveParameters(Position.of("e1"), Position.of("e2")), WHITE); + board.move(new MoveParameters(Position.of("c7"), Position.of("c5")), BLACK); + board.move(new MoveParameters(Position.of("d8"), Position.of("a5")), BLACK); return board; } } From 8e2830b5d2af567f7c6285481f2c432503883134 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Tue, 31 Aug 2021 17:30:54 +0900 Subject: [PATCH 26/38] =?UTF-8?q?refactor:=20ChessGame=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=82=AD=EC=A0=9C,=20ChessService?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConsoleChessController.java | 39 ++++++------ .../chess/controller/WebChessController.java | 2 +- src/main/java/chess/domain/ChessGame.java | 59 ------------------- .../{BoardDto.java => BoardConsoleDto.java} | 5 +- .../web/{BoardDto.java => BoardWebDto.java} | 4 +- src/main/java/chess/service/ChessService.java | 21 +++++-- .../java/chess/view/ConsoleOutputView.java | 15 ++--- src/main/java/chess/view/OutputView.java | 6 +- 8 files changed, 50 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/chess/domain/ChessGame.java rename src/main/java/chess/dto/console/{BoardDto.java => BoardConsoleDto.java} (93%) rename src/main/java/chess/dto/web/{BoardDto.java => BoardWebDto.java} (94%) diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index 1cb97f5..bea065f 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,9 +1,10 @@ package chess.controller; -import chess.dto.console.BoardDto; -import chess.domain.ChessGame; import chess.domain.board.Status; import chess.domain.command.Command; +import chess.domain.command.MoveParameters; +import chess.dto.console.BoardConsoleDto; +import chess.service.ChessService; import chess.view.InputView; import chess.view.OutputView; @@ -11,49 +12,51 @@ public class ConsoleChessController { private final InputView inputView; private final OutputView outputView; + private final ChessService chessService; public ConsoleChessController(final InputView inputView, final OutputView outputView) { this.inputView = inputView; this.outputView = outputView; + this.chessService = new ChessService(); } public void run() { outputView.printGuide(); - ChessGame chessGame = new ChessGame(); - while (chessGame.isRunning()) { + while (chessService.isGameRunning()) { try { - outputView.printTurn(chessGame.isWhiteTurn()); + outputView.printTurn(chessService.getCurrentTurn()); Command command = new Command(inputView.getCommand()); - runCommand(chessGame, command); - printBoard(chessGame); + run(command); + printBoard(); } catch (UnsupportedOperationException e) { System.out.println(e.getMessage()); } } - printBoard(chessGame); - printStatus(chessGame); + printBoard(); + printStatus(); } - private void runCommand(final ChessGame chessGame, final Command command) { + private void run(final Command command) { try { if (command.isStart()) { return; } if (command.isEnd()) { - chessGame.end(); + chessService.finish(); return; } if (command.isMove()) { - chessGame.move(command.getMoveParameters()); + MoveParameters parameters = command.getMoveParameters(); + chessService.movePiece(parameters); return; } if (command.isStatus()) { - printStatus(chessGame); + printStatus(); return; } } catch (IllegalArgumentException e) { @@ -64,13 +67,13 @@ private void runCommand(final ChessGame chessGame, final Command command) { throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); } - private void printBoard(final ChessGame chessGame) { - BoardDto boardDto = new BoardDto(chessGame.getBoard()); - outputView.printBoard(boardDto); + private void printBoard() { + BoardConsoleDto boardConsoleDto = chessService.getBoardConsoleView(); + outputView.printBoard(boardConsoleDto); } - private void printStatus(final ChessGame chessGame) { - Status status = chessGame.getStatus(); + private void printStatus() { + Status status = chessService.getStatus(); outputView.printStatus(status); } } diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java index 8f184a4..b79ca21 100644 --- a/src/main/java/chess/controller/WebChessController.java +++ b/src/main/java/chess/controller/WebChessController.java @@ -74,7 +74,7 @@ private String nextPage() { private Map getGameInfo() { Map model = new HashMap<>(); - model.put("board", chessService.getBoardView()); + model.put("board", chessService.getBoardWebView()); model.put("currentTurn", chessService.getCurrentTurn()); return model; } diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java deleted file mode 100644 index 72264a3..0000000 --- a/src/main/java/chess/domain/ChessGame.java +++ /dev/null @@ -1,59 +0,0 @@ -package chess.domain; - -import chess.domain.board.Board; -import chess.domain.board.Status; -import chess.domain.command.MoveParameters; -import chess.domain.piece.Color; - -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; - -public class ChessGame { - - private final Board board = new Board(); - private Color currentTurn = WHITE; - private boolean isFinished = false; - - public ChessGame() { - } - - public void move(final MoveParameters moveParameters) { - board.move(moveParameters, currentTurn); - changeTurn(); - - if (board.isKingDead()) { - end(); - } - } - - private void changeTurn() { - if (currentTurn == WHITE) { - currentTurn = BLACK; - return; - } - - if (currentTurn == BLACK) { - currentTurn = WHITE; - } - } - - public void end() { - isFinished = true; - } - - public Status getStatus() { - return board.getStatus(); - } - - public Board getBoard() { - return board; - } - - public boolean isWhiteTurn() { - return currentTurn.isWhite(); - } - - public boolean isRunning() { - return !isFinished; - } -} diff --git a/src/main/java/chess/dto/console/BoardDto.java b/src/main/java/chess/dto/console/BoardConsoleDto.java similarity index 93% rename from src/main/java/chess/dto/console/BoardDto.java rename to src/main/java/chess/dto/console/BoardConsoleDto.java index efac96e..031c642 100644 --- a/src/main/java/chess/dto/console/BoardDto.java +++ b/src/main/java/chess/dto/console/BoardConsoleDto.java @@ -12,10 +12,11 @@ import java.util.Collections; import java.util.List; -public class BoardDto { +public class BoardConsoleDto { + private List positionDtos = new ArrayList<>(); - public BoardDto(final Board board) { + public BoardConsoleDto(final Board board) { Arrays.stream(Rank.values()).forEach(rank -> Arrays.stream( File.values()).forEach(file -> addPositionDto(file, rank, board))); } diff --git a/src/main/java/chess/dto/web/BoardDto.java b/src/main/java/chess/dto/web/BoardWebDto.java similarity index 94% rename from src/main/java/chess/dto/web/BoardDto.java rename to src/main/java/chess/dto/web/BoardWebDto.java index 875ea9a..b19d314 100644 --- a/src/main/java/chess/dto/web/BoardDto.java +++ b/src/main/java/chess/dto/web/BoardWebDto.java @@ -8,9 +8,9 @@ import java.util.HashMap; import java.util.Map; -public class BoardDto { +public class BoardWebDto { - private BoardDto() { + private BoardWebDto() { } private static final String EMPTY_PIECE = "."; diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index c5e257c..21c5cb2 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -4,7 +4,8 @@ import chess.domain.board.Status; import chess.domain.command.MoveParameters; import chess.domain.piece.Color; -import chess.dto.web.BoardDto; +import chess.dto.console.BoardConsoleDto; +import chess.dto.web.BoardWebDto; import java.util.Map; @@ -27,8 +28,16 @@ public boolean isGameFinished() { return isFinished; } - public Map getBoardView() { - return BoardDto.of(board); + public boolean isGameRunning() { + return !isFinished; + } + + public BoardConsoleDto getBoardConsoleView() { + return new BoardConsoleDto(board); + } + + public Map getBoardWebView() { + return BoardWebDto.of(board); } public String getCurrentTurn() { @@ -39,8 +48,8 @@ public Status getStatus() { return board.getStatus(); } - public void movePiece(MoveParameters moveParameters) { - board.move(moveParameters, currentTurn); + public void movePiece(MoveParameters parameters) { + board.move(parameters, currentTurn); changeTurn(); if (board.isKingDead()) { @@ -59,7 +68,7 @@ private void changeTurn() { } } - private void finish() { + public void finish() { isFinished = true; } diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index dca3ee3..f090e44 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.dto.console.BoardDto; +import chess.dto.console.BoardConsoleDto; import chess.domain.board.Status; public class ConsoleOutputView implements OutputView { @@ -18,8 +18,8 @@ public void printGuide() { } @Override - public void printBoard(final BoardDto boardDto) { - boardDto.getPositionDtos() + public void printBoard(final BoardConsoleDto boardConsoleDto) { + boardConsoleDto.getPositionDtos() .forEach(positionDto -> { System.out.print(positionDto.getName()); if (positionDto.isLastFile()) { @@ -46,12 +46,7 @@ public void printStatus(final Status status) { } @Override - public void printTurn(boolean isWhiteTurn) { - if (isWhiteTurn) { - System.out.printf(TURN_FORMAT, "WHITE"); - return; - } - - System.out.printf(TURN_FORMAT, "BLACK"); + public void printTurn(String currentTurn) { + System.out.printf(TURN_FORMAT, currentTurn); } } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 377f4af..f0bfbed 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,14 +1,14 @@ package chess.view; -import chess.dto.console.BoardDto; +import chess.dto.console.BoardConsoleDto; import chess.domain.board.Status; public interface OutputView { void printGuide(); - void printBoard(BoardDto boardDto); + void printBoard(BoardConsoleDto boardConsoleDto); void printStatus(Status status); - void printTurn(boolean isWhiteTurn); + void printTurn(String currentTurn); } From 778b8d6acd20736e085ef695924de43d1106e49c Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Tue, 31 Aug 2021 18:06:33 +0900 Subject: [PATCH 27/38] =?UTF-8?q?refactor:=20ChessService=EC=9D=98=20isGam?= =?UTF-8?q?eFinished=20flag=20=EC=A0=9C=EA=B1=B0,=20Status=EC=9D=98=20isKi?= =?UTF-8?q?ngDead=20flag=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConsoleChessController.java | 40 +++++++++---- .../chess/controller/WebChessController.java | 12 ++-- src/main/java/chess/domain/board/Board.java | 14 +++-- src/main/java/chess/domain/board/Scores.java | 19 +++++++ src/main/java/chess/domain/board/Status.java | 31 ---------- src/main/java/chess/domain/player/Player.java | 7 ++- .../chess/dto/console/BoardConsoleDto.java | 6 +- .../exception/ForcedTerminationException.java | 10 ++++ src/main/java/chess/service/ChessService.java | 57 ++++++++----------- .../java/chess/view/ConsoleOutputView.java | 29 +++++----- src/main/java/chess/view/OutputView.java | 8 ++- src/main/resources/templates/game.html | 16 +++--- src/main/resources/templates/result.html | 8 +-- .../java/chess/domain/board/BoardTest.java | 6 +- .../java/chess/domain/player/PlayerTest.java | 2 +- 15 files changed, 144 insertions(+), 121 deletions(-) create mode 100644 src/main/java/chess/domain/board/Scores.java delete mode 100644 src/main/java/chess/domain/board/Status.java create mode 100644 src/main/java/chess/exception/ForcedTerminationException.java diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index bea065f..6344075 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,9 +1,10 @@ package chess.controller; -import chess.domain.board.Status; +import chess.domain.board.Scores; import chess.domain.command.Command; import chess.domain.command.MoveParameters; import chess.dto.console.BoardConsoleDto; +import chess.exception.ForcedTerminationException; import chess.service.ChessService; import chess.view.InputView; import chess.view.OutputView; @@ -25,17 +26,19 @@ public void run() { while (chessService.isGameRunning()) { try { - outputView.printTurn(chessService.getCurrentTurn()); + outputView.printTurn(chessService.getCurrentTurnView()); Command command = new Command(inputView.getCommand()); run(command); printBoard(); } catch (UnsupportedOperationException e) { - System.out.println(e.getMessage()); + outputView.printMessage(e.getMessage()); + } catch (ForcedTerminationException e) { + outputView.printMessage(e.getMessage()); + break; } } - printBoard(); - printStatus(); + printFinalResult(); } private void run(final Command command) { @@ -45,8 +48,7 @@ private void run(final Command command) { } if (command.isEnd()) { - chessService.finish(); - return; + throw new ForcedTerminationException(); } if (command.isMove()) { @@ -56,11 +58,11 @@ private void run(final Command command) { } if (command.isStatus()) { - printStatus(); + printScores(); return; } } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + outputView.printMessage(e.getMessage()); return; } @@ -72,8 +74,22 @@ private void printBoard() { outputView.printBoard(boardConsoleDto); } - private void printStatus() { - Status status = chessService.getStatus(); - outputView.printStatus(status); + private void printScores() { + Scores scores = chessService.getScores(); + outputView.printScores(scores); + } + + private void printWinner() { + try { + outputView.printWinner(chessService.getWinnerView()); + } catch (IllegalStateException e) { + outputView.printMessage(e.getMessage()); + } + } + + private void printFinalResult() { + printBoard(); + printScores(); + printWinner(); } } diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java index b79ca21..8e55260 100644 --- a/src/main/java/chess/controller/WebChessController.java +++ b/src/main/java/chess/controller/WebChessController.java @@ -30,7 +30,7 @@ public void run() { post("/start", this::startGame); - post("/status", this::getStatus); + post("/scores", this::getScores); post("/move", this::movePiece); @@ -46,7 +46,7 @@ private String startGame(Request request, Response response) { return render(getGameInfo(), GAME_PAGE); } - private String getStatus(Request request, Response response) { + private String getScores(Request request, Response response) { return render(getGameInfoWithStatus(), GAME_PAGE); } @@ -75,13 +75,13 @@ private String nextPage() { private Map getGameInfo() { Map model = new HashMap<>(); model.put("board", chessService.getBoardWebView()); - model.put("currentTurn", chessService.getCurrentTurn()); + model.put("currentTurn", chessService.getCurrentTurnView()); return model; } private Map getGameInfoWithStatus() { Map model = getGameInfo(); - model.put("status", chessService.getStatus()); + model.put("scores", chessService.getScores()); return model; } @@ -93,9 +93,9 @@ private Map getGameInfoWithMessage(String message) { private Map getGameResult() { Map model = new HashMap<>(); - model.put("status", chessService.getStatus()); + model.put("scores", chessService.getScores()); try { - model.put("winner", chessService.getWinner()); + model.put("winner", chessService.getWinnerView()); } catch (IllegalStateException e) { model.put("message", e.getMessage()); } diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 5ad918f..74d73b2 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -103,17 +103,21 @@ public boolean isEmpty(final Position position) { return !white.hasPieceOn(position) && !black.hasPieceOn(position); } - public Status getStatus() { - double whiteScore = white.sumScores(); - double blackScore = black.sumScores(); + public Scores getScores() { + double whiteScore = white.calculateScores(); + double blackScore = black.calculateScores(); - return new Status(whiteScore, blackScore, white.isKingDead(), black.isKingDead()); + return new Scores(whiteScore, blackScore); } - public boolean isKingDead() { + public boolean isAnyKingDead() { return white.isKingDead() || black.isKingDead(); } + public boolean isBothKingAlive() { + return white.isKingAlive() && black.isKingAlive(); + } + public Color getWinner() { if (white.isKingDead()) { return BLACK; diff --git a/src/main/java/chess/domain/board/Scores.java b/src/main/java/chess/domain/board/Scores.java new file mode 100644 index 0000000..68e09e4 --- /dev/null +++ b/src/main/java/chess/domain/board/Scores.java @@ -0,0 +1,19 @@ +package chess.domain.board; + +public class Scores { + private final double whiteScore; + private final double blackScore; + + public Scores(final double whiteScore, final double blackScore) { + this.whiteScore = whiteScore; + this.blackScore = blackScore; + } + + public double getWhiteScore() { + return whiteScore; + } + + public double getBlackScore() { + return blackScore; + } +} diff --git a/src/main/java/chess/domain/board/Status.java b/src/main/java/chess/domain/board/Status.java deleted file mode 100644 index b48f253..0000000 --- a/src/main/java/chess/domain/board/Status.java +++ /dev/null @@ -1,31 +0,0 @@ -package chess.domain.board; - -public class Status { - private final double whiteScore; - private final double blackScore; - private final boolean isWhiteKingDead; - private final boolean isBlackKingDead; - - public Status(final double whiteScore, final double blackScore, boolean isWhiteKingDead, boolean isBlackKingDead) { - this.whiteScore = whiteScore; - this.blackScore = blackScore; - this.isWhiteKingDead = isWhiteKingDead; - this.isBlackKingDead = isBlackKingDead; - } - - public double getWhiteScore() { - return whiteScore; - } - - public double getBlackScore() { - return blackScore; - } - - public boolean isWhiteKingDead() { - return isWhiteKingDead; - } - - public boolean isBlackKingDead() { - return isBlackKingDead; - } -} diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index 92ebdc3..d82e38a 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -56,6 +56,11 @@ public boolean isKingDead() { .noneMatch(PieceType::isKing); } + public boolean isKingAlive() { + return pieces.values().stream() + .anyMatch(PieceType::isKing); + } + public boolean canAttack(Position position) { return attackPositions.contains(position); } @@ -69,7 +74,7 @@ public void attacked(final Position target) { pieces.remove(target); } - public double sumScores() { + public double calculateScores() { List pawnPositions = findPawnPositions(); double pawnScores = calculatePawnScores(pawnPositions); diff --git a/src/main/java/chess/dto/console/BoardConsoleDto.java b/src/main/java/chess/dto/console/BoardConsoleDto.java index 031c642..994ff92 100644 --- a/src/main/java/chess/dto/console/BoardConsoleDto.java +++ b/src/main/java/chess/dto/console/BoardConsoleDto.java @@ -16,7 +16,11 @@ public class BoardConsoleDto { private List positionDtos = new ArrayList<>(); - public BoardConsoleDto(final Board board) { + public static BoardConsoleDto of(Board board) { + return new BoardConsoleDto(board); + } + + private BoardConsoleDto(final Board board) { Arrays.stream(Rank.values()).forEach(rank -> Arrays.stream( File.values()).forEach(file -> addPositionDto(file, rank, board))); } diff --git a/src/main/java/chess/exception/ForcedTerminationException.java b/src/main/java/chess/exception/ForcedTerminationException.java new file mode 100644 index 0000000..454726c --- /dev/null +++ b/src/main/java/chess/exception/ForcedTerminationException.java @@ -0,0 +1,10 @@ +package chess.exception; + +public class ForcedTerminationException extends RuntimeException { + + private static final String message = "사용자 입력에 의해 게임이 강제 종료되었습니다."; + + public ForcedTerminationException() { + super(message); + } +} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 21c5cb2..0501606 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,7 +1,7 @@ package chess.service; import chess.domain.board.Board; -import chess.domain.board.Status; +import chess.domain.board.Scores; import chess.domain.command.MoveParameters; import chess.domain.piece.Color; import chess.dto.console.BoardConsoleDto; @@ -16,45 +16,23 @@ public class ChessService { private final Board board; private Color currentTurn; - private boolean isFinished; public ChessService() { this.board = new Board(); this.currentTurn = WHITE; - this.isFinished = false; - } - - public boolean isGameFinished() { - return isFinished; } public boolean isGameRunning() { - return !isFinished; - } - - public BoardConsoleDto getBoardConsoleView() { - return new BoardConsoleDto(board); - } - - public Map getBoardWebView() { - return BoardWebDto.of(board); + return board.isBothKingAlive(); } - public String getCurrentTurn() { - return currentTurn.name(); - } - - public Status getStatus() { - return board.getStatus(); + public boolean isGameFinished() { + return board.isAnyKingDead(); } public void movePiece(MoveParameters parameters) { board.move(parameters, currentTurn); changeTurn(); - - if (board.isKingDead()) { - finish(); - } } private void changeTurn() { @@ -68,15 +46,28 @@ private void changeTurn() { } } - public void finish() { - isFinished = true; + public Scores getScores() { + return board.getScores(); } - public String getWinner() { - if (isFinished) { - Color color = board.getWinner(); - return color.name(); + public BoardConsoleDto getBoardConsoleView() { + return BoardConsoleDto.of(board); + } + + public Map getBoardWebView() { + return BoardWebDto.of(board); + } + + public String getCurrentTurnView() { + return currentTurn.name(); + } + + public String getWinnerView() { + if (board.isBothKingAlive()) { + throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); } - throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); + + Color color = board.getWinner(); + return color.name(); } } diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index f090e44..366b9ab 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,7 +1,7 @@ package chess.view; +import chess.domain.board.Scores; import chess.dto.console.BoardConsoleDto; -import chess.domain.board.Status; public class ConsoleOutputView implements OutputView { @@ -30,23 +30,24 @@ public void printBoard(final BoardConsoleDto boardConsoleDto) { } @Override - public void printStatus(final Status status) { - if (status.isWhiteKingDead()) { - System.out.printf(WINNER_FORMAT, "BLACK"); - return; - } - - if (status.isBlackKingDead()) { - System.out.printf(WINNER_FORMAT, "WHITE"); - return; - } - - System.out.println(HEADER + "WHITE 점수: " + status.getWhiteScore()); - System.out.println(HEADER + "BLACK 점수: " + status.getBlackScore()); + public void printScores(final Scores scores) { + System.out.println(HEADER + "WHITE 점수: " + scores.getWhiteScore()); + System.out.println(HEADER + "BLACK 점수: " + scores.getBlackScore()); } @Override public void printTurn(String currentTurn) { System.out.printf(TURN_FORMAT, currentTurn); } + + @Override + public void printWinner(String winnerName) { + System.out.printf(WINNER_FORMAT, winnerName); + } + + @Override + public void printMessage(String message) { + System.out.println(HEADER + message); + System.out.println(); + } } diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index f0bfbed..238e8ad 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,14 +1,18 @@ package chess.view; +import chess.domain.board.Scores; import chess.dto.console.BoardConsoleDto; -import chess.domain.board.Status; public interface OutputView { void printGuide(); void printBoard(BoardConsoleDto boardConsoleDto); - void printStatus(Status status); + void printScores(Scores scores); void printTurn(String currentTurn); + + void printWinner(String winner); + + void printMessage(String message); } diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html index b38d28d..c89f68e 100644 --- a/src/main/resources/templates/game.html +++ b/src/main/resources/templates/game.html @@ -109,24 +109,24 @@
{{currentTurn}} 차례입니다. - {{#status}} + {{#scores}}

- WHITE: {{status.whiteScore}}점 - BLACK: {{status.blackScore}}점 - {{/status}} - {{^status}} + WHITE: {{scores.whiteScore}}점 + BLACK: {{scores.blackScore}}점 + {{/scores}} + {{^scores}} {{#message}}

{{message}} {{/message}} {{^message}} {{/message}} - {{/status}} + {{/scores}}
-
- + +
diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html index f59b4fe..8fb589b 100644 --- a/src/main/resources/templates/result.html +++ b/src/main/resources/templates/result.html @@ -26,11 +26,11 @@ {{^winner}} {{#message}} {{message}} - {{#status}} + {{#scores}}

- WHITE: {{status.whiteScore}}점
- BLACK: {{status.blackScore}}점 - {{/status}} + WHITE: {{scores.whiteScore}}점
+ BLACK: {{scores.blackScore}}점 + {{/scores}} {{/message}} {{/winner}}
diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index ce70efb..321dffd 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -130,11 +130,11 @@ void get_status() { Board board = setBoardToGetStatus(); //when - Status status = board.getStatus(); + Scores scores = board.getScores(); //then - assertThat(status.getWhiteScore()).isEqualTo(37); - assertThat(status.getBlackScore()).isEqualTo(37); + assertThat(scores.getWhiteScore()).isEqualTo(37); + assertThat(scores.getBlackScore()).isEqualTo(37); } private Board setBoardToGetStatus() { diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index 56d64aa..384d883 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -116,7 +116,7 @@ void sum_scores() { Player player = new Player(Color.WHITE); //when - double sum = player.sumScores(); + double sum = player.calculateScores(); //then assertThat(sum).isEqualTo(38); From cdc9cdad6335617aa58036d481cafe059f0049f6 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 4 Sep 2021 00:58:22 +0900 Subject: [PATCH 28/38] =?UTF-8?q?fix:=20=ED=80=B8=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + src/main/java/chess/domain/piece/type/MovePattern.java | 4 +++- src/test/java/chess/domain/piece/QueenTest.java | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3cb26bf..90ce065 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ - [x] 기물을 이동시킨다. - [x] 기물의 이동 경로를 반환한다. - [x] 입력받은 위치에 기물이 있는지 확인한다. + - [x] 보드 위 자신의 기물의 점수 총 합을 계산한다. ## 이동 규칙 diff --git a/src/main/java/chess/domain/piece/type/MovePattern.java b/src/main/java/chess/domain/piece/type/MovePattern.java index 45a4ce7..4f50bfb 100644 --- a/src/main/java/chess/domain/piece/type/MovePattern.java +++ b/src/main/java/chess/domain/piece/type/MovePattern.java @@ -47,7 +47,9 @@ private MovePattern(final Collection infiniteMoveCoordinates, fi } public static MovePattern queenPattern() { - return new MovePattern(DIAGONAL_COORDINATES, CARDINAL_COORDINATES); + List infiniteMoveCoordinates = new ArrayList<>(CARDINAL_COORDINATES); + infiniteMoveCoordinates.addAll(DIAGONAL_COORDINATES); + return new MovePattern(infiniteMoveCoordinates, Collections.emptyList()); } public static MovePattern kingPattern() { diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java index 67b1ca9..f9abd8e 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -56,7 +56,7 @@ void find_paths_success_cardinal(String targetPosition) { } @ParameterizedTest - @ValueSource(strings = {"d6", "d2", "b4", "f4", "f5"}) + @ValueSource(strings = {"f5", "e6", "b3", "c2"}) @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") void find_paths_invalid_target(String invalidTarget) { //given @@ -78,7 +78,8 @@ void find_available_attack_positions() { Collection expected = Arrays.asList( Position.of("a1"), Position.of("b2"), Position.of("c3"), Position.of("e5"), Position.of("f6"), Position.of("g7"), Position.of("h8"), Position.of("a7"), Position.of("b6"), Position.of("c5"), Position.of("e3"), Position.of("f2"), Position.of("g1"), - Position.of("d3"), Position.of("d5"), Position.of("c4"), Position.of("e4") + Position.of("d1"), Position.of("d2"), Position.of("d3"), Position.of("d5"), Position.of("d6"), Position.of("d7"), Position.of("d8"), + Position.of("a4"), Position.of("b4"), Position.of("c4"), Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4") ); //when From a0a341cd1d45fdee4a01ed76b84384c27038adff Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 4 Sep 2021 01:22:38 +0900 Subject: [PATCH 29/38] =?UTF-8?q?refactor:=20ChessGame=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=82=AD=EC=A0=9C,=20ChessService?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=ED=95=A9,=20=EA=B2=8C=EC=9E=84=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=ED=94=8C=EB=9E=98=EA=B7=B8=20=EC=88=98=EC=A0=95,=20Bo?= =?UTF-8?q?ardDto=EC=99=80=20OutputView=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/WebChessApplication.java | 15 -- .../controller/ConsoleChessController.java | 7 +- .../chess/controller/WebChessController.java | 108 ------------- .../chess/domain/player/AttackPositions.java | 4 +- .../java/chess/domain/player/Position.java | 4 +- .../{web/BoardWebDto.java => BoardDto.java} | 6 +- .../chess/dto/console/BoardConsoleDto.java | 46 ------ .../java/chess/dto/console/PositionDto.java | 28 ---- src/main/java/chess/service/ChessService.java | 15 +- .../java/chess/view/ConsoleOutputView.java | 44 ++++-- src/main/java/chess/view/OutputView.java | 5 +- src/main/resources/public/css/chessBoard.css | 30 ---- src/main/resources/public/css/index.css | 50 ------ src/main/resources/public/css/menu.css | 73 --------- src/main/resources/public/css/result.css | 16 -- src/main/resources/public/js/game.js | 26 ---- src/main/resources/public/js/menu.js | 14 -- src/main/resources/public/js/result.js | 7 - src/main/resources/public/js/start.js | 7 - src/main/resources/templates/game.html | 142 ------------------ src/main/resources/templates/index.html | 27 ---- src/main/resources/templates/result.html | 47 ------ 22 files changed, 53 insertions(+), 668 deletions(-) delete mode 100644 src/main/java/chess/WebChessApplication.java delete mode 100644 src/main/java/chess/controller/WebChessController.java rename src/main/java/chess/dto/{web/BoardWebDto.java => BoardDto.java} (92%) delete mode 100644 src/main/java/chess/dto/console/BoardConsoleDto.java delete mode 100644 src/main/java/chess/dto/console/PositionDto.java delete mode 100644 src/main/resources/public/css/chessBoard.css delete mode 100644 src/main/resources/public/css/index.css delete mode 100644 src/main/resources/public/css/menu.css delete mode 100644 src/main/resources/public/css/result.css delete mode 100644 src/main/resources/public/js/game.js delete mode 100644 src/main/resources/public/js/menu.js delete mode 100644 src/main/resources/public/js/result.js delete mode 100644 src/main/resources/public/js/start.js delete mode 100644 src/main/resources/templates/game.html delete mode 100644 src/main/resources/templates/index.html delete mode 100644 src/main/resources/templates/result.html diff --git a/src/main/java/chess/WebChessApplication.java b/src/main/java/chess/WebChessApplication.java deleted file mode 100644 index 20ed161..0000000 --- a/src/main/java/chess/WebChessApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package chess; - -import chess.controller.WebChessController; - -import static spark.Spark.staticFiles; - -public class WebChessApplication { - - public static void main(String[] args) { - staticFiles.location("/public"); - - WebChessController webChessController = new WebChessController(); - webChessController.run(); - } -} diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index 6344075..7be97b1 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -3,12 +3,13 @@ import chess.domain.board.Scores; import chess.domain.command.Command; import chess.domain.command.MoveParameters; -import chess.dto.console.BoardConsoleDto; import chess.exception.ForcedTerminationException; import chess.service.ChessService; import chess.view.InputView; import chess.view.OutputView; +import java.util.Map; + public class ConsoleChessController { private final InputView inputView; @@ -70,8 +71,8 @@ private void run(final Command command) { } private void printBoard() { - BoardConsoleDto boardConsoleDto = chessService.getBoardConsoleView(); - outputView.printBoard(boardConsoleDto); + Map boardDto = chessService.getBoardDto(); + outputView.printBoard(boardDto); } private void printScores() { diff --git a/src/main/java/chess/controller/WebChessController.java b/src/main/java/chess/controller/WebChessController.java deleted file mode 100644 index 8e55260..0000000 --- a/src/main/java/chess/controller/WebChessController.java +++ /dev/null @@ -1,108 +0,0 @@ -package chess.controller; - -import chess.domain.command.MoveParameters; -import chess.service.ChessService; -import spark.ModelAndView; -import spark.Request; -import spark.Response; -import spark.template.handlebars.HandlebarsTemplateEngine; - -import java.util.HashMap; -import java.util.Map; - -import static spark.Spark.get; -import static spark.Spark.post; - -public class WebChessController { - - private static final String LANDING_PAGE = "index.html"; - private static final String GAME_PAGE = "game.html"; - private static final String RESULT_PAGE = "result.html"; - - private final ChessService chessService; - - public WebChessController() { - this.chessService = new ChessService(); - } - - public void run() { - get("/", this::landingPage); - - post("/start", this::startGame); - - post("/scores", this::getScores); - - post("/move", this::movePiece); - - post("/result", this::finishGame); - } - - private String landingPage(Request request, Response response) { - Map model = new HashMap<>(); - return render(model, LANDING_PAGE); - } - - private String startGame(Request request, Response response) { - return render(getGameInfo(), GAME_PAGE); - } - - private String getScores(Request request, Response response) { - return render(getGameInfoWithStatus(), GAME_PAGE); - } - - private String movePiece(Request request, Response response) { - try { - MoveParameters moveParameters = new MoveParameters(request.queryParams("source"), request.queryParams("target")); - chessService.movePiece(moveParameters); - } catch (IllegalArgumentException e) { - return render(getGameInfoWithMessage(e.getMessage()), GAME_PAGE); - } - - return nextPage(); - } - - private String finishGame(Request request, Response response) { - return render(getGameResult(), RESULT_PAGE); - } - - private String nextPage() { - if (chessService.isGameFinished()) { - return render(getGameResult(), RESULT_PAGE); - } - return render(getGameInfo(), GAME_PAGE); - } - - private Map getGameInfo() { - Map model = new HashMap<>(); - model.put("board", chessService.getBoardWebView()); - model.put("currentTurn", chessService.getCurrentTurnView()); - return model; - } - - private Map getGameInfoWithStatus() { - Map model = getGameInfo(); - model.put("scores", chessService.getScores()); - return model; - } - - private Map getGameInfoWithMessage(String message) { - Map model = getGameInfo(); - model.put("message", message); - return model; - } - - private Map getGameResult() { - Map model = new HashMap<>(); - model.put("scores", chessService.getScores()); - try { - model.put("winner", chessService.getWinnerView()); - } catch (IllegalStateException e) { - model.put("message", e.getMessage()); - } - return model; - } - - private String render(Map model, String templatePath) { - return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath)); - } -} diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index 2007382..8a1f893 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -10,9 +10,11 @@ public class AttackPositions { private static final int EMPTY = 0; - private final Map counts = new HashMap<>(); + private final Map counts; public AttackPositions(final Map pieces) { + this.counts = new HashMap<>(); + pieces.keySet() .forEach(position -> { Piece piece = pieces.get(position); diff --git a/src/main/java/chess/domain/player/Position.java b/src/main/java/chess/domain/player/Position.java index 244fa86..44cd1fd 100644 --- a/src/main/java/chess/domain/player/Position.java +++ b/src/main/java/chess/domain/player/Position.java @@ -7,10 +7,10 @@ public class Position { - private static final Map POSITIONS = createPositions(); + private static final Map POSITIONS = Collections.unmodifiableMap(createPositions()); private static Map createPositions() { - Map positions = new LinkedHashMap<>(); + Map positions = new HashMap<>(); Arrays.stream(File.values()) .forEach(file -> Arrays.stream(Rank.values()) diff --git a/src/main/java/chess/dto/web/BoardWebDto.java b/src/main/java/chess/dto/BoardDto.java similarity index 92% rename from src/main/java/chess/dto/web/BoardWebDto.java rename to src/main/java/chess/dto/BoardDto.java index b19d314..9f43605 100644 --- a/src/main/java/chess/dto/web/BoardWebDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -1,4 +1,4 @@ -package chess.dto.web; +package chess.dto; import chess.domain.board.Board; import chess.domain.piece.Piece; @@ -8,9 +8,9 @@ import java.util.HashMap; import java.util.Map; -public class BoardWebDto { +public class BoardDto { - private BoardWebDto() { + private BoardDto() { } private static final String EMPTY_PIECE = "."; diff --git a/src/main/java/chess/dto/console/BoardConsoleDto.java b/src/main/java/chess/dto/console/BoardConsoleDto.java deleted file mode 100644 index 994ff92..0000000 --- a/src/main/java/chess/dto/console/BoardConsoleDto.java +++ /dev/null @@ -1,46 +0,0 @@ -package chess.dto.console; - -import chess.domain.board.Board; -import chess.domain.board.File; -import chess.domain.board.Rank; -import chess.domain.piece.Piece; -import chess.domain.piece.type.PieceType; -import chess.domain.player.Position; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class BoardConsoleDto { - - private List positionDtos = new ArrayList<>(); - - public static BoardConsoleDto of(Board board) { - return new BoardConsoleDto(board); - } - - private BoardConsoleDto(final Board board) { - Arrays.stream(Rank.values()).forEach(rank -> Arrays.stream( - File.values()).forEach(file -> addPositionDto(file, rank, board))); - } - - private void addPositionDto(final File file, final Rank rank, final Board board) { - Position position = Position.from(file, rank); - - if (board.isEmpty(position)) { - PositionDto positionDto = new PositionDto(position.getFile()); - positionDtos.add(positionDto); - return; - } - - Piece piece = board.findBy(position); - String name = PieceType.findNameBy(piece); - PositionDto positionDto = new PositionDto(position.getFile(), name); - positionDtos.add(positionDto); - } - - public List getPositionDtos() { - return Collections.unmodifiableList(positionDtos); - } -} diff --git a/src/main/java/chess/dto/console/PositionDto.java b/src/main/java/chess/dto/console/PositionDto.java deleted file mode 100644 index 5a0d5ae..0000000 --- a/src/main/java/chess/dto/console/PositionDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package chess.dto.console; - -import chess.domain.board.File; - -public class PositionDto { - - private static final String DEFAULT_NAME = "."; - - private final boolean isLastFile; - private final String name; - - public PositionDto(final File file, final String name) { - this.isLastFile = (file == File.h); - this.name = name; - } - - public PositionDto(final File file) { - this(file, DEFAULT_NAME); - } - - public boolean isLastFile() { - return isLastFile; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 0501606..2993af7 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -4,8 +4,7 @@ import chess.domain.board.Scores; import chess.domain.command.MoveParameters; import chess.domain.piece.Color; -import chess.dto.console.BoardConsoleDto; -import chess.dto.web.BoardWebDto; +import chess.dto.BoardDto; import java.util.Map; @@ -26,10 +25,6 @@ public boolean isGameRunning() { return board.isBothKingAlive(); } - public boolean isGameFinished() { - return board.isAnyKingDead(); - } - public void movePiece(MoveParameters parameters) { board.move(parameters, currentTurn); changeTurn(); @@ -50,12 +45,8 @@ public Scores getScores() { return board.getScores(); } - public BoardConsoleDto getBoardConsoleView() { - return BoardConsoleDto.of(board); - } - - public Map getBoardWebView() { - return BoardWebDto.of(board); + public Map getBoardDto() { + return BoardDto.of(board); } public String getCurrentTurnView() { diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 366b9ab..e5b6214 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,7 +1,8 @@ package chess.view; import chess.domain.board.Scores; -import chess.dto.console.BoardConsoleDto; + +import java.util.Map; public class ConsoleOutputView implements OutputView { @@ -9,6 +10,10 @@ public class ConsoleOutputView implements OutputView { private static final String TURN_FORMAT = HEADER + "%s의 차례입니다.%n"; private static final String WINNER_FORMAT = HEADER + "%s의 승리입니다. 축하합니다.%n"; + private static final int FILE = 0; + private static final int RANK = 1; + private static final String NEW_LINE = "\n"; + @Override public void printGuide() { System.out.println(HEADER + "체스 게임을 실행합니다."); @@ -18,15 +23,36 @@ public void printGuide() { } @Override - public void printBoard(final BoardConsoleDto boardConsoleDto) { - boardConsoleDto.getPositionDtos() - .forEach(positionDto -> { - System.out.print(positionDto.getName()); - if (positionDto.isLastFile()) { - System.out.println(); - } + public void printBoard(final Map boardDto) { + StringBuilder sb = new StringBuilder(); + + boardDto.keySet().stream() + .sorted(this::comparePositionName) + .forEach(positionName -> { + String pieceName = boardDto.get(positionName); + sb.append(pieceName); }); - System.out.println(); + + insertNewLineAtTheEndOfEachRank(sb); + + System.out.println(sb); + } + + private int comparePositionName(String o1, String o2) { + if (o1.charAt(RANK) == o2.charAt(RANK)) { + return Character.compare(o1.charAt(FILE), o2.charAt(FILE)); + } + return Character.compare(o2.charAt(RANK), o1.charAt(RANK)); + } + + private void insertNewLineAtTheEndOfEachRank(StringBuilder sb) { + for (int rank = 1; rank <= 7; rank++) { + sb.insert(endOf(rank), NEW_LINE); + } + } + + private int endOf(int rank) { + return rank * 8 + rank - 1; } @Override diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 238e8ad..bc39768 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,12 +1,13 @@ package chess.view; import chess.domain.board.Scores; -import chess.dto.console.BoardConsoleDto; + +import java.util.Map; public interface OutputView { void printGuide(); - void printBoard(BoardConsoleDto boardConsoleDto); + void printBoard(Map boardDto); void printScores(Scores scores); diff --git a/src/main/resources/public/css/chessBoard.css b/src/main/resources/public/css/chessBoard.css deleted file mode 100644 index efd67f4..0000000 --- a/src/main/resources/public/css/chessBoard.css +++ /dev/null @@ -1,30 +0,0 @@ -.chess-board-wrapper { - width: 430px; - height: 430px; - display: flex; - place-content: center; -} - -.chess-board { - width: 400px; - height: 400px; - border: 15px solid #1E201F; -} - -.cell { - width: 50px; - height: 50px; - display: table-cell; - vertical-align: middle; - float: left; - text-align: center; - font-size: 40px; -} - -.dark { - background-color: #8D6F59; -} - -.light { - background-color: #E4CBB3; -} \ No newline at end of file diff --git a/src/main/resources/public/css/index.css b/src/main/resources/public/css/index.css deleted file mode 100644 index de82f50..0000000 --- a/src/main/resources/public/css/index.css +++ /dev/null @@ -1,50 +0,0 @@ -body { - width: 100%; - margin: 0 auto; - font-family: "배달의민족 한나는 열한살", system-ui; - text-align: center; -} - -.main-title { - height: 2em; - line-height: 2em; - font-size: 1.3em; - font-weight: bolder; - color: snow; - background-color: #333333; -} - -.mini-title { - font-size: 1.5em; - padding-top: 1em; - color: #333333; -} - -.guide { - font-size: 0.8em; - padding-top: 0.4em; - color: #333333; -} - -section { - margin-top: 20px; -} - -.button { - width: 160px; - height: 3em; - line-height: 3em; - font-size: 1.0em; - font-family: "배달의민족 한나는 열한살", system-ui; -} - -.box-border { - border: 2px solid; - border-radius: 5px; - background-color: rgba(0, 0, 0, 0); -} - -footer { - padding-bottom: 30px; - place-content: center; -} \ No newline at end of file diff --git a/src/main/resources/public/css/menu.css b/src/main/resources/public/css/menu.css deleted file mode 100644 index a2e5e81..0000000 --- a/src/main/resources/public/css/menu.css +++ /dev/null @@ -1,73 +0,0 @@ -.game-wrapper { - width: 630px; - height: 430px; - padding: 15px; - background: #eeeeee; - border-radius: 10px; - display: flex; - margin: auto; - place-content: center; -} - -.menu-wrapper { - width: 180px; - margin-left: 15px; - place-content: center; -} - -.move-form-wrapper { - width: 150px; - height: 135px; - padding: 15px; - background: white; - border-radius: 10px; -} - -.input-label { - height: auto; - font-size: 1em; -} - -.input-box { - width: 50px; - margin-left: 5px; - margin-top: 15px; -} - -.move-button { - width: 90px; - height: 1.8em; - margin-top: 20px; - margin-bottom: 10px; - line-height: 1.8em; - font-size: 1.1em; - font-family: "배달의민족 한나는 열한살", system-ui; -} - -.message-wrapper { - width: 150px; - height: 90px; - padding: 15px; - margin-top: 15px; - background: white; - border-radius: 10px; - display: flex; - align-items: center; - justify-content: center; -} - -.message-content { - font-size: 18px; -} - -.menu-button { - width: 180px; - height: 2.3em; - line-height: 2.3em; - margin-top: 15px; - background: white; - border-radius: 10px; - border: 0px solid; - font-size: 1.3em; - font-family: "배달의민족 한나는 열한살", system-ui; -} \ No newline at end of file diff --git a/src/main/resources/public/css/result.css b/src/main/resources/public/css/result.css deleted file mode 100644 index f8c8b72..0000000 --- a/src/main/resources/public/css/result.css +++ /dev/null @@ -1,16 +0,0 @@ -.result-wrapper { - width: 400px; - height: 150px; - padding: 20px; - margin: 30px auto; - background: #EEEEEE; - border-radius: 10px; - display: flex; - align-items: center; - justify-content: center; -} - -.result-content { - font-size: 20px; - line-height: 25px; -} diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js deleted file mode 100644 index 8b7bf48..0000000 --- a/src/main/resources/public/js/game.js +++ /dev/null @@ -1,26 +0,0 @@ -const nameSymbolMap = new Map( - [ - ["k", "♔"], - ["q", "♕"], - ["r", "♖"], - ["b", "♗"], - ["n", "♘"], - ["p", "♙"], - - ["K", "♚"], - ["Q", "♛"], - ["R", "♜"], - ["B", "♝"], - ["N", "♞"], - ["P", "♟"], - - [".", ""] - ] -); - -window.onload = function () { - document.querySelectorAll(".cell") - .forEach(element => { - element.innerText = nameSymbolMap.get(element.innerText); - }); -} \ No newline at end of file diff --git a/src/main/resources/public/js/menu.js b/src/main/resources/public/js/menu.js deleted file mode 100644 index ce4f0fb..0000000 --- a/src/main/resources/public/js/menu.js +++ /dev/null @@ -1,14 +0,0 @@ -const endGame = document.getElementById("end-game"); -const endButton = document.getElementById("end-button"); -const status = document.getElementById("status"); -const statusButton = document.getElementById("status-button"); - -endButton.onclick = () => { - alert("게임 종료"); - endGame.submit(); -}; - -statusButton.onclick = () => { - alert("현재 스코어"); - status.submit(); -}; \ No newline at end of file diff --git a/src/main/resources/public/js/result.js b/src/main/resources/public/js/result.js deleted file mode 100644 index 69a0e45..0000000 --- a/src/main/resources/public/js/result.js +++ /dev/null @@ -1,7 +0,0 @@ -const returnToMain = document.getElementById("return-to-main"); -const returnButton = document.getElementById("return-button"); - -returnButton.onclick = () => { - alert("초기 화면으로 돌아갑니다"); - returnToMain.submit(); -}; \ No newline at end of file diff --git a/src/main/resources/public/js/start.js b/src/main/resources/public/js/start.js deleted file mode 100644 index 99ad88b..0000000 --- a/src/main/resources/public/js/start.js +++ /dev/null @@ -1,7 +0,0 @@ -const startGame = document.getElementById("start-game"); -const startButton = document.getElementById("start-button"); - -startButton.onclick = () => { - alert("게임 시작"); - startGame.submit(); -}; \ No newline at end of file diff --git a/src/main/resources/templates/game.html b/src/main/resources/templates/game.html deleted file mode 100644 index c89f68e..0000000 --- a/src/main/resources/templates/game.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - Web Chess Game - - - - - - - - - - - -
-
♟️ 체스 게임이 진행 중입니다 ♟️
-
- -
-
- -
-
-
{{board.a8}}
-
{{board.b8}}
-
{{board.c8}}
-
{{board.d8}}
-
{{board.e8}}
-
{{board.f8}}
-
{{board.g8}}
-
{{board.h8}}
-
{{board.a7}}
-
{{board.b7}}
-
{{board.c7}}
-
{{board.d7}}
-
{{board.e7}}
-
{{board.f7}}
-
{{board.g7}}
-
{{board.h7}}
-
{{board.a6}}
-
{{board.b6}}
-
{{board.c6}}
-
{{board.d6}}
-
{{board.e6}}
-
{{board.f6}}
-
{{board.g6}}
-
{{board.h6}}
-
{{board.a5}}
-
{{board.b5}}
-
{{board.c5}}
-
{{board.d5}}
-
{{board.e5}}
-
{{board.f5}}
-
{{board.g5}}
-
{{board.h5}}
-
{{board.a4}}
-
{{board.b4}}
-
{{board.c4}}
-
{{board.d4}}
-
{{board.e4}}
-
{{board.f4}}
-
{{board.g4}}
-
{{board.h4}}
-
{{board.a3}}
-
{{board.b3}}
-
{{board.c3}}
-
{{board.d3}}
-
{{board.e3}}
-
{{board.f3}}
-
{{board.g3}}
-
{{board.h3}}
-
{{board.a2}}
-
{{board.b2}}
-
{{board.c2}}
-
{{board.d2}}
-
{{board.e2}}
-
{{board.f2}}
-
{{board.g2}}
-
{{board.h2}}
-
{{board.a1}}
-
{{board.b1}}
-
{{board.c1}}
-
{{board.d1}}
-
{{board.e1}}
-
{{board.f1}}
-
{{board.g1}}
-
{{board.h1}}
-
-
- - -
-
-
-
- - - \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html deleted file mode 100644 index 306a7ea..0000000 --- a/src/main/resources/templates/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Web Chess Game - - - - - - -
-
♟️ 체스 게임 시작하기 ♟️
-
새로운 체스 게임을 시작합니다.
-
- -
-
- -
-
- - - - - \ No newline at end of file diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html deleted file mode 100644 index 8fb589b..0000000 --- a/src/main/resources/templates/result.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - Web Chess Game - - - - - - - - -
-
♟️ 체스 게임이 종료되었습니다 ♟️
-
게임 결과
-
- -
-
-
- {{#winner}} - {{winner}}가 승리했습니다.
축하합니다! - {{/winner}} - {{^winner}} - {{#message}} - {{message}} - {{#scores}} -

- WHITE: {{scores.whiteScore}}점
- BLACK: {{scores.blackScore}}점 - {{/scores}} - {{/message}} - {{/winner}} -
-
- -
- -
-
- - - - - \ No newline at end of file From b3770b207529fc10897bd16eb8fc728f3b08a0bd Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 4 Sep 2021 03:40:25 +0900 Subject: [PATCH 30/38] =?UTF-8?q?refactor:=20Controller=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=EC=9D=84=20Service,=20Command,=20Operation=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConsoleChessController.java | 56 ++++++------------ .../java/chess/domain/command/Command.java | 39 ++++++------- .../java/chess/domain/command/Operation.java | 57 +++++++++++++++++++ .../exception/ScoresRequestedException.java | 4 ++ src/main/java/chess/service/ChessService.java | 26 ++++++++- 5 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 src/main/java/chess/domain/command/Operation.java create mode 100644 src/main/java/chess/exception/ScoresRequestedException.java diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index 7be97b1..e8a0e63 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -2,8 +2,8 @@ import chess.domain.board.Scores; import chess.domain.command.Command; -import chess.domain.command.MoveParameters; import chess.exception.ForcedTerminationException; +import chess.exception.ScoresRequestedException; import chess.service.ChessService; import chess.view.InputView; import chess.view.OutputView; @@ -27,12 +27,7 @@ public void run() { while (chessService.isGameRunning()) { try { - outputView.printTurn(chessService.getCurrentTurnView()); - Command command = new Command(inputView.getCommand()); - run(command); - printBoard(); - } catch (UnsupportedOperationException e) { - outputView.printMessage(e.getMessage()); + runOneTurn(); } catch (ForcedTerminationException e) { outputView.printMessage(e.getMessage()); break; @@ -42,32 +37,25 @@ public void run() { printFinalResult(); } - private void run(final Command command) { + private void runOneTurn() { try { - if (command.isStart()) { - return; - } + String currentTurn = chessService.getCurrentTurnDto(); + outputView.printTurn(currentTurn); - if (command.isEnd()) { - throw new ForcedTerminationException(); - } - - if (command.isMove()) { - MoveParameters parameters = command.getMoveParameters(); - chessService.movePiece(parameters); - return; - } + Command command = new Command(inputView.getCommand()); + chessService.run(command); - if (command.isStatus()) { - printScores(); - return; - } - } catch (IllegalArgumentException e) { + } catch (UnsupportedOperationException | IllegalArgumentException e) { outputView.printMessage(e.getMessage()); - return; + } catch (ScoresRequestedException e) { + Scores scores = chessService.getScores(); + outputView.printScores(scores); } + } - throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); + private void printFinalResult() { + printBoard(); + printWinner(); } private void printBoard() { @@ -75,22 +63,12 @@ private void printBoard() { outputView.printBoard(boardDto); } - private void printScores() { - Scores scores = chessService.getScores(); - outputView.printScores(scores); - } - private void printWinner() { try { - outputView.printWinner(chessService.getWinnerView()); + String winner = chessService.getWinnerDto(); + outputView.printWinner(winner); } catch (IllegalStateException e) { outputView.printMessage(e.getMessage()); } } - - private void printFinalResult() { - printBoard(); - printScores(); - printWinner(); - } } diff --git a/src/main/java/chess/domain/command/Command.java b/src/main/java/chess/domain/command/Command.java index 5aff42b..53c19a6 100644 --- a/src/main/java/chess/domain/command/Command.java +++ b/src/main/java/chess/domain/command/Command.java @@ -7,14 +7,9 @@ public class Command { private static final String DELIMITER = " "; - private static final int COMMAND_INDEX = 0; + private static final int OPERATION_INDEX = 0; - private static final String START = "start"; - private static final String END = "end"; - private static final String MOVE = "move"; - private static final String STATUS = "status"; - - private final String command; + private final Operation operation; private final List parameters; public Command(final String commandLine) { @@ -24,34 +19,34 @@ public Command(final String commandLine) { .collect(Collectors.toList()); if (chunks.isEmpty()) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("명령어를 입력해주세요."); + } + + this.operation = Operation.of(chunks.get(OPERATION_INDEX)); + this.parameters = chunks.subList(OPERATION_INDEX + 1, chunks.size()); + } + + public MoveParameters getMoveParameters() { + if (parameters.size() != 2) { + throw new IllegalArgumentException("기물 이동 위치를 정확하게 입력해주세요."); } - this.command = chunks.get(COMMAND_INDEX); - this.parameters = chunks.subList(COMMAND_INDEX + 1, chunks.size()); + return new MoveParameters(parameters); } public boolean isStart() { - return command.equals(START); + return operation.isStart(); } public boolean isEnd() { - return command.equals(END); + return operation.isEnd(); } public boolean isMove() { - return command.equals(MOVE); + return operation.isMove(); } public boolean isStatus() { - return command.equals(STATUS); - } - - public MoveParameters getMoveParameters() { - if (parameters.size() != 2) { - throw new IllegalArgumentException(); - } - - return new MoveParameters(parameters); + return operation.isStatus(); } } diff --git a/src/main/java/chess/domain/command/Operation.java b/src/main/java/chess/domain/command/Operation.java new file mode 100644 index 0000000..3ba2729 --- /dev/null +++ b/src/main/java/chess/domain/command/Operation.java @@ -0,0 +1,57 @@ +package chess.domain.command; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public enum Operation { + + START("start"), + END("end"), + MOVE("move"), + STATUS("status"); + + private static final Map operationNames = createMap(); + + private static Map createMap() { + Map map = new HashMap<>(); + + Arrays.stream(Operation.values()) + .forEach(operation -> map.put(operation.keyword, operation)); + + return Collections.unmodifiableMap(map); + } + + public static Operation of(String input) { + Operation operation = operationNames.get(input); + + if (operation == null) { + throw new UnsupportedOperationException("유효하지 않은 명령어입니다."); + } + + return operation; + } + + private final String keyword; + + Operation(String keyword) { + this.keyword = keyword; + } + + public boolean isStart() { + return this == START; + } + + public boolean isEnd() { + return this == END; + } + + public boolean isMove() { + return this == MOVE; + } + + public boolean isStatus() { + return this == STATUS; + } +} diff --git a/src/main/java/chess/exception/ScoresRequestedException.java b/src/main/java/chess/exception/ScoresRequestedException.java new file mode 100644 index 0000000..4524816 --- /dev/null +++ b/src/main/java/chess/exception/ScoresRequestedException.java @@ -0,0 +1,4 @@ +package chess.exception; + +public class ScoresRequestedException extends RuntimeException { +} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 2993af7..7641402 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -2,9 +2,12 @@ import chess.domain.board.Board; import chess.domain.board.Scores; +import chess.domain.command.Command; import chess.domain.command.MoveParameters; import chess.domain.piece.Color; import chess.dto.BoardDto; +import chess.exception.ForcedTerminationException; +import chess.exception.ScoresRequestedException; import java.util.Map; @@ -21,6 +24,25 @@ public ChessService() { this.currentTurn = WHITE; } + public void run(Command command) { + if (command.isStart()) { + return; + } + + if (command.isEnd()) { + throw new ForcedTerminationException(); + } + + if (command.isStatus()) { + throw new ScoresRequestedException(); + } + + if (command.isMove()) { + MoveParameters parameters = command.getMoveParameters(); + movePiece(parameters); + } + } + public boolean isGameRunning() { return board.isBothKingAlive(); } @@ -49,11 +71,11 @@ public Map getBoardDto() { return BoardDto.of(board); } - public String getCurrentTurnView() { + public String getCurrentTurnDto() { return currentTurn.name(); } - public String getWinnerView() { + public String getWinnerDto() { if (board.isBothKingAlive()) { throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); } From d97f27123ef228bec59d4294fa2d3569d632267f Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Sat, 4 Sep 2021 05:05:00 +0900 Subject: [PATCH 31/38] =?UTF-8?q?refactor:=20Board=20=EA=B0=80=EB=8F=85?= =?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0,=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EA=B8=B8=EC=9D=B4=20=EC=A0=9C=ED=95=9C=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConsoleChessController.java | 3 +- src/main/java/chess/domain/board/Board.java | 122 ++++++++++-------- src/main/java/chess/domain/board/File.java | 1 + .../domain/{player => board}/Position.java | 37 +++--- src/main/java/chess/domain/board/Rank.java | 1 + .../chess/domain/command/MoveParameters.java | 2 +- src/main/java/chess/domain/piece/Color.java | 8 ++ src/main/java/chess/domain/piece/Pawn.java | 38 +++--- src/main/java/chess/domain/piece/Piece.java | 16 +-- .../java/chess/domain/piece/PieceFactory.java | 2 +- .../chess/domain/piece/type/MovePattern.java | 75 ++++++----- .../type/MoveUnit.java} | 7 +- .../chess/domain/player/AttackPositions.java | 1 + src/main/java/chess/domain/player/Player.java | 21 +-- .../domain/{board => player}/Scores.java | 3 +- src/main/java/chess/dto/BoardDto.java | 2 +- src/main/java/chess/service/ChessService.java | 27 +--- .../java/chess/view/ConsoleOutputView.java | 2 +- src/main/java/chess/view/OutputView.java | 2 +- .../java/chess/domain/board/BoardTest.java | 109 ++++------------ .../java/chess/domain/piece/BishopTest.java | 2 +- .../java/chess/domain/piece/KingTest.java | 2 +- .../java/chess/domain/piece/KnightTest.java | 2 +- .../java/chess/domain/piece/PawnTest.java | 2 +- .../java/chess/domain/piece/QueenTest.java | 2 +- .../java/chess/domain/piece/RookTest.java | 2 +- .../domain/piece/type/MovePatternTest.java | 11 +- .../domain/player/AttackPositionsTest.java | 1 + .../java/chess/domain/player/PlayerTest.java | 7 +- .../chess/domain/player/PositionTest.java | 25 +++- 30 files changed, 249 insertions(+), 286 deletions(-) rename src/main/java/chess/domain/{player => board}/Position.java (69%) rename src/main/java/chess/domain/{player/MoveCoordinate.java => piece/type/MoveUnit.java} (94%) rename src/main/java/chess/domain/{board => player}/Scores.java (92%) diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index e8a0e63..246f3df 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,6 +1,6 @@ package chess.controller; -import chess.domain.board.Scores; +import chess.domain.player.Scores; import chess.domain.command.Command; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; @@ -28,6 +28,7 @@ public void run() { while (chessService.isGameRunning()) { try { runOneTurn(); + printBoard(); } catch (ForcedTerminationException e) { outputView.printMessage(e.getMessage()); break; diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 74d73b2..fcaf6da 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -4,7 +4,7 @@ import chess.domain.piece.Color; import chess.domain.piece.Piece; import chess.domain.player.Player; -import chess.domain.player.Position; +import chess.domain.player.Scores; import java.util.Set; @@ -12,45 +12,35 @@ import static chess.domain.piece.Color.WHITE; public class Board { - private final Player white; - private final Player black; + + private final Player whitePlayer; + private final Player blackPlayer; + private Color currentTurn; public Board() { - this.white = new Player(Color.WHITE); - this.black = new Player(BLACK); + this.whitePlayer = new Player(WHITE); + this.blackPlayer = new Player(BLACK); + this.currentTurn = WHITE; } - public void move(final MoveParameters moveParameters, final Color currentTurn) { - Player player = currentPlayer(currentTurn); - Player enemy = enemyPlayer(currentTurn); + public void move(final MoveParameters moveParameters) { Position source = moveParameters.getSource(); Position target = moveParameters.getTarget(); + validateParameters(source, target); - validateSourceOwner(enemy, source); - validateSamePosition(source, target); - validateTarget(player, target); - validateKingMovable(player, enemy, source, target); - - enemy.attacked(target); - movePiece(player, source, target); - } - - private Player currentPlayer(final Color currentTurn) { - if (currentTurn == Color.WHITE) { - return white; - } - return black; + movePiece(source, target); + currentTurn = currentTurn.nextTurn(); } - private Player enemyPlayer(final Color currentTurn) { - if (currentTurn == Color.WHITE) { - return black; - } - return white; + private void validateParameters(final Position source, final Position target) { + validateSourceOwner(source); + validateSamePosition(source, target); + validateTargetOwner(target); + validateKingMovable(source, target); } - private void validateSourceOwner(final Player enemy, final Position source) { - if (enemy.hasPieceOn(source)) { + private void validateSourceOwner(final Position source) { + if (currentPlayer().hasNoPieceOn(source)) { throw new IllegalArgumentException("자신의 기물만 움직일 수 있습니다."); } } @@ -61,67 +51,85 @@ private void validateSamePosition(final Position source, final Position target) } } - private void validateTarget(final Player player, final Position target) { - if (player.hasPieceOn(target)) { - throw new IllegalArgumentException("같은 색상의 기물은 공격할 수 없습니다."); + private void validateTargetOwner(final Position target) { + if (currentPlayer().hasPieceOn(target)) { + throw new IllegalArgumentException("자신의 기물이 있는 곳으로 이동할 수 없습니다."); } } - private void validateKingMovable(final Player player, final Player enemy, final Position source, final Position target) { - if (player.hasKingOn(source) && enemy.canAttack(target)) { + private void validateKingMovable(final Position source, final Position target) { + if (currentPlayer().hasKingOn(source) && enemyPlayer().canAttack(target)) { throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } } - private void movePiece(final Player player, final Position source, final Position target) { - Set paths = player.findPaths(source, target); + private Player currentPlayer() { + if (currentTurn.isWhite()) { + return whitePlayer; + } + return blackPlayer; + } + + private Player enemyPlayer() { + if (currentTurn.isWhite()) { + return blackPlayer; + } + return whitePlayer; + } + + private void movePiece(final Position source, final Position target) { + Set paths = currentPlayer().findPaths(source, target); validatePathsEmpty(paths); - player.update(source, target); + enemyPlayer().isUnderAttack(target); + currentPlayer().move(source, target); } private void validatePathsEmpty(final Set paths) { boolean isWhiteBlocked = paths.stream() - .anyMatch(white::hasPieceOn); + .anyMatch(whitePlayer::hasPieceOn); boolean isBlackBlocked = paths.stream() - .anyMatch(black::hasPieceOn); + .anyMatch(blackPlayer::hasPieceOn); if (isWhiteBlocked || isBlackBlocked) { - throw new IllegalArgumentException("기물을 통과하여 이동할 수 없습니다."); + throw new IllegalArgumentException("다른 기물을 통과하여 이동할 수 없습니다."); } } + public boolean isEmpty(final Position position) { + return whitePlayer.hasNoPieceOn(position) && blackPlayer.hasNoPieceOn(position); + } + public Piece findBy(final Position position) { - if (white.hasPieceOn(position)) { - return white.findPieceBy(position); + if (whitePlayer.hasPieceOn(position)) { + return whitePlayer.findPieceBy(position); } - - return black.findPieceBy(position); + return blackPlayer.findPieceBy(position); } - public boolean isEmpty(final Position position) { - return !white.hasPieceOn(position) && !black.hasPieceOn(position); + public Color getCurrentTurn() { + return currentTurn; } public Scores getScores() { - double whiteScore = white.calculateScores(); - double blackScore = black.calculateScores(); + double whiteScore = whitePlayer.calculateScores(); + double blackScore = blackPlayer.calculateScores(); return new Scores(whiteScore, blackScore); } - public boolean isAnyKingDead() { - return white.isKingDead() || black.isKingDead(); - } - - public boolean isBothKingAlive() { - return white.isKingAlive() && black.isKingAlive(); - } - public Color getWinner() { - if (white.isKingDead()) { + if (isBothKingAlive()) { + throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); + } + + if (whitePlayer.isKingDead()) { return BLACK; } return WHITE; } + + public boolean isBothKingAlive() { + return whitePlayer.isKingAlive() && blackPlayer.isKingAlive(); + } } diff --git a/src/main/java/chess/domain/board/File.java b/src/main/java/chess/domain/board/File.java index 8a18a09..758f17f 100644 --- a/src/main/java/chess/domain/board/File.java +++ b/src/main/java/chess/domain/board/File.java @@ -3,6 +3,7 @@ import java.util.Arrays; public enum File { + a(1), b(2), c(3), diff --git a/src/main/java/chess/domain/player/Position.java b/src/main/java/chess/domain/board/Position.java similarity index 69% rename from src/main/java/chess/domain/player/Position.java rename to src/main/java/chess/domain/board/Position.java index 44cd1fd..e6bc6d4 100644 --- a/src/main/java/chess/domain/player/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -1,7 +1,6 @@ -package chess.domain.player; +package chess.domain.board; -import chess.domain.board.File; -import chess.domain.board.Rank; +import chess.domain.piece.type.MoveUnit; import java.util.*; @@ -58,12 +57,12 @@ public int calculateRankGap(final Position position) { return rank.calculateGap(position.getRank()); } - public Set findPassingPositions(Position target, MoveCoordinate moveCoordinate) { + public Set findPassingPositions(Position target, MoveUnit moveUnit) { Set positions = new HashSet<>(); Position current = this; while (!target.equals(current)) { - current = current.move(moveCoordinate); + current = current.move(moveUnit); positions.add(current); } @@ -71,39 +70,39 @@ public Set findPassingPositions(Position target, MoveCoordinate moveCo return positions; } - public Collection findAvailablePositions(final MoveCoordinate moveCoordinate, final boolean isFinite) { + public Collection findAvailablePositions(final MoveUnit moveUnit, final boolean isFinite) { if (isFinite) { - return getFinitePositions(moveCoordinate); + return getFinitePositions(moveUnit); } - return getInfinitePositions(moveCoordinate); + return getInfinitePositions(moveUnit); } - private Collection getFinitePositions(MoveCoordinate moveCoordinate) { - if (isMovable(moveCoordinate)) { - return Collections.singleton(move(moveCoordinate)); + private Collection getFinitePositions(MoveUnit moveUnit) { + if (isMovable(moveUnit)) { + return Collections.singleton(move(moveUnit)); } return Collections.emptySet(); } - private Collection getInfinitePositions(MoveCoordinate moveCoordinate) { + private Collection getInfinitePositions(MoveUnit moveUnit) { Collection positions = new HashSet<>(); Position current = this; - while (current.isMovable(moveCoordinate)) { - current = current.move(moveCoordinate); + while (current.isMovable(moveUnit)) { + current = current.move(moveUnit); positions.add(current); } return positions; } - private boolean isMovable(final MoveCoordinate moveCoordinate) { - return rank.canMove(moveCoordinate.getRank()) && file.canMove(moveCoordinate.getFile()); + private boolean isMovable(final MoveUnit moveUnit) { + return rank.canMove(moveUnit.getRank()) && file.canMove(moveUnit.getFile()); } - private Position move(final MoveCoordinate moveCoordinate) { - File file = this.file.add(moveCoordinate.getFile()); - Rank rank = this.rank.add(moveCoordinate.getRank()); + private Position move(final MoveUnit moveUnit) { + File file = this.file.add(moveUnit.getFile()); + Rank rank = this.rank.add(moveUnit.getRank()); return Position.from(file, rank); } diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java index 549e78e..daecfbc 100644 --- a/src/main/java/chess/domain/board/Rank.java +++ b/src/main/java/chess/domain/board/Rank.java @@ -3,6 +3,7 @@ import java.util.Arrays; public enum Rank { + R8(8), R7(7), R6(6), diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java index cb78afd..7a1cad1 100644 --- a/src/main/java/chess/domain/command/MoveParameters.java +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -1,6 +1,6 @@ package chess.domain.command; -import chess.domain.player.Position; +import chess.domain.board.Position; import java.util.List; diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/piece/Color.java index c83450c..f38342f 100644 --- a/src/main/java/chess/domain/piece/Color.java +++ b/src/main/java/chess/domain/piece/Color.java @@ -1,10 +1,18 @@ package chess.domain.piece; public enum Color { + WHITE, BLACK; public boolean isWhite() { return this == WHITE; } + + public Color nextTurn() { + if (isWhite()) { + return BLACK; + } + return WHITE; + } } diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java index 0d23f38..1bc8214 100644 --- a/src/main/java/chess/domain/piece/Pawn.java +++ b/src/main/java/chess/domain/piece/Pawn.java @@ -1,15 +1,15 @@ package chess.domain.piece; +import chess.domain.board.Position; import chess.domain.board.Rank; import chess.domain.piece.type.MovePattern; -import chess.domain.player.MoveCoordinate; -import chess.domain.player.Position; +import chess.domain.piece.type.MoveUnit; import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; -import static chess.domain.player.MoveCoordinate.*; +import static chess.domain.piece.type.MoveUnit.*; public class Pawn extends Piece { @@ -25,48 +25,48 @@ public Set findPath(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); - MoveCoordinate moveCoordinate = movePattern.findMoveCoordinate(fileGap, rankGap); - moveCoordinate = resetIfInitialMove(moveCoordinate, isInitialMove(source)); + MoveUnit moveUnit = movePattern.findMoveUnit(fileGap, rankGap); + moveUnit = resetIfInitialMove(moveUnit, isInitialMove(source)); - return source.findPassingPositions(target, moveCoordinate); + return source.findPassingPositions(target, moveUnit); } private boolean isInitialMove(Position source) { return source.hasSameRank(WHITE_INITIAL_RANK) || source.hasSameRank(BLACK_INITIAL_RANK); } - private MoveCoordinate resetIfInitialMove(MoveCoordinate moveCoordinate, boolean isInitialMove) { - validateMoveCount(moveCoordinate, isInitialMove); + private MoveUnit resetIfInitialMove(MoveUnit moveUnit, boolean isInitialMove) { + validateMoveCount(moveUnit, isInitialMove); - if (isWhiteInitialMove(moveCoordinate)) { + if (isWhiteInitialMove(moveUnit)) { return NORTH; } - if (isBlackInitialMove(moveCoordinate)) { + if (isBlackInitialMove(moveUnit)) { return SOUTH; } - return moveCoordinate; + return moveUnit; } - private void validateMoveCount(MoveCoordinate moveCoordinate, boolean isInitialMove) { - if (!isInitialMove && (isBlackInitialMove(moveCoordinate) || isWhiteInitialMove(moveCoordinate))) { + private void validateMoveCount(MoveUnit moveUnit, boolean isInitialMove) { + if (!isInitialMove && (isBlackInitialMove(moveUnit) || isWhiteInitialMove(moveUnit))) { throw new IllegalArgumentException("최초 이동 시에만 2칸 이동할 수 있습니다."); } } - private boolean isWhiteInitialMove(final MoveCoordinate moveCoordinate) { - return moveCoordinate == WHITE_PAWN_INITIAL_NORTH; + private boolean isWhiteInitialMove(final MoveUnit moveUnit) { + return moveUnit == WHITE_PAWN_INITIAL_NORTH; } - private boolean isBlackInitialMove(final MoveCoordinate moveCoordinate) { - return moveCoordinate == BLACK_PAWN_INITIAL_SOUTH; + private boolean isBlackInitialMove(final MoveUnit moveUnit) { + return moveUnit == BLACK_PAWN_INITIAL_SOUTH; } @Override public Collection findAvailableAttackPositions(final Position position) { - return movePattern.pawnAttackMoveCoordinates(isWhite()).stream() - .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, true)) + return movePattern.pawnAttackMoveUnits(isWhite()).stream() + .map(moveUnit -> position.findAvailablePositions(moveUnit, true)) .flatMap(Collection::stream) .collect(Collectors.toSet()); } diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java index 1f72110..7d63bbc 100644 --- a/src/main/java/chess/domain/piece/Piece.java +++ b/src/main/java/chess/domain/piece/Piece.java @@ -1,8 +1,8 @@ package chess.domain.piece; +import chess.domain.board.Position; import chess.domain.piece.type.MovePattern; -import chess.domain.player.MoveCoordinate; -import chess.domain.player.Position; +import chess.domain.piece.type.MoveUnit; import java.util.Collection; import java.util.HashSet; @@ -27,18 +27,18 @@ public Set findPath(final Position source, final Position target) { int fileGap = target.calculateFileGap(source); int rankGap = target.calculateRankGap(source); - MoveCoordinate moveCoordinate = movePattern.findMoveCoordinate(fileGap, rankGap); - return source.findPassingPositions(target, moveCoordinate); + MoveUnit moveUnit = movePattern.findMoveUnit(fileGap, rankGap); + return source.findPassingPositions(target, moveUnit); } public Collection findAvailableAttackPositions(final Position position) { - Set finitePositions = movePattern.finiteMoveCoordinates().stream() - .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, true)) + Set finitePositions = movePattern.finiteMoveUnits().stream() + .map(moveUnit -> position.findAvailablePositions(moveUnit, true)) .flatMap(Collection::stream) .collect(Collectors.toSet()); - Set infinitePositions = movePattern.infiniteMoveCoordinates().stream() - .map(moveCoordinate -> position.findAvailablePositions(moveCoordinate, false)) + Set infinitePositions = movePattern.infiniteMoveUnits().stream() + .map(moveUnit -> position.findAvailablePositions(moveUnit, false)) .flatMap(Collection::stream) .collect(Collectors.toSet()); diff --git a/src/main/java/chess/domain/piece/PieceFactory.java b/src/main/java/chess/domain/piece/PieceFactory.java index 4ee6f22..8fe131a 100644 --- a/src/main/java/chess/domain/piece/PieceFactory.java +++ b/src/main/java/chess/domain/piece/PieceFactory.java @@ -2,7 +2,7 @@ import chess.domain.board.File; import chess.domain.board.Rank; -import chess.domain.player.Position; +import chess.domain.board.Position; import java.util.Arrays; import java.util.HashMap; diff --git a/src/main/java/chess/domain/piece/type/MovePattern.java b/src/main/java/chess/domain/piece/type/MovePattern.java index 4f50bfb..c7810f5 100644 --- a/src/main/java/chess/domain/piece/type/MovePattern.java +++ b/src/main/java/chess/domain/piece/type/MovePattern.java @@ -1,92 +1,91 @@ package chess.domain.piece.type; import chess.domain.piece.Color; -import chess.domain.player.MoveCoordinate; import java.util.*; -import static chess.domain.player.MoveCoordinate.*; +import static chess.domain.piece.type.MoveUnit.*; public class MovePattern { - private static final Collection CARDINAL_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection CARDINAL_UNITS = Collections.unmodifiableList(Arrays.asList( NORTH, SOUTH, WEST, EAST )); - private static final Collection DIAGONAL_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection DIAGONAL_UNITS = Collections.unmodifiableList(Arrays.asList( NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST )); - private static final Collection WHITE_PAWN_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection WHITE_PAWN_UNITS = Collections.unmodifiableList(Arrays.asList( WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH )); - private static final Collection WHITE_PAWN_ATTACK_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection WHITE_PAWN_ATTACK_UNITS = Collections.unmodifiableList(Arrays.asList( NORTH_EAST, NORTH_WEST )); - private static final Collection BLACK_PAWN_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection BLACK_PAWN_UNITS = Collections.unmodifiableList(Arrays.asList( BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH )); - private static final Collection BLACK_PAWN_ATTACK_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection BLACK_PAWN_ATTACK_UNITS = Collections.unmodifiableList(Arrays.asList( SOUTH_EAST, SOUTH_WEST )); - private static final Collection KNIGHT_COORDINATES = Collections.unmodifiableList(Arrays.asList( + private static final Collection KNIGHT_UNITS = Collections.unmodifiableList(Arrays.asList( NORTH_EAST_LEFT, NORTH_EAST_RIGHT, NORTH_WEST_LEFT, NORTH_WEST_RIGHT, SOUTH_EAST_LEFT, SOUTH_EAST_RIGHT, SOUTH_WEST_LEFT, SOUTH_WEST_RIGHT )); - private final Collection infiniteMoveCoordinates; - private final Collection finiteMoveCoordinates; + private final Collection infiniteMoveUnits; + private final Collection finiteMoveUnits; - private MovePattern(final Collection infiniteMoveCoordinates, final Collection finiteMoveCoordinates) { - this.infiniteMoveCoordinates = Collections.unmodifiableCollection(infiniteMoveCoordinates); - this.finiteMoveCoordinates = Collections.unmodifiableCollection(finiteMoveCoordinates); + private MovePattern(final Collection infiniteMoveUnits, final Collection finiteMoveUnits) { + this.infiniteMoveUnits = Collections.unmodifiableCollection(infiniteMoveUnits); + this.finiteMoveUnits = Collections.unmodifiableCollection(finiteMoveUnits); } public static MovePattern queenPattern() { - List infiniteMoveCoordinates = new ArrayList<>(CARDINAL_COORDINATES); - infiniteMoveCoordinates.addAll(DIAGONAL_COORDINATES); - return new MovePattern(infiniteMoveCoordinates, Collections.emptyList()); + List infiniteMoveUnits = new ArrayList<>(CARDINAL_UNITS); + infiniteMoveUnits.addAll(DIAGONAL_UNITS); + return new MovePattern(infiniteMoveUnits, Collections.emptyList()); } public static MovePattern kingPattern() { - List finiteMoveCoordinates = new ArrayList<>(CARDINAL_COORDINATES); - finiteMoveCoordinates.addAll(DIAGONAL_COORDINATES); - return new MovePattern(Collections.emptyList(), finiteMoveCoordinates); + List finiteMoveUnits = new ArrayList<>(CARDINAL_UNITS); + finiteMoveUnits.addAll(DIAGONAL_UNITS); + return new MovePattern(Collections.emptyList(), finiteMoveUnits); } public static MovePattern knightPattern() { - return new MovePattern(Collections.emptyList(), KNIGHT_COORDINATES); + return new MovePattern(Collections.emptyList(), KNIGHT_UNITS); } public static MovePattern rookPattern() { - return new MovePattern(CARDINAL_COORDINATES, Collections.emptyList()); + return new MovePattern(CARDINAL_UNITS, Collections.emptyList()); } public static MovePattern bishopPattern() { - return new MovePattern(DIAGONAL_COORDINATES, Collections.emptyList()); + return new MovePattern(DIAGONAL_UNITS, Collections.emptyList()); } public static MovePattern pawnPattern(final Color color) { if (color.isWhite()) { - return new MovePattern(Collections.emptyList(), WHITE_PAWN_COORDINATES); + return new MovePattern(Collections.emptyList(), WHITE_PAWN_UNITS); } - return new MovePattern(Collections.emptyList(), BLACK_PAWN_COORDINATES); + return new MovePattern(Collections.emptyList(), BLACK_PAWN_UNITS); } - public MoveCoordinate findMoveCoordinate(int fileGap, int rankGap) { - List result = new ArrayList<>(); + public MoveUnit findMoveUnit(int fileGap, int rankGap) { + List result = new ArrayList<>(); - infiniteMoveCoordinates.stream() - .filter(moveCoordinate -> moveCoordinate.matches(fileGap, rankGap, false)) + infiniteMoveUnits.stream() + .filter(moveUnit -> moveUnit.matches(fileGap, rankGap, false)) .findAny() .ifPresent(result::add); - finiteMoveCoordinates.stream() - .filter(moveCoordinate -> moveCoordinate.matches(fileGap, rankGap, true)) + finiteMoveUnits.stream() + .filter(moveUnit -> moveUnit.matches(fileGap, rankGap, true)) .findAny() .ifPresent(result::add); @@ -97,18 +96,18 @@ public MoveCoordinate findMoveCoordinate(int fileGap, int rankGap) { return result.get(0); } - public Collection finiteMoveCoordinates() { - return finiteMoveCoordinates; + public Collection finiteMoveUnits() { + return finiteMoveUnits; } - public Collection infiniteMoveCoordinates() { - return infiniteMoveCoordinates; + public Collection infiniteMoveUnits() { + return infiniteMoveUnits; } - public Collection pawnAttackMoveCoordinates(boolean isWhite) { + public Collection pawnAttackMoveUnits(boolean isWhite) { if (isWhite) { - return WHITE_PAWN_ATTACK_COORDINATES; + return WHITE_PAWN_ATTACK_UNITS; } - return BLACK_PAWN_ATTACK_COORDINATES; + return BLACK_PAWN_ATTACK_UNITS; } } diff --git a/src/main/java/chess/domain/player/MoveCoordinate.java b/src/main/java/chess/domain/piece/type/MoveUnit.java similarity index 94% rename from src/main/java/chess/domain/player/MoveCoordinate.java rename to src/main/java/chess/domain/piece/type/MoveUnit.java index 59545f0..04106fd 100644 --- a/src/main/java/chess/domain/player/MoveCoordinate.java +++ b/src/main/java/chess/domain/piece/type/MoveUnit.java @@ -1,6 +1,7 @@ -package chess.domain.player; +package chess.domain.piece.type; + +public enum MoveUnit { -public enum MoveCoordinate { NORTH_EAST(1, 1), SOUTH_EAST(1, -1), NORTH_WEST(-1, 1), @@ -25,7 +26,7 @@ public enum MoveCoordinate { private final int file; private final int rank; - MoveCoordinate(final int file, final int rank) { + MoveUnit(final int file, final int rank) { this.file = file; this.rank = rank; } diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java index 8a1f893..5658acd 100644 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ b/src/main/java/chess/domain/player/AttackPositions.java @@ -1,5 +1,6 @@ package chess.domain.player; +import chess.domain.board.Position; import chess.domain.piece.Piece; import java.util.Collection; diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index d82e38a..9b4a184 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -1,6 +1,7 @@ package chess.domain.player; import chess.domain.board.File; +import chess.domain.board.Position; import chess.domain.piece.Color; import chess.domain.piece.Piece; import chess.domain.piece.PieceFactory; @@ -23,12 +24,16 @@ public boolean hasPieceOn(final Position position) { return pieces.containsKey(position); } + public boolean hasNoPieceOn(final Position position) { + return !hasPieceOn(position); + } + public Set findPaths(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); return sourcePiece.findPath(source, target); } - public void update(final Position source, final Position target) { + public void move(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); movePiece(source, target, sourcePiece); attackPositions.update(source, target, sourcePiece); @@ -40,14 +45,14 @@ private void movePiece(final Position source, final Position target, final Piece } public Piece findPieceBy(final Position position) { - if (!hasPieceOn(position)) { + if (hasNoPieceOn(position)) { throw new IllegalArgumentException("해당 위치에 기물이 존재하지 않습니다."); } return pieces.get(position); } - public boolean hasKingOn(Position position) { + public boolean hasKingOn(final Position position) { return PieceType.isKing(findPieceBy(position)); } @@ -61,17 +66,17 @@ public boolean isKingAlive() { .anyMatch(PieceType::isKing); } - public boolean canAttack(Position position) { + public boolean canAttack(final Position position) { return attackPositions.contains(position); } - public void attacked(final Position target) { - if (!hasPieceOn(target)) { + public void isUnderAttack(final Position position) { + if (hasNoPieceOn(position)) { return; } - attackPositions.remove(target, pieces.get(target)); - pieces.remove(target); + attackPositions.remove(position, pieces.get(position)); + pieces.remove(position); } public double calculateScores() { diff --git a/src/main/java/chess/domain/board/Scores.java b/src/main/java/chess/domain/player/Scores.java similarity index 92% rename from src/main/java/chess/domain/board/Scores.java rename to src/main/java/chess/domain/player/Scores.java index 68e09e4..91d1a4a 100644 --- a/src/main/java/chess/domain/board/Scores.java +++ b/src/main/java/chess/domain/player/Scores.java @@ -1,6 +1,7 @@ -package chess.domain.board; +package chess.domain.player; public class Scores { + private final double whiteScore; private final double blackScore; diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index 9f43605..1cb8533 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -3,7 +3,7 @@ import chess.domain.board.Board; import chess.domain.piece.Piece; import chess.domain.piece.type.PieceType; -import chess.domain.player.Position; +import chess.domain.board.Position; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 7641402..1b7ffe9 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,7 +1,7 @@ package chess.service; import chess.domain.board.Board; -import chess.domain.board.Scores; +import chess.domain.player.Scores; import chess.domain.command.Command; import chess.domain.command.MoveParameters; import chess.domain.piece.Color; @@ -11,17 +11,12 @@ import java.util.Map; -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; - public class ChessService { private final Board board; - private Color currentTurn; public ChessService() { this.board = new Board(); - this.currentTurn = WHITE; } public void run(Command command) { @@ -48,19 +43,7 @@ public boolean isGameRunning() { } public void movePiece(MoveParameters parameters) { - board.move(parameters, currentTurn); - changeTurn(); - } - - private void changeTurn() { - if (currentTurn == WHITE) { - currentTurn = BLACK; - return; - } - - if (currentTurn == BLACK) { - currentTurn = WHITE; - } + board.move(parameters); } public Scores getScores() { @@ -72,14 +55,10 @@ public Map getBoardDto() { } public String getCurrentTurnDto() { - return currentTurn.name(); + return board.getCurrentTurn().name(); } public String getWinnerDto() { - if (board.isBothKingAlive()) { - throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); - } - Color color = board.getWinner(); return color.name(); } diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index e5b6214..13597a4 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.domain.board.Scores; +import chess.domain.player.Scores; import java.util.Map; diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index bc39768..50eeef5 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.domain.board.Scores; +import chess.domain.player.Scores; import java.util.Map; diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index 321dffd..bd9e336 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,15 +1,12 @@ package chess.domain.board; import chess.domain.command.MoveParameters; -import chess.domain.piece.Color; -import chess.domain.player.Position; +import chess.domain.player.Scores; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -30,73 +27,14 @@ void create() { assertThat(board.isEmpty(emptyPosition)).isTrue(); } - @Test - @DisplayName("시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") - void move_source_position_empty() { - //given - Board board = new Board(); - Position source = Position.of("b3"); - Position target = Position.of("b4"); - MoveParameters moveParameters = new MoveParameters(source, target); - - //when, then - assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, WHITE)) - .withMessage("해당 위치에 기물이 존재하지 않습니다."); - } - - @ParameterizedTest - @CsvSource({"b2, b3, BLACK", "a7, a6, WHITE"}) - @DisplayName("자신의 기물이 아닌 기물을 선택할 경우 예외가 발생한다.") - void move_source_not_owner(String sourcePosition, String targetPosition, Color currentTurn) { - //given - Board board = new Board(); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); - MoveParameters moveParameters = new MoveParameters(source, target); - - //when, then - assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, currentTurn)) - .withMessage("자신의 기물만 움직일 수 있습니다."); - } - - @ParameterizedTest - @CsvSource({"a1, a2, WHITE", "a8, a7, BLACK"}) - @DisplayName("시작과 도착 위치의 기물이 같은 색상일 경우 예외가 발생한다.") - void move_source_and_target_same_color(String sourcePosition, String targetPosition, Color currentTurn) { - //given - Board board = new Board(); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); - MoveParameters moveParameters = new MoveParameters(source, target); - - //when, then - assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, currentTurn)) - .withMessage("같은 색상의 기물은 공격할 수 없습니다."); - } - - @ParameterizedTest - @CsvSource({"a1, a1, WHITE", "a8, a8, BLACK"}) - @DisplayName("시작과 도착 위치가 같을 경우 예외가 발생한다.") - void move_source_and_target_same(String sourcePosition, String targetPosition, Color currentTurn) { - //given - Board board = new Board(); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); - MoveParameters moveParameters = new MoveParameters(source, target); - - //when, then - assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, currentTurn)) - .withMessage("출발 위치와 도착 위치가 같을 수 없습니다."); - } - @ParameterizedTest - @CsvSource({"a1, a3, WHITE", "a8, a6, BLACK"}) - @DisplayName("경로에 다른 기물이 존재하는 경우 예외가 발생한다.") - void move_invalid_paths(String sourcePosition, String targetPosition, Color currentTurn) { + @CsvSource({"b3, b4, 자신의 기물만 움직일 수 있습니다.", // 빈칸 + "a7, a6, 자신의 기물만 움직일 수 있습니다.", // 적 기물 + "a1, a2, 자신의 기물이 있는 곳으로 이동할 수 없습니다.", + "a1, a1, 출발 위치와 도착 위치가 같을 수 없습니다.", + "a1, a3, 다른 기물을 통과하여 이동할 수 없습니다."}) + @DisplayName("이동할 수 없는 경우 예외가 발생한다.") + void move_exception(String sourcePosition, String targetPosition, String exceptionMessage) { //given Board board = new Board(); Position source = Position.of(sourcePosition); @@ -105,8 +43,8 @@ void move_invalid_paths(String sourcePosition, String targetPosition, Color curr //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, currentTurn)) - .withMessage("기물을 통과하여 이동할 수 없습니다."); + .isThrownBy(() -> board.move(moveParameters)) + .withMessage(exceptionMessage); } @ParameterizedTest @@ -119,7 +57,7 @@ void move_king_invalid_target(String source, String target) { //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters, WHITE)) + .isThrownBy(() -> board.move(moveParameters)) .withMessage("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } @@ -133,26 +71,31 @@ void get_status() { Scores scores = board.getScores(); //then - assertThat(scores.getWhiteScore()).isEqualTo(37); + assertThat(scores.getWhiteScore()).isEqualTo(36); assertThat(scores.getBlackScore()).isEqualTo(37); } private Board setBoardToGetStatus() { Board board = new Board(); - board.move(new MoveParameters(Position.of("e7"), Position.of("e5")), BLACK); - board.move(new MoveParameters(Position.of("e5"), Position.of("e4")), BLACK); - board.move(new MoveParameters(Position.of("e4"), Position.of("e3")), BLACK); - board.move(new MoveParameters(Position.of("d2"), Position.of("e3")), WHITE); + board.move(new MoveParameters(Position.of("a2"), Position.of("a3"))); + board.move(new MoveParameters(Position.of("e7"), Position.of("e5"))); + board.move(new MoveParameters(Position.of("d2"), Position.of("e3"))); + board.move(new MoveParameters(Position.of("e5"), Position.of("e4"))); + board.move(new MoveParameters(Position.of("b2"), Position.of("b3"))); + board.move(new MoveParameters(Position.of("a7"), Position.of("a6"))); + board.move(new MoveParameters(Position.of("f2"), Position.of("f3"))); + board.move(new MoveParameters(Position.of("e4"), Position.of("f3"))); return board; } private Board setBoardToAttackKing() { Board board = new Board(); - board.move(new MoveParameters(Position.of("e2"), Position.of("e4")), WHITE); - board.move(new MoveParameters(Position.of("d2"), Position.of("d4")), WHITE); - board.move(new MoveParameters(Position.of("e1"), Position.of("e2")), WHITE); - board.move(new MoveParameters(Position.of("c7"), Position.of("c5")), BLACK); - board.move(new MoveParameters(Position.of("d8"), Position.of("a5")), BLACK); + board.move(new MoveParameters(Position.of("e2"), Position.of("e4"))); + board.move(new MoveParameters(Position.of("c7"), Position.of("c5"))); + board.move(new MoveParameters(Position.of("d2"), Position.of("d4"))); + board.move(new MoveParameters(Position.of("d8"), Position.of("a5"))); + board.move(new MoveParameters(Position.of("e1"), Position.of("e2"))); + board.move(new MoveParameters(Position.of("h7"), Position.of("h5"))); return board; } } diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/BishopTest.java index 64eb8b5..d29fedf 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/BishopTest.java @@ -2,7 +2,7 @@ import chess.domain.board.File; import chess.domain.board.Rank; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/KingTest.java index 2dc762e..240d103 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/KingTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/KnightTest.java index 2629bcc..1f26c41 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/KnightTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/PawnTest.java index 3d702ed..13e79a6 100644 --- a/src/test/java/chess/domain/piece/PawnTest.java +++ b/src/test/java/chess/domain/piece/PawnTest.java @@ -1,6 +1,6 @@ package chess.domain.piece; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/QueenTest.java index f9abd8e..774517f 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/QueenTest.java @@ -2,7 +2,7 @@ import chess.domain.board.File; import chess.domain.board.Rank; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java index 4331349..0b3e482 100644 --- a/src/test/java/chess/domain/piece/RookTest.java +++ b/src/test/java/chess/domain/piece/RookTest.java @@ -2,7 +2,7 @@ import chess.domain.board.File; import chess.domain.board.Rank; -import chess.domain.player.Position; +import chess.domain.board.Position; import org.assertj.core.groups.Tuple; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/piece/type/MovePatternTest.java b/src/test/java/chess/domain/piece/type/MovePatternTest.java index a45bb0e..20809be 100644 --- a/src/test/java/chess/domain/piece/type/MovePatternTest.java +++ b/src/test/java/chess/domain/piece/type/MovePatternTest.java @@ -1,13 +1,12 @@ package chess.domain.piece.type; import chess.domain.piece.Color; -import chess.domain.player.MoveCoordinate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Collection; -import static chess.domain.player.MoveCoordinate.*; +import static chess.domain.piece.type.MoveUnit.*; import static org.assertj.core.api.Assertions.assertThat; class MovePatternTest { @@ -20,10 +19,10 @@ void pawn_pattern_black() { // when MovePattern movePattern = MovePattern.pawnPattern(color); - Collection moveCoordinates = movePattern.finiteMoveCoordinates(); + Collection moveUnits = movePattern.finiteMoveUnits(); // then - assertThat(moveCoordinates) + assertThat(moveUnits) .containsExactlyInAnyOrder(BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH); } @@ -35,10 +34,10 @@ void pawn_pattern_white() { // when MovePattern movePattern = MovePattern.pawnPattern(color); - Collection moveCoordinates = movePattern.finiteMoveCoordinates(); + Collection moveUnits = movePattern.finiteMoveUnits(); // then - assertThat(moveCoordinates) + assertThat(moveUnits) .containsExactlyInAnyOrder(WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH); } } diff --git a/src/test/java/chess/domain/player/AttackPositionsTest.java b/src/test/java/chess/domain/player/AttackPositionsTest.java index bc9dc11..84ad239 100644 --- a/src/test/java/chess/domain/player/AttackPositionsTest.java +++ b/src/test/java/chess/domain/player/AttackPositionsTest.java @@ -1,5 +1,6 @@ package chess.domain.player; +import chess.domain.board.Position; import chess.domain.piece.Color; import chess.domain.piece.Knight; import chess.domain.piece.Piece; diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index 384d883..f900a6a 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -1,5 +1,6 @@ package chess.domain.player; +import chess.domain.board.Position; import chess.domain.piece.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -39,7 +40,7 @@ void update_source_position_empty(Color color, String sourcePosition, String tar //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> player.update(source, target)) + .isThrownBy(() -> player.move(source, target)) .withMessage("해당 위치에 기물이 존재하지 않습니다."); } @@ -53,7 +54,7 @@ void update_board(Color color, String sourcePosition, String targetPosition) { Position target = Position.of(targetPosition); //when - player.update(source, target); + player.move(source, target); //then assertThat(player.hasPieceOn(source)).isFalse(); @@ -127,7 +128,7 @@ void sum_scores() { void is_king_dead() { //given Player player = new Player(Color.WHITE); - player.attacked(Position.of("e1")); + player.isUnderAttack(Position.of("e1")); //when boolean kingDead = player.isKingDead(); diff --git a/src/test/java/chess/domain/player/PositionTest.java b/src/test/java/chess/domain/player/PositionTest.java index 8c83bbc..8138862 100644 --- a/src/test/java/chess/domain/player/PositionTest.java +++ b/src/test/java/chess/domain/player/PositionTest.java @@ -1,12 +1,15 @@ package chess.domain.player; import chess.domain.board.File; +import chess.domain.board.Position; import chess.domain.board.Rank; +import chess.domain.piece.type.MoveUnit; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import java.util.Arrays; import java.util.Collection; @@ -15,6 +18,7 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; class PositionTest { @@ -35,7 +39,7 @@ void from_file_and_rank() { @Test @DisplayName("문자열 키에 해당하는 위치를 반환한다.") - void from_key() { + void of_valid_key() { //given File file = File.a; Rank rank = Rank.R1; @@ -49,16 +53,27 @@ void from_key() { .containsOnly(file, rank); } + @ParameterizedTest + @ValueSource(strings = {"a0", "j1"}) + @DisplayName("유효하지 않은 키로 검색하면 예외를 던진다.") + void of_invalid_key(String key) { + //given, when, then + assertThrows(IllegalArgumentException.class, () -> { + Position.of(key); + }); + } + + @Test @DisplayName("거쳐가는 모든 위치를 반환한다.") void find_passing_positions() { // given Position source = Position.of("d4"); Position target = Position.of("d7"); - MoveCoordinate moveCoordinate = MoveCoordinate.NORTH; + MoveUnit moveUnit = MoveUnit.NORTH; // when - Set positions = source.findPassingPositions(target, moveCoordinate); + Set positions = source.findPassingPositions(target, moveUnit); // then assertThat(positions) @@ -72,10 +87,10 @@ void find_passing_positions() { void find_available_positions(boolean isFinite, Collection expected) { // given Position position = Position.of("d4"); - MoveCoordinate moveCoordinate = MoveCoordinate.EAST; + MoveUnit moveUnit = MoveUnit.EAST; // when - Collection positions = position.findAvailablePositions(moveCoordinate, isFinite); + Collection positions = position.findAvailablePositions(moveUnit, isFinite); // then assertThat(positions) From cd61bd78f2f3e6e465108c53fb66563fa5ec2baf Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Mon, 6 Sep 2021 05:16:26 +0900 Subject: [PATCH 32/38] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 191 ++++++++++-------- .../controller/ConsoleChessController.java | 3 +- src/main/java/chess/domain/board/Board.java | 80 ++++---- .../java/chess/domain/board/Position.java | 78 +++---- src/main/java/chess/domain/board/Rank.java | 11 + .../chess/domain/command/MoveParameters.java | 6 +- src/main/java/chess/domain/piece/Bishop.java | 10 - src/main/java/chess/domain/piece/King.java | 10 - src/main/java/chess/domain/piece/Knight.java | 10 - src/main/java/chess/domain/piece/Pawn.java | 73 ------- src/main/java/chess/domain/piece/Piece.java | 50 ----- src/main/java/chess/domain/piece/Queen.java | 10 - src/main/java/chess/domain/piece/Rook.java | 10 - .../java/chess/domain/piece/move/Gap.java | 20 ++ .../chess/domain/piece/move/MoveLimit.java | 17 ++ .../chess/domain/piece/move/MovePattern.java | 56 +++++ .../chess/domain/piece/move/MoveUnit.java | 85 ++++++++ .../chess/domain/piece/move/MoveUnits.java | 63 ++++++ .../java/chess/domain/piece/move/Path.java | 67 ++++++ .../java/chess/domain/piece/type/Bishop.java | 11 + .../java/chess/domain/piece/type/King.java | 11 + .../java/chess/domain/piece/type/Knight.java | 11 + .../chess/domain/piece/type/MovePattern.java | 113 ----------- .../chess/domain/piece/type/MoveUnit.java | 73 ------- .../java/chess/domain/piece/type/Pawn.java | 75 +++++++ .../java/chess/domain/piece/type/Piece.java | 51 +++++ .../domain/piece/{ => type}/PieceFactory.java | 13 +- .../chess/domain/piece/type/PieceType.java | 21 +- .../java/chess/domain/piece/type/Queen.java | 11 + .../java/chess/domain/piece/type/Rook.java | 11 + .../chess/domain/player/AttackPositions.java | 57 ------ .../chess/domain/{piece => player}/Color.java | 8 +- src/main/java/chess/domain/player/Player.java | 115 ++++++----- src/main/java/chess/dto/BoardDto.java | 22 +- .../exception/EmptyPositionException.java | 4 + src/main/java/chess/service/ChessService.java | 8 +- .../java/chess/domain/board/BoardTest.java | 150 +++++++++----- .../java/chess/domain/board/PositionTest.java | 146 +++++++++++++ .../java/chess/domain/piece/PieceTest.java | 21 -- .../java/chess/domain/piece/RookTest.java | 82 -------- .../domain/piece/move/MoveUnitsTest.java | 38 ++++ .../chess/domain/piece/move/PathTest.java | 43 ++++ .../domain/piece/{ => type}/BishopTest.java | 48 ++--- .../domain/piece/{ => type}/KingTest.java | 27 +-- .../domain/piece/{ => type}/KnightTest.java | 21 +- .../domain/piece/type/MovePatternTest.java | 43 ---- .../domain/piece/{ => type}/PawnTest.java | 41 ++-- .../chess/domain/piece/type/PieceTest.java | 55 +++++ .../domain/piece/type/PieceTypeTest.java | 2 +- .../domain/piece/{ => type}/QueenTest.java | 60 +++--- .../chess/domain/piece/type/RookTest.java | 69 +++++++ .../domain/player/AttackPositionsTest.java | 54 ----- .../java/chess/domain/player/ColorTest.java | 35 ++++ .../java/chess/domain/player/PlayerTest.java | 119 ++++++----- .../chess/domain/player/PositionTest.java | 121 ----------- 55 files changed, 1459 insertions(+), 1181 deletions(-) delete mode 100644 src/main/java/chess/domain/piece/Bishop.java delete mode 100644 src/main/java/chess/domain/piece/King.java delete mode 100644 src/main/java/chess/domain/piece/Knight.java delete mode 100644 src/main/java/chess/domain/piece/Pawn.java delete mode 100644 src/main/java/chess/domain/piece/Piece.java delete mode 100644 src/main/java/chess/domain/piece/Queen.java delete mode 100644 src/main/java/chess/domain/piece/Rook.java create mode 100644 src/main/java/chess/domain/piece/move/Gap.java create mode 100644 src/main/java/chess/domain/piece/move/MoveLimit.java create mode 100644 src/main/java/chess/domain/piece/move/MovePattern.java create mode 100644 src/main/java/chess/domain/piece/move/MoveUnit.java create mode 100644 src/main/java/chess/domain/piece/move/MoveUnits.java create mode 100644 src/main/java/chess/domain/piece/move/Path.java create mode 100644 src/main/java/chess/domain/piece/type/Bishop.java create mode 100644 src/main/java/chess/domain/piece/type/King.java create mode 100644 src/main/java/chess/domain/piece/type/Knight.java delete mode 100644 src/main/java/chess/domain/piece/type/MovePattern.java delete mode 100644 src/main/java/chess/domain/piece/type/MoveUnit.java create mode 100644 src/main/java/chess/domain/piece/type/Pawn.java create mode 100644 src/main/java/chess/domain/piece/type/Piece.java rename src/main/java/chess/domain/piece/{ => type}/PieceFactory.java (84%) create mode 100644 src/main/java/chess/domain/piece/type/Queen.java create mode 100644 src/main/java/chess/domain/piece/type/Rook.java delete mode 100644 src/main/java/chess/domain/player/AttackPositions.java rename src/main/java/chess/domain/{piece => player}/Color.java (62%) create mode 100644 src/main/java/chess/exception/EmptyPositionException.java create mode 100644 src/test/java/chess/domain/board/PositionTest.java delete mode 100644 src/test/java/chess/domain/piece/PieceTest.java delete mode 100644 src/test/java/chess/domain/piece/RookTest.java create mode 100644 src/test/java/chess/domain/piece/move/MoveUnitsTest.java create mode 100644 src/test/java/chess/domain/piece/move/PathTest.java rename src/test/java/chess/domain/piece/{ => type}/BishopTest.java (50%) rename src/test/java/chess/domain/piece/{ => type}/KingTest.java (67%) rename src/test/java/chess/domain/piece/{ => type}/KnightTest.java (70%) delete mode 100644 src/test/java/chess/domain/piece/type/MovePatternTest.java rename src/test/java/chess/domain/piece/{ => type}/PawnTest.java (76%) create mode 100644 src/test/java/chess/domain/piece/type/PieceTest.java rename src/test/java/chess/domain/piece/{ => type}/QueenTest.java (51%) create mode 100644 src/test/java/chess/domain/piece/type/RookTest.java delete mode 100644 src/test/java/chess/domain/player/AttackPositionsTest.java create mode 100644 src/test/java/chess/domain/player/ColorTest.java delete mode 100644 src/test/java/chess/domain/player/PositionTest.java diff --git a/README.md b/README.md index 90ce065..515bdf9 100644 --- a/README.md +++ b/README.md @@ -2,101 +2,126 @@ ## 구현해야할 목록 -- [x] 입력 - - [x] 명령어 입력 -- [x] 출력 - - [x] 시작 안내 문구 출력 - - [x] 체스판 전체 출력 - - [x] 점수와 결과 출력 - -- [x] 체스게임(ChessGame) - - [x] 체스판 - - [x] 현재 플레이어 - - [x] 게임 시작&종료 상태 제어 - - - 기능 - - [x] 명령어 검증 및 처리 - - [x] start : 게임 실행 - - [x] end : 게임 종료 - - [x] move : 인자로 전달받은 source 위치에서 target 위치로 기물 이동 - -- [x] 체스판(Board) - - [x] 체스판 - - [x] 생성 시 32개의 기물 초기화 - - - 기능 - - [x] 인자로 전달받은 위치에 기물이 있는지 확인 - - [x] ERROR : 존재하지 않을 경우 - - [x] 같은 색상 기물인지 확인 - - [x] ERROR : 다른 색상일 경우 - - [x] 시작과 도착 위치의 기물이 다른 색상인지 확인 - - [x] ERROR : 같은 색상일 경우 - - [x] source 위치에서 target 위치로 기물 이동 - - [x] ERROR : source 위치에 기물이 없는 경우 - - [x] ERROR : 자신의 기물이 아닌 경우 - - [x] ERROR : source, target 위치의 기물 색상이 같을 경우 - - [x] ERROR : source, target 위치가 같을 경우 - - [x] ERROR : 이동 경로에 기물이 존재할 경우 - -- [x] 기물(Piece) - - [x] 색상 - - - [x] 기물 위치(Position) - - [x] 파일 - - [x] 랭크 - - [x] 64개의 캐싱된 위치 - - - 기능 - - [x] 전달받은 인자에 해당하는 위치 객체 반환 - - - [x] 기물 색상(Color) - - [x] 색상 - - - [x] 기물 이름 매퍼 - - [x] 기물 종류에 따라 이름을 매핑 - -- [x] 플레이어(Player) - - [x] 자신의 기물 - - [x] 자신이 공격 가능한 범위 - - - 기능 - - [x] 기물을 이동시킨다. - - [x] 기물의 이동 경로를 반환한다. - - [x] 입력받은 위치에 기물이 있는지 확인한다. - - [x] 보드 위 자신의 기물의 점수 총 합을 계산한다. +### 입력 + +- [x] 명령어 입력 + - [x] 예외: `null` 입력 + +### 출력 + +- [x] 시작 안내 문구 출력 +- [x] 체스판 스냅샷 출력 +- [x] 차례 출력 +- [x] 점수 출력 +- [x] 승자 출력 + +### 서비스 + +#### `ChessService` + +- [x] `Command`에 따라 기능 수행 +- [x] 게임 현재 상황 DTO + +### 도메인 + +#### `Operation` + +- [x] 명령어 검증 + - [x] start: 게임 실행 + - [x] status: 팀별 점수 + - [x] move: 기물 이동 + - [x] end: 강제 종료 + +#### `Board` + +- [x] 기물 이동 + - [x] 예외 + - [x] 움직일 기물이 플레이어 소유가 아닌 경우 + - [x] `source`와 `target`이 같은 위치인 경우 + - [x] 본인의 기물을 공격하는 경우 + - [x] 이동 경로가 다른 기물에 가로막힌 경우 + - [x] `King`: `target`이 적이 공격 가능한 위치인 경우 + - [x] `Pawn`: `target`에 적이 없는데 대각선으로 이동하는 경우 + - [x] 플레이어 기물 이동 + - [x] 공격인 경우 적 기물 제거 + - [x] 차례 변경 +- [x] 현재 차례 관리 +- [x] 위치 정보 + - [x] 특정 위치가 비었는지 확인 + - [x] 특정 위치에 있는 기물 확인 +- [x] 각 플레이어 점수 계산 +- [x] 승자 확인 + - [x] 예외: 두 `King`이 모두 살아 있는 경우 + +#### `Player` + +- [x] 기물 이동 +- [x] 기물 이동 경로 검색 +- [x] 현위치에서 공격 가능한 모든 경로 검색 +- [x] 적에게 공격받은 경우 기물 제거 +- [x] 기물 검색 +- [x] 점수 계산 + +#### `Piece` + +- [x] 이동 경로 검색 +- [x] 현위치에서 공격 가능한 모든 경로 검색 +- [x] 색상 확인 +- [x] 타입 확인 + - [x] `Pawn` + - [x] `King` + +#### `MovePattern` + +- [x] 각 기물 타입에 맞는 패턴 검색 + - [x] `Pawn`은 색상에 맞는 패턴 검색 +- [x] 본인 패턴 중 `gap`에 맞는 `MoveUnit` 검색 +- [x] 본인 패턴으로 공격 가능한 모든 경로 검색 + +#### `MoveLimit` + +- [x] 한 칸만 이동 가능한지 확인 + +#### `MoveUnits` + +- [x] 본인 리스트에서 매칭되는 `MoveUnit` 검색 + - [x] 예외: 매칭되는 `MoveUnit`이 없는 경우 +- [x] 이동 단위 별로 이동 가능한 모든 경로 검색 + +#### `MoveUnit` + +- [x] `MoveUnit`의 좌표가 `gap`에 매칭되는지 확인 + +#### `Position` + +- [x] 64개 위치 캐싱 및 검색 +- [x] `Gap` 계산 +- [x] 이동 단위로 타겟까지 이동할 때 지나가는 모든 위치 검색 +- [x] 이동 단위로 이동 가능한 체스판의 모든 위치 검색 +- [x] 이동 단위로 한 번 더 이동 가능한지 확인 +- [x] 이동 단위로 이동 ## 이동 규칙 - [x] 폰 (1 or 0.5) - - [x] 적 방향 직선 1칸 이동 - - [x] ERROR : 직선 방향 2칸 이상일 경우 - - [x] ERROR : 좌, 우 이동이 포함될 경우 - - [x] 처음 이동 시에는 2칸 이동 가능 - - [x] ERROR : 직선 방향 3칸 이상일 경우 - - [x] ERROR : 좌, 우 이동이 포함될 경우 - - - [x] 공격 : 적 방향 좌, 우 대각선 1칸 - - [x] ERROR : 직선 1칸 && 좌 또는 우 1칸이 아닐 경우 + - [x] 적 방향 직선 1칸 이동 + - [x] 처음 이동 시에는 2칸 이동 가능 + - [x] 공격 시에는 적 방향 좌, 우 대각선 1칸 이동 - [x] 룩 (5) - - [x] 모든 직선 방향으로 원하는 만큼 이동 가능 - - [x] ERROR : 룩 이동 패턴으로 이동할 수 없는 위치일 경우 + - [x] 모든 직선 방향으로 원하는 만큼 이동 - [x] 나이트 (2.5) - - [x] 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 가능 - - [x] ERROR : 나이트 이동 패턴으로 이동할 수 없는 위치일 경우 - - [x] 진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있다. + - [x] 모든 직선 방향 1칸 + 이동한 직선 방향의 좌, 우 대각선 1칸으로 이동 + - [x] 진행 방향이 가로막혀도 적, 아군 상관없이 뛰어넘을 수 있음 - [x] 비숍 (3) - - [x] 모든 대각선 방향으로 원하는 만큼 이동 가능 - - [x] ERROR : 비숍 이동 패턴으로 이동할 수 없는 위치일 경우 + - [x] 모든 대각선 방향으로 원하는 만큼 이동 - [x] 퀸 (9) - - [x] 모든 방향 1칸 + α 이동 (모든 대각선 방향으로는 원하는 만큼 이동 가능) - - [x] ERROR : 퀸 이동 패턴으로 이동할 수 없는 위치일 경우 + - [x] 모든 방향으로 원하는 만큼 이동 - [x] 킹 - - [x] 모든 방향 1칸 이동 - - [x] ERROR : 킹 이동 패턴으로 이동할 수 없는 위치일 경우 - - [x] 상대의 공격 범위로는 이동 불가능 + - [x] 모든 방향 1칸 이동 + - [x] 상대의 공격 범위로는 이동 불가능 diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index 246f3df..f6d62ee 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,7 +1,7 @@ package chess.controller; -import chess.domain.player.Scores; import chess.domain.command.Command; +import chess.domain.player.Scores; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; import chess.service.ChessService; @@ -45,7 +45,6 @@ private void runOneTurn() { Command command = new Command(inputView.getCommand()); chessService.run(command); - } catch (UnsupportedOperationException | IllegalArgumentException e) { outputView.printMessage(e.getMessage()); } catch (ScoresRequestedException e) { diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index fcaf6da..958e32e 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -1,15 +1,15 @@ package chess.domain.board; import chess.domain.command.MoveParameters; -import chess.domain.piece.Color; -import chess.domain.piece.Piece; +import chess.domain.piece.move.Path; +import chess.domain.piece.type.Piece; +import chess.domain.player.Color; import chess.domain.player.Player; import chess.domain.player.Scores; +import chess.exception.EmptyPositionException; -import java.util.Set; - -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; +import static chess.domain.player.Color.BLACK; +import static chess.domain.player.Color.WHITE; public class Board { @@ -29,7 +29,7 @@ public void move(final MoveParameters moveParameters) { validateParameters(source, target); movePiece(source, target); - currentTurn = currentTurn.nextTurn(); + currentTurn = currentTurn.flip(); } private void validateParameters(final Position source, final Position target) { @@ -46,7 +46,7 @@ private void validateSourceOwner(final Position source) { } private void validateSamePosition(final Position source, final Position target) { - if (source.equals(target)) { + if (source.isSame(target)) { throw new IllegalArgumentException("출발 위치와 도착 위치가 같을 수 없습니다."); } } @@ -58,57 +58,47 @@ private void validateTargetOwner(final Position target) { } private void validateKingMovable(final Position source, final Position target) { - if (currentPlayer().hasKingOn(source) && enemyPlayer().canAttack(target)) { + if (currentPlayer().hasKingOn(source) && canEnemyAttack(target)) { throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } } - private Player currentPlayer() { - if (currentTurn.isWhite()) { - return whitePlayer; - } - return blackPlayer; + private boolean canEnemyAttack(final Position target) { + return enemyPlayer().findAttackPaths(target).stream() + .anyMatch(path -> path.isNotBlockedBy(whitePlayer) && path.isNotBlockedBy(blackPlayer)); } - private Player enemyPlayer() { - if (currentTurn.isWhite()) { - return blackPlayer; + private void movePiece(final Position source, final Position target) { + if (currentPlayer().isPawnAttacking(source, target) && enemyPlayer().hasNoPieceOn(target)) { + throw new IllegalArgumentException("폰은 공격 대상이 있는 경우에만 대각선으로 이동할 수 있습니다."); } - return whitePlayer; - } - private void movePiece(final Position source, final Position target) { - Set paths = currentPlayer().findPaths(source, target); - validatePathsEmpty(paths); + Path path = currentPlayer().findMovePath(source, target); + validatePathNotBlocked(path); - enemyPlayer().isUnderAttack(target); + enemyPlayer().wasAttackedBy(target); currentPlayer().move(source, target); } - private void validatePathsEmpty(final Set paths) { - boolean isWhiteBlocked = paths.stream() - .anyMatch(whitePlayer::hasPieceOn); - boolean isBlackBlocked = paths.stream() - .anyMatch(blackPlayer::hasPieceOn); - - if (isWhiteBlocked || isBlackBlocked) { + private void validatePathNotBlocked(final Path path) { + if (path.isBlockedBy(whitePlayer) || path.isBlockedBy(blackPlayer)) { throw new IllegalArgumentException("다른 기물을 통과하여 이동할 수 없습니다."); } } - public boolean isEmpty(final Position position) { - return whitePlayer.hasNoPieceOn(position) && blackPlayer.hasNoPieceOn(position); - } - public Piece findBy(final Position position) { + if (isEmpty(position)) { + throw new EmptyPositionException(); + } + if (whitePlayer.hasPieceOn(position)) { return whitePlayer.findPieceBy(position); } return blackPlayer.findPieceBy(position); } - public Color getCurrentTurn() { - return currentTurn; + private boolean isEmpty(Position position) { + return whitePlayer.hasNoPieceOn(position) && blackPlayer.hasNoPieceOn(position); } public Scores getScores() { @@ -132,4 +122,22 @@ public Color getWinner() { public boolean isBothKingAlive() { return whitePlayer.isKingAlive() && blackPlayer.isKingAlive(); } + + public Color getCurrentTurn() { + return currentTurn; + } + + private Player currentPlayer() { + if (currentTurn.isWhite()) { + return whitePlayer; + } + return blackPlayer; + } + + private Player enemyPlayer() { + if (currentTurn.isWhite()) { + return blackPlayer; + } + return whitePlayer; + } } diff --git a/src/main/java/chess/domain/board/Position.java b/src/main/java/chess/domain/board/Position.java index e6bc6d4..fa6e4f0 100644 --- a/src/main/java/chess/domain/board/Position.java +++ b/src/main/java/chess/domain/board/Position.java @@ -1,6 +1,7 @@ package chess.domain.board; -import chess.domain.piece.type.MoveUnit; +import chess.domain.piece.move.Gap; +import chess.domain.piece.move.MoveUnit; import java.util.*; @@ -22,6 +23,14 @@ private static String createKey(final File file, final Rank rank) { return file.name() + rank.getIndex(); } + public static Collection names() { + return POSITIONS.keySet(); + } + + public static Position from(final File file, final Rank rank) { + return of(createKey(file, rank)); + } + public static Position of(String key) { key = key.toLowerCase(); @@ -32,16 +41,7 @@ public static Position of(String key) { return POSITIONS.get(key); } - public static Position from(final File file, final Rank rank) { - return of(createKey(file, rank)); - } - - public static Collection names() { - return POSITIONS.keySet(); - } - private final File file; - private final Rank rank; private Position(final File file, final Rank rank) { @@ -49,18 +49,25 @@ private Position(final File file, final Rank rank) { this.rank = rank; } - public int calculateFileGap(final Position position) { + public Gap calculateGap(final Position position) { + int fileGap = calculateFileGap(position); + int rankGap = calculateRankGap(position); + + return new Gap(fileGap, rankGap); + } + + private int calculateFileGap(final Position position) { return file.calculateGap(position.getFile()); } - public int calculateRankGap(final Position position) { + private int calculateRankGap(final Position position) { return rank.calculateGap(position.getRank()); } - public Set findPassingPositions(Position target, MoveUnit moveUnit) { - Set positions = new HashSet<>(); - Position current = this; + public List findPassingPositions(final Position target, final MoveUnit moveUnit) { + List positions = new ArrayList<>(); + Position current = this; while (!target.equals(current)) { current = current.move(moveUnit); positions.add(current); @@ -70,22 +77,8 @@ public Set findPassingPositions(Position target, MoveUnit moveUnit) { return positions; } - public Collection findAvailablePositions(final MoveUnit moveUnit, final boolean isFinite) { - if (isFinite) { - return getFinitePositions(moveUnit); - } - return getInfinitePositions(moveUnit); - } - - private Collection getFinitePositions(MoveUnit moveUnit) { - if (isMovable(moveUnit)) { - return Collections.singleton(move(moveUnit)); - } - return Collections.emptySet(); - } - - private Collection getInfinitePositions(MoveUnit moveUnit) { - Collection positions = new HashSet<>(); + public List findReachablePositions(final MoveUnit moveUnit) { + List positions = new ArrayList<>(); Position current = this; while (current.isMovable(moveUnit)) { @@ -96,18 +89,19 @@ private Collection getInfinitePositions(MoveUnit moveUnit) { return positions; } - private boolean isMovable(final MoveUnit moveUnit) { + public boolean isMovable(final MoveUnit moveUnit) { return rank.canMove(moveUnit.getRank()) && file.canMove(moveUnit.getFile()); } - private Position move(final MoveUnit moveUnit) { - File file = this.file.add(moveUnit.getFile()); - Rank rank = this.rank.add(moveUnit.getRank()); - return Position.from(file, rank); + public Position move(final MoveUnit moveUnit) { + File nextFile = this.file.add(moveUnit.getFile()); + Rank nextRank = this.rank.add(moveUnit.getRank()); + + return Position.from(nextFile, nextRank); } - public boolean hasSameRank(Rank rank) { - return this.rank == rank; + public boolean isSame(Position position) { + return this == position; } public File getFile() { @@ -117,4 +111,12 @@ public File getFile() { public Rank getRank() { return rank; } + + public boolean isWhitePawnInitialRank() { + return this.rank.isWhiteInitialRank(); + } + + public boolean isBlackPawnInitialRank() { + return this.rank.isBlackInitialRank(); + } } diff --git a/src/main/java/chess/domain/board/Rank.java b/src/main/java/chess/domain/board/Rank.java index daecfbc..3b58100 100644 --- a/src/main/java/chess/domain/board/Rank.java +++ b/src/main/java/chess/domain/board/Rank.java @@ -13,6 +13,9 @@ public enum Rank { R2(2), R1(1); + private static final Rank WHITE_INITIAL_RANK = R2; + private static final Rank BLACK_INITIAL_RANK = R7; + private final int index; Rank(final int index) { @@ -46,5 +49,13 @@ public boolean canMove(final int amount) { int rankIndex = index + amount; return rankIndex >= R1.index && rankIndex <= R8.index; } + + public boolean isWhiteInitialRank() { + return this == WHITE_INITIAL_RANK; + } + + public boolean isBlackInitialRank() { + return this == BLACK_INITIAL_RANK; + } } diff --git a/src/main/java/chess/domain/command/MoveParameters.java b/src/main/java/chess/domain/command/MoveParameters.java index 7a1cad1..d9e3617 100644 --- a/src/main/java/chess/domain/command/MoveParameters.java +++ b/src/main/java/chess/domain/command/MoveParameters.java @@ -13,14 +13,14 @@ public class MoveParameters { private final Position target; public MoveParameters(final List parameters) { - this(Position.of(parameters.get(SOURCE_INDEX)), Position.of(parameters.get(TARGET_INDEX))); + this(parameters.get(SOURCE_INDEX), parameters.get(TARGET_INDEX)); } - public MoveParameters(String source, String target) { + public MoveParameters(final String source, final String target) { this(Position.of(source), Position.of(target)); } - public MoveParameters(final Position source, final Position target) { + private MoveParameters(final Position source, final Position target) { this.source = source; this.target = target; } diff --git a/src/main/java/chess/domain/piece/Bishop.java b/src/main/java/chess/domain/piece/Bishop.java deleted file mode 100644 index e6a7a6a..0000000 --- a/src/main/java/chess/domain/piece/Bishop.java +++ /dev/null @@ -1,10 +0,0 @@ -package chess.domain.piece; - -import chess.domain.piece.type.MovePattern; - -public class Bishop extends Piece { - - public Bishop(final Color color) { - super(MovePattern.bishopPattern(), color); - } -} diff --git a/src/main/java/chess/domain/piece/King.java b/src/main/java/chess/domain/piece/King.java deleted file mode 100644 index f3c7b73..0000000 --- a/src/main/java/chess/domain/piece/King.java +++ /dev/null @@ -1,10 +0,0 @@ -package chess.domain.piece; - -import chess.domain.piece.type.MovePattern; - -public class King extends Piece { - - public King(final Color color) { - super(MovePattern.kingPattern(), color); - } -} diff --git a/src/main/java/chess/domain/piece/Knight.java b/src/main/java/chess/domain/piece/Knight.java deleted file mode 100644 index 14e65ee..0000000 --- a/src/main/java/chess/domain/piece/Knight.java +++ /dev/null @@ -1,10 +0,0 @@ -package chess.domain.piece; - -import chess.domain.piece.type.MovePattern; - -public class Knight extends Piece { - - public Knight(final Color color) { - super(MovePattern.knightPattern(), color); - } -} diff --git a/src/main/java/chess/domain/piece/Pawn.java b/src/main/java/chess/domain/piece/Pawn.java deleted file mode 100644 index 1bc8214..0000000 --- a/src/main/java/chess/domain/piece/Pawn.java +++ /dev/null @@ -1,73 +0,0 @@ -package chess.domain.piece; - -import chess.domain.board.Position; -import chess.domain.board.Rank; -import chess.domain.piece.type.MovePattern; -import chess.domain.piece.type.MoveUnit; - -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; - -import static chess.domain.piece.type.MoveUnit.*; - -public class Pawn extends Piece { - - private static final Rank WHITE_INITIAL_RANK = Rank.R2; - private static final Rank BLACK_INITIAL_RANK = Rank.R7; - - public Pawn(final Color color) { - super(MovePattern.pawnPattern(color), color); - } - - @Override - public Set findPath(final Position source, final Position target) { - int fileGap = target.calculateFileGap(source); - int rankGap = target.calculateRankGap(source); - - MoveUnit moveUnit = movePattern.findMoveUnit(fileGap, rankGap); - moveUnit = resetIfInitialMove(moveUnit, isInitialMove(source)); - - return source.findPassingPositions(target, moveUnit); - } - - private boolean isInitialMove(Position source) { - return source.hasSameRank(WHITE_INITIAL_RANK) || source.hasSameRank(BLACK_INITIAL_RANK); - } - - private MoveUnit resetIfInitialMove(MoveUnit moveUnit, boolean isInitialMove) { - validateMoveCount(moveUnit, isInitialMove); - - if (isWhiteInitialMove(moveUnit)) { - return NORTH; - } - - if (isBlackInitialMove(moveUnit)) { - return SOUTH; - } - - return moveUnit; - } - - private void validateMoveCount(MoveUnit moveUnit, boolean isInitialMove) { - if (!isInitialMove && (isBlackInitialMove(moveUnit) || isWhiteInitialMove(moveUnit))) { - throw new IllegalArgumentException("최초 이동 시에만 2칸 이동할 수 있습니다."); - } - } - - private boolean isWhiteInitialMove(final MoveUnit moveUnit) { - return moveUnit == WHITE_PAWN_INITIAL_NORTH; - } - - private boolean isBlackInitialMove(final MoveUnit moveUnit) { - return moveUnit == BLACK_PAWN_INITIAL_SOUTH; - } - - @Override - public Collection findAvailableAttackPositions(final Position position) { - return movePattern.pawnAttackMoveUnits(isWhite()).stream() - .map(moveUnit -> position.findAvailablePositions(moveUnit, true)) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - } -} diff --git a/src/main/java/chess/domain/piece/Piece.java b/src/main/java/chess/domain/piece/Piece.java deleted file mode 100644 index 7d63bbc..0000000 --- a/src/main/java/chess/domain/piece/Piece.java +++ /dev/null @@ -1,50 +0,0 @@ -package chess.domain.piece; - -import chess.domain.board.Position; -import chess.domain.piece.type.MovePattern; -import chess.domain.piece.type.MoveUnit; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - -public abstract class Piece { - - protected final MovePattern movePattern; - private final Color color; - - Piece(final MovePattern movePattern, final Color color) { - this.movePattern = movePattern; - this.color = color; - } - - public boolean isWhite() { - return color.isWhite(); - } - - public Set findPath(final Position source, final Position target) { - int fileGap = target.calculateFileGap(source); - int rankGap = target.calculateRankGap(source); - - MoveUnit moveUnit = movePattern.findMoveUnit(fileGap, rankGap); - return source.findPassingPositions(target, moveUnit); - } - - public Collection findAvailableAttackPositions(final Position position) { - Set finitePositions = movePattern.finiteMoveUnits().stream() - .map(moveUnit -> position.findAvailablePositions(moveUnit, true)) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - - Set infinitePositions = movePattern.infiniteMoveUnits().stream() - .map(moveUnit -> position.findAvailablePositions(moveUnit, false)) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); - - Collection positions = new HashSet<>(finitePositions); - positions.addAll(infinitePositions); - return positions; - } -} - diff --git a/src/main/java/chess/domain/piece/Queen.java b/src/main/java/chess/domain/piece/Queen.java deleted file mode 100644 index 9c68242..0000000 --- a/src/main/java/chess/domain/piece/Queen.java +++ /dev/null @@ -1,10 +0,0 @@ -package chess.domain.piece; - -import chess.domain.piece.type.MovePattern; - -public class Queen extends Piece { - - public Queen(final Color color) { - super(MovePattern.queenPattern(), color); - } -} diff --git a/src/main/java/chess/domain/piece/Rook.java b/src/main/java/chess/domain/piece/Rook.java deleted file mode 100644 index 2a7bba0..0000000 --- a/src/main/java/chess/domain/piece/Rook.java +++ /dev/null @@ -1,10 +0,0 @@ -package chess.domain.piece; - -import chess.domain.piece.type.MovePattern; - -public class Rook extends Piece { - - public Rook(final Color color) { - super(MovePattern.rookPattern(), color); - } -} diff --git a/src/main/java/chess/domain/piece/move/Gap.java b/src/main/java/chess/domain/piece/move/Gap.java new file mode 100644 index 0000000..b05e586 --- /dev/null +++ b/src/main/java/chess/domain/piece/move/Gap.java @@ -0,0 +1,20 @@ +package chess.domain.piece.move; + +public class Gap { + + private final int fileGap; + private final int rankGap; + + public Gap(int fileGap, int rankGap) { + this.fileGap = fileGap; + this.rankGap = rankGap; + } + + public int file() { + return fileGap; + } + + public int rank() { + return rankGap; + } +} diff --git a/src/main/java/chess/domain/piece/move/MoveLimit.java b/src/main/java/chess/domain/piece/move/MoveLimit.java new file mode 100644 index 0000000..225a4a8 --- /dev/null +++ b/src/main/java/chess/domain/piece/move/MoveLimit.java @@ -0,0 +1,17 @@ +package chess.domain.piece.move; + +public enum MoveLimit { + + FINITE(true), + INFINITE(false); + + private final boolean isFinite; + + MoveLimit(boolean isFinite) { + this.isFinite = isFinite; + } + + public boolean isFinite() { + return isFinite; + } +} diff --git a/src/main/java/chess/domain/piece/move/MovePattern.java b/src/main/java/chess/domain/piece/move/MovePattern.java new file mode 100644 index 0000000..8c74b05 --- /dev/null +++ b/src/main/java/chess/domain/piece/move/MovePattern.java @@ -0,0 +1,56 @@ +package chess.domain.piece.move; + +import chess.domain.board.Position; +import chess.domain.player.Color; + +import java.util.Collection; + +import static chess.domain.piece.move.MoveLimit.FINITE; +import static chess.domain.piece.move.MoveLimit.INFINITE; +import static chess.domain.piece.move.MoveUnits.*; + +public enum MovePattern { + + KING(CARDINAL_AND_DIAGONAL, FINITE), + QUEEN(CARDINAL_AND_DIAGONAL, INFINITE), + BISHOP(DIAGONAL, INFINITE), + ROOK(CARDINAL, INFINITE), + KNIGHT(MoveUnits.KNIGHT, FINITE), + WHITE_PAWN(MoveUnits.WHITE_PAWN, FINITE), + BLACK_PAWN(MoveUnits.BLACK_PAWN, FINITE), + WHITE_PAWN_ATTACK(MoveUnits.WHITE_PAWN_ATTACK, FINITE), + BLACK_PAWN_ATTACK(MoveUnits.BLACK_PAWN_ATTACK, FINITE); + + public static MovePattern pawnPattern(final Color color) { + if (color.isWhite()) { + return WHITE_PAWN; + } + return BLACK_PAWN; + } + + public static MovePattern pawnAttackPattern(final Color color) { + if (color.isWhite()) { + return WHITE_PAWN_ATTACK; + } + return BLACK_PAWN_ATTACK; + } + + private final MoveUnits moveUnits; + private final MoveLimit moveLimit; + + MovePattern(final MoveUnits moveUnits, final MoveLimit moveLimit) { + this.moveUnits = moveUnits; + this.moveLimit = moveLimit; + } + + public MoveUnit findMatchMoveUnit(final Gap gap) { + return moveUnits.findMatchMoveUnit(gap, moveLimit); + } + + public Collection findAttackPaths(final Position source) { + if (moveLimit.isFinite()) { + return moveUnits.findReachableFinitePaths(source); + } + return moveUnits.findReachableInfinitePaths(source); + } +} diff --git a/src/main/java/chess/domain/piece/move/MoveUnit.java b/src/main/java/chess/domain/piece/move/MoveUnit.java new file mode 100644 index 0000000..ac1b4f3 --- /dev/null +++ b/src/main/java/chess/domain/piece/move/MoveUnit.java @@ -0,0 +1,85 @@ +package chess.domain.piece.move; + +public enum MoveUnit { + + NORTH_EAST(1, 1), + SOUTH_EAST(1, -1), + NORTH_WEST(-1, 1), + SOUTH_WEST(-1, -1), + NORTH(0, 1), + SOUTH(0, -1), + EAST(1, 0), + WEST(-1, 0), + + WHITE_PAWN_INITIAL(0, 2), + BLACK_PAWN_INITIAL(0, -2), + + NORTH_EAST_LEFT(1, 2), + NORTH_EAST_RIGHT(2, 1), + NORTH_WEST_LEFT(-2, 1), + NORTH_WEST_RIGHT(-1, 2), + SOUTH_EAST_LEFT(2, -1), + SOUTH_EAST_RIGHT(1, -2), + SOUTH_WEST_LEFT(-1, -2), + SOUTH_WEST_RIGHT(-2, -1); + + public static MoveUnit whitePawnMove() { + return NORTH; + } + + public static MoveUnit blackPawnMove() { + return SOUTH; + } + + private final int file; + private final int rank; + + MoveUnit(final int file, final int rank) { + this.file = file; + this.rank = rank; + } + + public boolean matches(final Gap gap, final MoveLimit moveLimit) { + if (moveLimit.isFinite()) { + return this.file == gap.file() && this.rank == gap.rank(); + } + + if (this.file == 0) { + return this.file == gap.file() && (gap.rank() % this.rank == 0) && hasSameSign(gap); + } + + if (this.rank == 0) { + return this.rank == gap.rank() && (gap.file() % this.file == 0) && hasSameSign(gap); + } + + return isMultiple(gap) && hasSameSign(gap); + } + + private boolean isMultiple(final Gap gap) { + return hasSameRate(gap) && isDivisible(gap); + } + + private boolean isDivisible(final Gap gap) { + return (gap.file() % this.file == 0) && (gap.rank() % this.rank == 0); + } + + private boolean hasSameRate(final Gap gap) { + return (gap.file() / this.file) == (gap.rank() / this.rank); + } + + private boolean hasSameSign(final Gap gap) { + return (file ^ gap.file()) >= 0 && (rank ^ gap.rank()) >= 0; + } + + public boolean isInitialPawn() { + return this == WHITE_PAWN_INITIAL || this == BLACK_PAWN_INITIAL; + } + + public int getFile() { + return file; + } + + public int getRank() { + return rank; + } +} diff --git a/src/main/java/chess/domain/piece/move/MoveUnits.java b/src/main/java/chess/domain/piece/move/MoveUnits.java new file mode 100644 index 0000000..2bee6cd --- /dev/null +++ b/src/main/java/chess/domain/piece/move/MoveUnits.java @@ -0,0 +1,63 @@ +package chess.domain.piece.move; + +import chess.domain.board.Position; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.stream.Collectors; + +import static chess.domain.piece.move.MoveUnit.*; + +public enum MoveUnits { + + CARDINAL(NORTH, SOUTH, WEST, EAST), + DIAGONAL(NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST), + CARDINAL_AND_DIAGONAL(NORTH, SOUTH, WEST, EAST, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST), + KNIGHT(NORTH_EAST_LEFT, NORTH_EAST_RIGHT, NORTH_WEST_LEFT, NORTH_WEST_RIGHT, SOUTH_EAST_LEFT, SOUTH_EAST_RIGHT, SOUTH_WEST_LEFT, SOUTH_WEST_RIGHT), + WHITE_PAWN(WHITE_PAWN_INITIAL, NORTH_EAST, NORTH_WEST, NORTH), + WHITE_PAWN_ATTACK(NORTH_EAST, NORTH_WEST), + BLACK_PAWN(BLACK_PAWN_INITIAL, SOUTH_EAST, SOUTH_WEST, SOUTH), + BLACK_PAWN_ATTACK(SOUTH_EAST, SOUTH_WEST); + + private final Collection moveUnits; + + MoveUnits(MoveUnit... moveUnits) { + this.moveUnits = Collections.unmodifiableCollection( + Arrays.stream(moveUnits) + .collect(Collectors.toSet()) + ); + } + + public MoveUnit findMatchMoveUnit(final Gap gap, final MoveLimit moveLimit) { + return moveUnits.stream() + .filter(moveUnit -> moveUnit.matches(gap, moveLimit)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("해당 방향으로 이동할 수 없습니다.")); + } + + public Collection findReachableFinitePaths(final Position source) { + Collection paths = new HashSet<>(); + + moveUnits.stream() + .filter(source::isMovable) + .forEach(moveUnit -> addNewPath(source, moveUnit, paths)); + + return paths; + } + + private void addNewPath(Position source, MoveUnit moveUnit, Collection paths) { + Position target = source.move(moveUnit); + Path path = new Path(source, target); + paths.add(path); + } + + public Collection findReachableInfinitePaths(final Position source) { + return moveUnits.stream() + .filter(source::isMovable) + .map(source::findReachablePositions) + .map(Path::new) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/chess/domain/piece/move/Path.java b/src/main/java/chess/domain/piece/move/Path.java new file mode 100644 index 0000000..87d7f27 --- /dev/null +++ b/src/main/java/chess/domain/piece/move/Path.java @@ -0,0 +1,67 @@ +package chess.domain.piece.move; + +import chess.domain.board.Position; +import chess.domain.player.Player; + +import java.util.*; + +public class Path { + + private final List positions; + + public Path(Position position) { + this(Collections.singletonList(position)); + } + + public Path(Position... positions) { + this(Arrays.asList(positions)); + } + + public Path(List positions) { + this.positions = Collections.unmodifiableList(new ArrayList<>(positions)); + } + + public boolean contains(Position position) { + return positions.contains(position); + } + + public Path getPositionsUntilTarget(Position target) { + List positionsBeforeTarget = new ArrayList<>(); + + for (Position position : positions) { + if (position.isSame(target)) { + break; + } + positionsBeforeTarget.add(position); + } + + return new Path(positionsBeforeTarget); + } + + public boolean isNotBlockedBy(Player player) { + return positions.stream() + .noneMatch(player::hasPieceOn); + } + + public boolean isBlockedBy(Player player) { + return positions.stream() + .anyMatch(player::hasPieceOn); + } + + public boolean isNotEmpty() { + return !positions.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Path path = (Path) o; + return positions.equals(path.positions); + } + + @Override + public int hashCode() { + return Objects.hash(positions); + } +} diff --git a/src/main/java/chess/domain/piece/type/Bishop.java b/src/main/java/chess/domain/piece/type/Bishop.java new file mode 100644 index 0000000..0834202 --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Bishop.java @@ -0,0 +1,11 @@ +package chess.domain.piece.type; + +import chess.domain.piece.move.MovePattern; +import chess.domain.player.Color; + +public class Bishop extends Piece { + + public Bishop(final Color color) { + super(MovePattern.BISHOP, color); + } +} diff --git a/src/main/java/chess/domain/piece/type/King.java b/src/main/java/chess/domain/piece/type/King.java new file mode 100644 index 0000000..5c897f7 --- /dev/null +++ b/src/main/java/chess/domain/piece/type/King.java @@ -0,0 +1,11 @@ +package chess.domain.piece.type; + +import chess.domain.piece.move.MovePattern; +import chess.domain.player.Color; + +public class King extends Piece { + + public King(final Color color) { + super(MovePattern.KING, color); + } +} diff --git a/src/main/java/chess/domain/piece/type/Knight.java b/src/main/java/chess/domain/piece/type/Knight.java new file mode 100644 index 0000000..e83799b --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Knight.java @@ -0,0 +1,11 @@ +package chess.domain.piece.type; + +import chess.domain.piece.move.MovePattern; +import chess.domain.player.Color; + +public class Knight extends Piece { + + public Knight(final Color color) { + super(MovePattern.KNIGHT, color); + } +} diff --git a/src/main/java/chess/domain/piece/type/MovePattern.java b/src/main/java/chess/domain/piece/type/MovePattern.java deleted file mode 100644 index c7810f5..0000000 --- a/src/main/java/chess/domain/piece/type/MovePattern.java +++ /dev/null @@ -1,113 +0,0 @@ -package chess.domain.piece.type; - -import chess.domain.piece.Color; - -import java.util.*; - -import static chess.domain.piece.type.MoveUnit.*; - -public class MovePattern { - - private static final Collection CARDINAL_UNITS = Collections.unmodifiableList(Arrays.asList( - NORTH, SOUTH, WEST, EAST - )); - - private static final Collection DIAGONAL_UNITS = Collections.unmodifiableList(Arrays.asList( - NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST - )); - - private static final Collection WHITE_PAWN_UNITS = Collections.unmodifiableList(Arrays.asList( - WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH - )); - - private static final Collection WHITE_PAWN_ATTACK_UNITS = Collections.unmodifiableList(Arrays.asList( - NORTH_EAST, NORTH_WEST - )); - - private static final Collection BLACK_PAWN_UNITS = Collections.unmodifiableList(Arrays.asList( - BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH - )); - - private static final Collection BLACK_PAWN_ATTACK_UNITS = Collections.unmodifiableList(Arrays.asList( - SOUTH_EAST, SOUTH_WEST - )); - - private static final Collection KNIGHT_UNITS = Collections.unmodifiableList(Arrays.asList( - NORTH_EAST_LEFT, NORTH_EAST_RIGHT, NORTH_WEST_LEFT, NORTH_WEST_RIGHT, - SOUTH_EAST_LEFT, SOUTH_EAST_RIGHT, SOUTH_WEST_LEFT, SOUTH_WEST_RIGHT - )); - - private final Collection infiniteMoveUnits; - private final Collection finiteMoveUnits; - - private MovePattern(final Collection infiniteMoveUnits, final Collection finiteMoveUnits) { - this.infiniteMoveUnits = Collections.unmodifiableCollection(infiniteMoveUnits); - this.finiteMoveUnits = Collections.unmodifiableCollection(finiteMoveUnits); - } - - public static MovePattern queenPattern() { - List infiniteMoveUnits = new ArrayList<>(CARDINAL_UNITS); - infiniteMoveUnits.addAll(DIAGONAL_UNITS); - return new MovePattern(infiniteMoveUnits, Collections.emptyList()); - } - - public static MovePattern kingPattern() { - List finiteMoveUnits = new ArrayList<>(CARDINAL_UNITS); - finiteMoveUnits.addAll(DIAGONAL_UNITS); - return new MovePattern(Collections.emptyList(), finiteMoveUnits); - } - - public static MovePattern knightPattern() { - return new MovePattern(Collections.emptyList(), KNIGHT_UNITS); - } - - public static MovePattern rookPattern() { - return new MovePattern(CARDINAL_UNITS, Collections.emptyList()); - } - - public static MovePattern bishopPattern() { - return new MovePattern(DIAGONAL_UNITS, Collections.emptyList()); - } - - public static MovePattern pawnPattern(final Color color) { - if (color.isWhite()) { - return new MovePattern(Collections.emptyList(), WHITE_PAWN_UNITS); - } - return new MovePattern(Collections.emptyList(), BLACK_PAWN_UNITS); - } - - public MoveUnit findMoveUnit(int fileGap, int rankGap) { - List result = new ArrayList<>(); - - infiniteMoveUnits.stream() - .filter(moveUnit -> moveUnit.matches(fileGap, rankGap, false)) - .findAny() - .ifPresent(result::add); - - finiteMoveUnits.stream() - .filter(moveUnit -> moveUnit.matches(fileGap, rankGap, true)) - .findAny() - .ifPresent(result::add); - - if (result.isEmpty()) { - throw new IllegalArgumentException("해당 방향으로 이동할 수 없습니다."); - } - - return result.get(0); - } - - public Collection finiteMoveUnits() { - return finiteMoveUnits; - } - - public Collection infiniteMoveUnits() { - return infiniteMoveUnits; - } - - public Collection pawnAttackMoveUnits(boolean isWhite) { - if (isWhite) { - return WHITE_PAWN_ATTACK_UNITS; - } - return BLACK_PAWN_ATTACK_UNITS; - } -} diff --git a/src/main/java/chess/domain/piece/type/MoveUnit.java b/src/main/java/chess/domain/piece/type/MoveUnit.java deleted file mode 100644 index 04106fd..0000000 --- a/src/main/java/chess/domain/piece/type/MoveUnit.java +++ /dev/null @@ -1,73 +0,0 @@ -package chess.domain.piece.type; - -public enum MoveUnit { - - NORTH_EAST(1, 1), - SOUTH_EAST(1, -1), - NORTH_WEST(-1, 1), - SOUTH_WEST(-1, -1), - NORTH(0, 1), - SOUTH(0, -1), - EAST(1, 0), - WEST(-1, 0), - - WHITE_PAWN_INITIAL_NORTH(0, 2), - BLACK_PAWN_INITIAL_SOUTH(0, -2), - - NORTH_EAST_LEFT(1, 2), - NORTH_EAST_RIGHT(2, 1), - NORTH_WEST_LEFT(-2, 1), - NORTH_WEST_RIGHT(-1, 2), - SOUTH_EAST_LEFT(2, -1), - SOUTH_EAST_RIGHT(1, -2), - SOUTH_WEST_LEFT(-1, -2), - SOUTH_WEST_RIGHT(-2, -1); - - private final int file; - private final int rank; - - MoveUnit(final int file, final int rank) { - this.file = file; - this.rank = rank; - } - - public boolean matches(final int fileGap, final int rankGap, boolean isFinite) { - if (isFinite) { - return this.file == fileGap && this.rank == rankGap; - } - - if (this.file == 0) { - return this.file == fileGap && (rankGap % this.rank == 0) && hasSameSign(fileGap, rankGap); - } - - if (this.rank == 0) { - return this.rank == rankGap && (fileGap % this.file == 0) && hasSameSign(fileGap, rankGap); - } - - return isMultiple(fileGap, rankGap) && hasSameSign(fileGap, rankGap); - } - - private boolean isMultiple(final int fileGap, final int rankGap) { - return hasSameRate(fileGap, rankGap) && isDivisible(fileGap, rankGap); - } - - private boolean isDivisible(final int fileGap, final int rankGap) { - return (fileGap % this.file == 0) && (rankGap % this.rank == 0); - } - - private boolean hasSameRate(final int fileGap, final int rankGap) { - return (fileGap / this.file) == (rankGap / this.rank); - } - - private boolean hasSameSign(final int fileGap, final int rankGap) { - return (file ^ fileGap) >= 0 && (rank ^ rankGap) >= 0; - } - - public int getFile() { - return file; - } - - public int getRank() { - return rank; - } -} diff --git a/src/main/java/chess/domain/piece/type/Pawn.java b/src/main/java/chess/domain/piece/type/Pawn.java new file mode 100644 index 0000000..b6c185f --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Pawn.java @@ -0,0 +1,75 @@ +package chess.domain.piece.type; + +import chess.domain.board.Position; +import chess.domain.piece.move.Gap; +import chess.domain.piece.move.MovePattern; +import chess.domain.piece.move.MoveUnit; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; + +import java.util.Collection; +import java.util.List; + +public class Pawn extends Piece { + + private final MovePattern attackPattern; + + public Pawn(final Color color) { + super(MovePattern.pawnPattern(color), color); + this.attackPattern = MovePattern.pawnAttackPattern(color); + } + + @Override + public Path findMovePath(final Position source, final Position target) { + Gap gap = target.calculateGap(source); + MoveUnit moveUnit = movePattern.findMatchMoveUnit(gap); + + if (moveUnit.isInitialPawn()) { + validateInitialMoveAvailable(source); + moveUnit = resetMoveUnit(); + } + + List passingPositions = source.findPassingPositions(target, moveUnit); + return new Path(passingPositions); + } + + private void validateInitialMoveAvailable(Position source) { + if (isInitialMoveUnavailable(source)) { + throw new IllegalArgumentException("최초 이동 시에만 2칸 이동할 수 있습니다."); + } + } + + private MoveUnit resetMoveUnit() { + if (color.isWhite()) { + return MoveUnit.whitePawnMove(); + } + return MoveUnit.blackPawnMove(); + } + + private boolean isInitialMoveUnavailable(final Position source) { + return !isWhiteInitialMoveAvailable(source) && !isBlackInitialMoveAvailable(source); + } + + private boolean isWhiteInitialMoveAvailable(final Position source) { + return color.isWhite() && source.isWhitePawnInitialRank(); + } + + private boolean isBlackInitialMoveAvailable(final Position source) { + return color.isBlack() && source.isBlackPawnInitialRank(); + } + + @Override + public Collection findAttackPaths(final Position source) { + return attackPattern.findAttackPaths(source); + } + + public boolean isAttacking(final Position source, final Position target) { + try { + Gap gap = target.calculateGap(source); + attackPattern.findMatchMoveUnit(gap); + } catch (IllegalArgumentException e) { + return false; + } + return true; + } +} diff --git a/src/main/java/chess/domain/piece/type/Piece.java b/src/main/java/chess/domain/piece/type/Piece.java new file mode 100644 index 0000000..132d5f9 --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Piece.java @@ -0,0 +1,51 @@ +package chess.domain.piece.type; + +import chess.domain.board.Position; +import chess.domain.piece.move.Gap; +import chess.domain.piece.move.MovePattern; +import chess.domain.piece.move.MoveUnit; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; + +import java.util.Collection; +import java.util.List; + +public abstract class Piece { + + protected final MovePattern movePattern; + protected final Color color; + + Piece(final MovePattern movePattern, final Color color) { + this.movePattern = movePattern; + this.color = color; + } + + public Path findMovePath(final Position source, final Position target) { + Gap gap = target.calculateGap(source); + + MoveUnit moveUnit = movePattern.findMatchMoveUnit(gap); + List passingPositions = source.findPassingPositions(target, moveUnit); + + return new Path(passingPositions); + } + + public Collection findAttackPaths(final Position source) { + return movePattern.findAttackPaths(source); + } + + public boolean isWhite() { + return color.isWhite(); + } + + public boolean isPawn() { + return PieceType.isPawn(this); + } + + public boolean isNotPawn() { + return !isPawn(); + } + + public boolean isKing() { + return PieceType.isKing(this); + } +} diff --git a/src/main/java/chess/domain/piece/PieceFactory.java b/src/main/java/chess/domain/piece/type/PieceFactory.java similarity index 84% rename from src/main/java/chess/domain/piece/PieceFactory.java rename to src/main/java/chess/domain/piece/type/PieceFactory.java index 8fe131a..705b4cd 100644 --- a/src/main/java/chess/domain/piece/PieceFactory.java +++ b/src/main/java/chess/domain/piece/type/PieceFactory.java @@ -1,23 +1,24 @@ -package chess.domain.piece; +package chess.domain.piece.type; import chess.domain.board.File; -import chess.domain.board.Rank; import chess.domain.board.Position; +import chess.domain.board.Rank; +import chess.domain.player.Color; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import static chess.domain.piece.Color.BLACK; -import static chess.domain.piece.Color.WHITE; +import static chess.domain.player.Color.BLACK; +import static chess.domain.player.Color.WHITE; public class PieceFactory { private PieceFactory() { } - public static Map createPieces(Color color) { - if (color == WHITE) { + public static Map initialPieces(Color color) { + if (color.isWhite()) { return whitePieces(); } return blackPieces(); diff --git a/src/main/java/chess/domain/piece/type/PieceType.java b/src/main/java/chess/domain/piece/type/PieceType.java index e62961b..563f90e 100644 --- a/src/main/java/chess/domain/piece/type/PieceType.java +++ b/src/main/java/chess/domain/piece/type/PieceType.java @@ -1,7 +1,5 @@ package chess.domain.piece.type; -import chess.domain.piece.*; - import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -18,6 +16,7 @@ public enum PieceType { private static final Map, PieceType> PIECE_TYPE_MAP = createPieceTypeMap(); private static final int DUPLICATION_THRESHOLD = 1; private static final double PAWN_SCORE_ON_DUPLICATION = 0.5; + private final String name; private final double score; @@ -37,38 +36,40 @@ private static Map, PieceType> createPieceTypeMap() { return Collections.unmodifiableMap(map); } - public static PieceType of(Piece piece) { + public static PieceType of(final Piece piece) { return PIECE_TYPE_MAP.get(piece.getClass()); } - public static String findNameBy(Piece piece) { + public static String findNameBy(final Piece piece) { PieceType pieceType = PieceType.of(piece); if (piece.isWhite()) { - return pieceType.name; + return pieceType.name.toLowerCase(); } - return pieceType.name.toUpperCase(); } - public static double findScoreBy(Piece piece) { + public static double findScoreBy(final Piece piece) { PieceType pieceType = PieceType.of(piece); return pieceType.score; } - public static boolean isKing(Piece piece) { + public static boolean isKing(final Piece piece) { return PieceType.of(piece) == KING; } - public static boolean isPawn(Piece piece) { + public static boolean isPawn(final Piece piece) { return PieceType.of(piece) == PAWN; } + public static boolean isNotPawn(final Piece piece) { + return !isPawn(piece); + } + public static double sumPawnScores(final int count) { if (count > DUPLICATION_THRESHOLD) { return count * PAWN_SCORE_ON_DUPLICATION; } - return PAWN.score; } } diff --git a/src/main/java/chess/domain/piece/type/Queen.java b/src/main/java/chess/domain/piece/type/Queen.java new file mode 100644 index 0000000..c11b5bc --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Queen.java @@ -0,0 +1,11 @@ +package chess.domain.piece.type; + +import chess.domain.piece.move.MovePattern; +import chess.domain.player.Color; + +public class Queen extends Piece { + + public Queen(final Color color) { + super(MovePattern.QUEEN, color); + } +} diff --git a/src/main/java/chess/domain/piece/type/Rook.java b/src/main/java/chess/domain/piece/type/Rook.java new file mode 100644 index 0000000..ee0c373 --- /dev/null +++ b/src/main/java/chess/domain/piece/type/Rook.java @@ -0,0 +1,11 @@ +package chess.domain.piece.type; + +import chess.domain.piece.move.MovePattern; +import chess.domain.player.Color; + +public class Rook extends Piece { + + public Rook(final Color color) { + super(MovePattern.ROOK, color); + } +} diff --git a/src/main/java/chess/domain/player/AttackPositions.java b/src/main/java/chess/domain/player/AttackPositions.java deleted file mode 100644 index 5658acd..0000000 --- a/src/main/java/chess/domain/player/AttackPositions.java +++ /dev/null @@ -1,57 +0,0 @@ -package chess.domain.player; - -import chess.domain.board.Position; -import chess.domain.piece.Piece; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class AttackPositions { - - private static final int EMPTY = 0; - - private final Map counts; - - public AttackPositions(final Map pieces) { - this.counts = new HashMap<>(); - - pieces.keySet() - .forEach(position -> { - Piece piece = pieces.get(position); - Collection positions = piece.findAvailableAttackPositions(position); - positions.forEach(this::increase); - }); - } - - public void update(final Position source, final Position target, final Piece piece) { - remove(source, piece); - add(target, piece); - } - - public void remove(final Position position, final Piece piece) { - Collection previousAttackPositions = piece.findAvailableAttackPositions(position); - previousAttackPositions.forEach(this::decrease); - } - - private void add(final Position position, final Piece piece) { - Collection currentAttackPositions = piece.findAvailableAttackPositions(position); - currentAttackPositions.forEach(this::increase); - } - - private void decrease(final Position position) { - counts.put(position, counts.get(position) - 1); - } - - private void increase(final Position position) { - counts.put(position, counts.getOrDefault(position, 0) + 1); - } - - public boolean isEmpty(final Position position) { - return !counts.containsKey(position) || (counts.get(position) == EMPTY); - } - - public boolean contains(final Position position) { - return counts.containsKey(position); - } -} diff --git a/src/main/java/chess/domain/piece/Color.java b/src/main/java/chess/domain/player/Color.java similarity index 62% rename from src/main/java/chess/domain/piece/Color.java rename to src/main/java/chess/domain/player/Color.java index f38342f..2f88839 100644 --- a/src/main/java/chess/domain/piece/Color.java +++ b/src/main/java/chess/domain/player/Color.java @@ -1,4 +1,4 @@ -package chess.domain.piece; +package chess.domain.player; public enum Color { @@ -9,7 +9,11 @@ public boolean isWhite() { return this == WHITE; } - public Color nextTurn() { + public boolean isBlack() { + return this == BLACK; + } + + public Color flip() { if (isWhite()) { return BLACK; } diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/player/Player.java index 9b4a184..2596fe9 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/player/Player.java @@ -2,46 +2,53 @@ import chess.domain.board.File; import chess.domain.board.Position; -import chess.domain.piece.Color; -import chess.domain.piece.Piece; -import chess.domain.piece.PieceFactory; +import chess.domain.piece.move.Path; +import chess.domain.piece.type.Pawn; +import chess.domain.piece.type.Piece; +import chess.domain.piece.type.PieceFactory; import chess.domain.piece.type.PieceType; -import java.util.*; +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; import java.util.stream.Collectors; public class Player { private final Map pieces; - private final AttackPositions attackPositions; public Player(final Color color) { - pieces = new HashMap<>(PieceFactory.createPieces(color)); - attackPositions = new AttackPositions(pieces); + pieces = new HashMap<>(PieceFactory.initialPieces(color)); } - public boolean hasPieceOn(final Position position) { - return pieces.containsKey(position); - } - - public boolean hasNoPieceOn(final Position position) { - return !hasPieceOn(position); + public void move(final Position source, final Position target) { + Piece sourcePiece = findPieceBy(source); + pieces.remove(source); + pieces.put(target, sourcePiece); } - public Set findPaths(final Position source, final Position target) { + public Path findMovePath(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); - return sourcePiece.findPath(source, target); + return sourcePiece.findMovePath(source, target); } - public void move(final Position source, final Position target) { - Piece sourcePiece = findPieceBy(source); - movePiece(source, target, sourcePiece); - attackPositions.update(source, target, sourcePiece); + public Collection findAttackPaths(final Position target) { + return pieces.entrySet().stream() + .map(entry -> entry.getValue().findAttackPaths(entry.getKey())) + .flatMap(Collection::stream) + .filter(path -> path.contains(target)) + .map(path -> path.getPositionsUntilTarget(target)) + .filter(Path::isNotEmpty) + .collect(Collectors.toList()); } - private void movePiece(final Position source, final Position target, final Piece sourcePiece) { - pieces.put(target, sourcePiece); - pieces.remove(source); + public void wasAttackedBy(final Position position) { + if (hasNoPieceOn(position)) { + return; + } + + pieces.remove(position); } public Piece findPieceBy(final Position position) { @@ -52,67 +59,67 @@ public Piece findPieceBy(final Position position) { return pieces.get(position); } + public boolean hasPieceOn(final Position position) { + return pieces.containsKey(position); + } + + public boolean hasNoPieceOn(final Position position) { + return !hasPieceOn(position); + } + public boolean hasKingOn(final Position position) { - return PieceType.isKing(findPieceBy(position)); + Piece piece = findPieceBy(position); + return piece.isKing(); + } + + private boolean hasPawnOn(final Position position) { + Piece piece = findPieceBy(position); + return piece.isPawn(); } public boolean isKingDead() { return pieces.values().stream() - .noneMatch(PieceType::isKing); + .noneMatch(Piece::isKing); } public boolean isKingAlive() { return pieces.values().stream() - .anyMatch(PieceType::isKing); - } - - public boolean canAttack(final Position position) { - return attackPositions.contains(position); + .anyMatch(Piece::isKing); } - public void isUnderAttack(final Position position) { - if (hasNoPieceOn(position)) { - return; + public boolean isPawnAttacking(final Position source, final Position target) { + Piece piece = findPieceBy(source); + if (piece.isNotPawn()) { + return false; } - attackPositions.remove(position, pieces.get(position)); - pieces.remove(position); + Pawn pawn = (Pawn) piece; + return pawn.isAttacking(source, target); } public double calculateScores() { - List pawnPositions = findPawnPositions(); - - double pawnScores = calculatePawnScores(pawnPositions); + double pawnScores = calculatePawnScores(); double scoresExceptPawn = calculateScoresExceptPawn(); - return pawnScores + scoresExceptPawn; - } - private List findPawnPositions() { - return pieces.keySet() - .stream() - .filter(position -> { - Piece piece = pieces.get(position); - return PieceType.isPawn(piece); - }) - .collect(Collectors.toList()); + return pawnScores + scoresExceptPawn; } - private double calculatePawnScores(final List pawnPositions) { + private double calculatePawnScores() { Map pawnCount = new EnumMap<>(File.class); - pawnPositions.stream() + + pieces.keySet().stream() + .filter(this::hasPawnOn) .map(Position::getFile) .forEach(file -> pawnCount.put(file, pawnCount.getOrDefault(file, 0) + 1)); - return pawnCount.values() - .stream() + return pawnCount.values().stream() .mapToDouble(PieceType::sumPawnScores) .sum(); } private double calculateScoresExceptPawn() { - return pieces.values() - .stream() - .filter(piece -> !PieceType.isPawn(piece)) + return pieces.values().stream() + .filter(PieceType::isNotPawn) .mapToDouble(PieceType::findScoreBy) .sum(); } diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index 1cb8533..44190d1 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -1,9 +1,10 @@ package chess.dto; import chess.domain.board.Board; -import chess.domain.piece.Piece; -import chess.domain.piece.type.PieceType; import chess.domain.board.Position; +import chess.domain.piece.type.Piece; +import chess.domain.piece.type.PieceType; +import chess.exception.EmptyPositionException; import java.util.HashMap; import java.util.Map; @@ -18,11 +19,10 @@ private BoardDto() { public static Map of(Board board) { Map pieceOnPositions = new HashMap<>(); - Position.names() - .forEach(positionKey -> { - String pieceName = findPieceName(positionKey, board); - pieceOnPositions.put(positionKey, pieceName); - }); + Position.names().forEach(positionKey -> { + String pieceName = findPieceName(positionKey, board); + pieceOnPositions.put(positionKey, pieceName); + }); return pieceOnPositions; } @@ -30,11 +30,11 @@ public static Map of(Board board) { private static String findPieceName(String positionKey, Board board) { Position position = Position.of(positionKey); - if (board.isEmpty(position)) { + try { + Piece piece = board.findBy(position); + return PieceType.findNameBy(piece); + } catch (EmptyPositionException e) { return EMPTY_PIECE; } - - Piece piece = board.findBy(position); - return PieceType.findNameBy(piece); } } diff --git a/src/main/java/chess/exception/EmptyPositionException.java b/src/main/java/chess/exception/EmptyPositionException.java new file mode 100644 index 0000000..debb3de --- /dev/null +++ b/src/main/java/chess/exception/EmptyPositionException.java @@ -0,0 +1,4 @@ +package chess.exception; + +public class EmptyPositionException extends RuntimeException { +} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 1b7ffe9..71ed65a 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,10 +1,10 @@ package chess.service; import chess.domain.board.Board; -import chess.domain.player.Scores; import chess.domain.command.Command; import chess.domain.command.MoveParameters; -import chess.domain.piece.Color; +import chess.domain.player.Color; +import chess.domain.player.Scores; import chess.dto.BoardDto; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; @@ -42,6 +42,10 @@ public boolean isGameRunning() { return board.isBothKingAlive(); } + public boolean isGameFinished() { + return !isGameRunning(); + } + public void movePiece(MoveParameters parameters) { board.move(parameters); } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index bd9e336..dd00394 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -1,44 +1,33 @@ package chess.domain.board; import chess.domain.command.MoveParameters; +import chess.domain.piece.type.Pawn; +import chess.domain.piece.type.Piece; +import chess.domain.piece.type.Rook; +import chess.domain.player.Color; import chess.domain.player.Scores; +import chess.exception.EmptyPositionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; public class BoardTest { - @Test - @DisplayName("객체를 생성한다.") - void create() { - //given - Position pawnPosition = Position.of("b2"); - Position emptyPosition = Position.of("b3"); - - //when - Board board = new Board(); - - //then - assertThat(board.isEmpty(pawnPosition)).isFalse(); - assertThat(board.isEmpty(emptyPosition)).isTrue(); - } - @ParameterizedTest @CsvSource({"b3, b4, 자신의 기물만 움직일 수 있습니다.", // 빈칸 "a7, a6, 자신의 기물만 움직일 수 있습니다.", // 적 기물 "a1, a2, 자신의 기물이 있는 곳으로 이동할 수 없습니다.", "a1, a1, 출발 위치와 도착 위치가 같을 수 없습니다.", - "a1, a3, 다른 기물을 통과하여 이동할 수 없습니다."}) + "a1, a3, 다른 기물을 통과하여 이동할 수 없습니다.", + "c2, d3, 폰은 공격 대상이 있는 경우에만 대각선으로 이동할 수 있습니다."}) @DisplayName("이동할 수 없는 경우 예외가 발생한다.") - void move_exception(String sourcePosition, String targetPosition, String exceptionMessage) { + void move_exception(String source, String target, String exceptionMessage) { //given Board board = new Board(); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); MoveParameters moveParameters = new MoveParameters(source, target); //when, then @@ -52,8 +41,14 @@ void move_exception(String sourcePosition, String targetPosition, String excepti @DisplayName("상대방이 킹의 목적지를 공격 가능한 경우 예외가 발생한다.") void move_king_invalid_target(String source, String target) { //given - Board board = setBoardToAttackKing(); - MoveParameters moveParameters = new MoveParameters(Position.of(source), Position.of(target)); + Board board = new Board(); + board.move(new MoveParameters("e2", "e4")); + board.move(new MoveParameters("c7", "c5")); + board.move(new MoveParameters("d2", "d4")); + board.move(new MoveParameters("d8", "a5")); + board.move(new MoveParameters("e1", "e2")); + board.move(new MoveParameters("h7", "h5")); + MoveParameters moveParameters = new MoveParameters(source, target); //when, then assertThatIllegalArgumentException() @@ -62,40 +57,105 @@ void move_king_invalid_target(String source, String target) { } @Test - @DisplayName("모든 플레이어의 점수를 반환한다.") - void get_status() { + @DisplayName("위치에 있는 기물을 반환한다.") + void find_by() { //given - Board board = setBoardToGetStatus(); + Position whitePawnPosition = Position.of("d2"); + Position blackRookPosition = Position.of("a8"); + + //when + Board board = new Board(); + Piece whitePawn = board.findBy(whitePawnPosition); + Piece blackRook = board.findBy(blackRookPosition); + + //then + assertThat(whitePawn).isInstanceOf(Pawn.class); + assertThat(whitePawn.isWhite()).isTrue(); + assertThat(blackRook).isInstanceOf(Rook.class); + assertThat(blackRook.isWhite()).isFalse(); + } + + @Test + @DisplayName("위치에 기물이 존재하지 않으면 예외를 던진다.") + void find_by_empty() { + //given + Position emptyPosition = Position.of("d5"); + + //when + Board board = new Board(); + + //then + assertThrows(EmptyPositionException.class, () -> board.findBy(emptyPosition)); + } + + @Test + @DisplayName("각 플레이어의 점수를 계산한다.") + void get_scores() { + //given + Board board = new Board(); + board.move(new MoveParameters("a2", "a3")); + board.move(new MoveParameters("e7", "e5")); + board.move(new MoveParameters("d2", "d4")); + board.move(new MoveParameters("e5", "e4")); + board.move(new MoveParameters("b2", "b3")); + board.move(new MoveParameters("a7", "a6")); + board.move(new MoveParameters("f2", "f3")); + board.move(new MoveParameters("e4", "f3")); //when Scores scores = board.getScores(); //then - assertThat(scores.getWhiteScore()).isEqualTo(36); + assertThat(scores.getWhiteScore()).isEqualTo(37); assertThat(scores.getBlackScore()).isEqualTo(37); } - private Board setBoardToGetStatus() { + @Test + @DisplayName("흑팀 킹이 죽으면 백팀을 승자로 반환한다.") + void get_winner_white() { + //given + Board board = new Board(); + board.move(new MoveParameters("e2", "e4")); + board.move(new MoveParameters("f7", "f5")); + board.move(new MoveParameters("d1", "h5")); + board.move(new MoveParameters("a7", "a5")); + board.move(new MoveParameters("h5", "e8")); + + //when + Color winner = board.getWinner(); + + //then + assertThat(winner.isWhite()).isTrue(); + } + + @Test + @DisplayName("백팀 킹이 죽으면 흑팀을 승자로 반환한다.") + void get_winner_black() { + //given Board board = new Board(); - board.move(new MoveParameters(Position.of("a2"), Position.of("a3"))); - board.move(new MoveParameters(Position.of("e7"), Position.of("e5"))); - board.move(new MoveParameters(Position.of("d2"), Position.of("e3"))); - board.move(new MoveParameters(Position.of("e5"), Position.of("e4"))); - board.move(new MoveParameters(Position.of("b2"), Position.of("b3"))); - board.move(new MoveParameters(Position.of("a7"), Position.of("a6"))); - board.move(new MoveParameters(Position.of("f2"), Position.of("f3"))); - board.move(new MoveParameters(Position.of("e4"), Position.of("f3"))); - return board; + board.move(new MoveParameters("f2", "f4")); + board.move(new MoveParameters("e7", "e5")); + board.move(new MoveParameters("a2", "a4")); + board.move(new MoveParameters("d8", "h4")); + board.move(new MoveParameters("b2", "b4")); + board.move(new MoveParameters("h4", "e1")); + + //when + Color winner = board.getWinner(); + + //then + assertThat(winner.isBlack()).isTrue(); } - private Board setBoardToAttackKing() { + @Test + @DisplayName("흑백 킹이 모두 살아있는 경우 승자를 요청하면 예외를 반환한다.") + void get_winner_exception() { + //given Board board = new Board(); - board.move(new MoveParameters(Position.of("e2"), Position.of("e4"))); - board.move(new MoveParameters(Position.of("c7"), Position.of("c5"))); - board.move(new MoveParameters(Position.of("d2"), Position.of("d4"))); - board.move(new MoveParameters(Position.of("d8"), Position.of("a5"))); - board.move(new MoveParameters(Position.of("e1"), Position.of("e2"))); - board.move(new MoveParameters(Position.of("h7"), Position.of("h5"))); - return board; + + //when, then + assertThatIllegalStateException() + .isThrownBy(board::getWinner) + .withMessage("King이 잡히지 않아 승자가 없습니다."); } } diff --git a/src/test/java/chess/domain/board/PositionTest.java b/src/test/java/chess/domain/board/PositionTest.java new file mode 100644 index 0000000..f23d641 --- /dev/null +++ b/src/test/java/chess/domain/board/PositionTest.java @@ -0,0 +1,146 @@ +package chess.domain.board; + +import chess.domain.piece.move.Gap; +import chess.domain.piece.move.MoveUnit; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class PositionTest { + + @Test + @DisplayName("가로, 세로 인자에 해당하는 위치를 반환한다.") + void from_file_and_rank() { + //given + File file = File.a; + Rank rank = Rank.R1; + + //when + Position position = Position.from(file, rank); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } + + @Test + @DisplayName("문자열 키에 해당하는 위치를 반환한다.") + void of_valid_key() { + //given + File file = File.a; + Rank rank = Rank.R1; + String key = file.name() + rank.getIndex(); + + //when + Position position = Position.of(key); + + //then + assertThat(position).extracting("file", "rank") + .containsOnly(file, rank); + } + + @ParameterizedTest + @ValueSource(strings = {"a0", "j1"}) + @DisplayName("유효하지 않은 키로 검색하면 예외를 던진다.") + void of_invalid_key(String key) { + //given, when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> Position.of(key)) + .withMessage("위치 입력값이 잘못되었습니다."); + } + + @Test + @DisplayName("모든 위치 이름을 반환한다.") + void names() { + //given, when + Collection names = Position.names(); + + //then + assertThat(names).hasSize(64); + } + + @Test + @DisplayName("두 위치가 주어지면 좌표 차이(target-source)를 계산한다.") + void calculate_gap() { + // given + Position source = Position.of("a1"); + Position target = Position.of("f3"); + + // when + Gap gap = target.calculateGap(source); + + // then + assertThat(gap.file()).isEqualTo(5); + assertThat(gap.rank()).isEqualTo(2); + } + + @Test + @DisplayName("거쳐가는 모든 위치를 반환한다.") + void find_passing_positions() { + // given + Position source = Position.of("d4"); + Position target = Position.of("d7"); + MoveUnit moveUnit = MoveUnit.NORTH; + + // when + Collection positions = source.findPassingPositions(target, moveUnit); + + // then + assertThat(positions) + .hasSize(2) + .containsExactlyInAnyOrder(Position.of("d5"), Position.of("d6")); + } + + @Test + @DisplayName("이동 가능한 모든 위치를 반환한다.") + void find_reachable_positions() { + // given + Position source = Position.of("d4"); + MoveUnit moveUnit = MoveUnit.SOUTH; + List expected = Arrays.asList(Position.of("d3"), Position.of("d2"), Position.of("d1")); + + // when + List reachablePositions = source.findReachablePositions(moveUnit); + + // then + assertThat(reachablePositions).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource({"d4, NORTH, true", "h2, EAST, false"}) + @DisplayName("주어진 좌표만큼 이동 가능한지 확인한다.") + void is_movable(String source, MoveUnit moveUnit, boolean expected) { + // given + Position position = Position.of(source); + + // when + boolean movable = position.isMovable(moveUnit); + + // then + assertThat(movable).isSameAs(expected); + } + + @ParameterizedTest + @CsvSource({"a2, NORTH, a3", "b7, SOUTH_WEST, a6", "e5, SOUTH_EAST_LEFT, g4"}) + @DisplayName("주어진 좌표만큼 이동한 위치를 반환한다.") + void move(String sourceKey, MoveUnit moveUnit, String targetKey) { + // given + Position source = Position.of(sourceKey); + Position target = Position.of(targetKey); + + // when + Position result = source.move(moveUnit); + + // then + assertThat(result).isSameAs(target); + } +} diff --git a/src/test/java/chess/domain/piece/PieceTest.java b/src/test/java/chess/domain/piece/PieceTest.java deleted file mode 100644 index e4659fe..0000000 --- a/src/test/java/chess/domain/piece/PieceTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package chess.domain.piece; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PieceTest { - - @ParameterizedTest - @CsvSource({"WHITE, true", "BLACK, false"}) - @DisplayName("색상을 인자로 받아 객체를 생성한다.") - void create(Color color, boolean expected) { - //given, when - Piece piece = new Pawn(color); - - //then - assertThat(piece.isWhite()).isEqualTo(expected); - } -} diff --git a/src/test/java/chess/domain/piece/RookTest.java b/src/test/java/chess/domain/piece/RookTest.java deleted file mode 100644 index 0b3e482..0000000 --- a/src/test/java/chess/domain/piece/RookTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package chess.domain.piece; - -import chess.domain.board.File; -import chess.domain.board.Rank; -import chess.domain.board.Position; -import org.assertj.core.groups.Tuple; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Set; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.groups.Tuple.tuple; - -public class RookTest { - - @ParameterizedTest - @MethodSource("createParameters") - @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success(String targetPosition, Tuple expected) { - //given - Position source = Position.of("d4"); - Position target = Position.of(targetPosition); - Piece piece = new Rook(Color.WHITE); - - //when - Set paths = piece.findPath(source, target); - - //then - assertThat(paths).extracting("file", "rank") - .containsOnly(expected); - } - - @Test - @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") - void find_paths_invalid_target() { - //given - Position source = Position.of("c1"); - Position target = Position.of("f5"); - Piece piece = new Rook(Color.WHITE); - - //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); - } - - @Test - @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") - void find_available_attack_positions() { - //given - Position position = Position.of("d4"); - Piece rook = new Rook(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("d1"), Position.of("d2"), Position.of("d3"), Position.of("d5"), - Position.of("d6"), Position.of("d7"), Position.of("d8"), - Position.of("a4"), Position.of("b4"), Position.of("c4"), Position.of("e4"), - Position.of("f4"), Position.of("g4"), Position.of("h4") - ); - - //when - Collection availableAttackPositions = rook.findAvailableAttackPositions(position); - - //then - assertThat(availableAttackPositions) - .containsAll(expected); - } - - private static Stream createParameters() { - return Stream.of( - Arguments.of("d2", tuple(File.d, Rank.R3)), - Arguments.of("d6", tuple(File.d, Rank.R5)), - Arguments.of("b4", tuple(File.c, Rank.R4)), - Arguments.of("f4", tuple(File.e, Rank.R4)) - ); - } -} diff --git a/src/test/java/chess/domain/piece/move/MoveUnitsTest.java b/src/test/java/chess/domain/piece/move/MoveUnitsTest.java new file mode 100644 index 0000000..8024710 --- /dev/null +++ b/src/test/java/chess/domain/piece/move/MoveUnitsTest.java @@ -0,0 +1,38 @@ +package chess.domain.piece.move; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static chess.domain.piece.move.MoveUnit.NORTH_EAST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class MoveUnitsTest { + + @Test + @DisplayName("매칭되는 이동 단위를 반환한다.") + void find_match_move_unit() { + // given + MoveUnits moveUnits = MoveUnits.DIAGONAL; + Gap gap = new Gap(2, 2); + + // when + MoveUnit moveUnit = moveUnits.findMatchMoveUnit(gap, MoveLimit.INFINITE); + + // then + assertThat(moveUnit).isSameAs(NORTH_EAST); + } + + @Test + @DisplayName("매칭되는 이동 단위가 없는 경우 예외를 던진다.") + void find_match_move_unit_exception() { + // given + MoveUnits moveUnits = MoveUnits.KNIGHT; + Gap gap = new Gap(2, 4); + + // when, then + assertThatIllegalArgumentException() + .isThrownBy(() -> moveUnits.findMatchMoveUnit(gap, MoveLimit.FINITE)) + .withMessage("해당 방향으로 이동할 수 없습니다."); + } +} \ No newline at end of file diff --git a/src/test/java/chess/domain/piece/move/PathTest.java b/src/test/java/chess/domain/piece/move/PathTest.java new file mode 100644 index 0000000..dbef426 --- /dev/null +++ b/src/test/java/chess/domain/piece/move/PathTest.java @@ -0,0 +1,43 @@ +package chess.domain.piece.move; + +import chess.domain.board.Position; +import chess.domain.player.Color; +import chess.domain.player.Player; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PathTest { + + @Test + @DisplayName("주어진 경로에서 타겟 이전까지의 경로만 반환한다.") + void get_positions_until_target() { + // given + Path path = new Path(Position.of("d1"), Position.of("d2"), Position.of("d3"), Position.of("d4")); + Position target = Position.of("d3"); + Path expected = new Path(Position.of("d1"), Position.of("d2")); + + // when + Path positionsUntilTarget = path.getPositionsUntilTarget(target); + + // then + assertThat(positionsUntilTarget).isEqualTo(expected); + } + + @Test + @DisplayName("경로가 플레이어의 기물로 막혀있는지 확인한다.") + void is_blocked_by() { + // given + Path path = new Path(Position.of("c4"), Position.of("c3"), Position.of("c2")); + Player player = new Player(Color.WHITE); + + // when + boolean blockedBy = path.isBlockedBy(player); + boolean notBlockedBy = path.isNotBlockedBy(player); + + // then + assertThat(blockedBy).isTrue(); + assertThat(notBlockedBy).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/chess/domain/piece/BishopTest.java b/src/test/java/chess/domain/piece/type/BishopTest.java similarity index 50% rename from src/test/java/chess/domain/piece/BishopTest.java rename to src/test/java/chess/domain/piece/type/BishopTest.java index d29fedf..2addfd4 100644 --- a/src/test/java/chess/domain/piece/BishopTest.java +++ b/src/test/java/chess/domain/piece/type/BishopTest.java @@ -1,41 +1,36 @@ -package chess.domain.piece; +package chess.domain.piece.type; -import chess.domain.board.File; -import chess.domain.board.Rank; import chess.domain.board.Position; -import org.assertj.core.groups.Tuple; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.CsvSource; import java.util.Arrays; import java.util.Collection; -import java.util.Set; -import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.groups.Tuple.tuple; class BishopTest { @ParameterizedTest - @MethodSource("createParameters") + @CsvSource({"b6, c5", "b2, c3", "f2, e3", "f6, e5"}) @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success(String targetPosition, Tuple expected) { + void find_paths_success(String targetPosition, String expectedPosition) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new Bishop(Color.WHITE); + Path expected = new Path(Position.of(expectedPosition)); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).extracting("file", "rank") - .containsOnly(expected); + assertThat(path).isEqualTo(expected); } @Test @@ -47,7 +42,7 @@ void find_paths_invalid_target() { Piece piece = new Bishop(Color.WHITE); //when //then - assertThatIllegalArgumentException().isThrownBy(() -> piece.findPath(source, target)); + assertThatIllegalArgumentException().isThrownBy(() -> piece.findMovePath(source, target)); } @Test @@ -56,28 +51,19 @@ void find_available_attack_positions() { //given Position position = Position.of("d4"); Piece bishop = new Bishop(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("a1"), Position.of("b2"), Position.of("c3"), Position.of("e5"), - Position.of("a7"), Position.of("b6"), Position.of("c5"), Position.of("e3"), - Position.of("f6"), Position.of("g7"), Position.of("h8"), - Position.of("f2"), Position.of("g1") + Collection expected = Arrays.asList( + new Path(Position.of("c3"), Position.of("b2"), Position.of("a1")), + new Path(Position.of("e5"), Position.of("f6"), Position.of("g7"), Position.of("h8")), + new Path(Position.of("c5"), Position.of("b6"), Position.of("a7")), + new Path(Position.of("e3"), Position.of("f2"), Position.of("g1")) ); //when - Collection availableAttackPositions = bishop.findAvailableAttackPositions(position); + Collection availableAttackPaths = bishop.findAttackPaths(position); //then - assertThat(availableAttackPositions) + assertThat(availableAttackPaths) .hasSize(expected.size()) .containsAll(expected); } - - private static Stream createParameters() { - return Stream.of( - Arguments.of("b6", tuple(File.c, Rank.R5)), - Arguments.of("b2", tuple(File.c, Rank.R3)), - Arguments.of("f2", tuple(File.e, Rank.R3)), - Arguments.of("f6", tuple(File.e, Rank.R5)) - ); - } } diff --git a/src/test/java/chess/domain/piece/KingTest.java b/src/test/java/chess/domain/piece/type/KingTest.java similarity index 67% rename from src/test/java/chess/domain/piece/KingTest.java rename to src/test/java/chess/domain/piece/type/KingTest.java index 240d103..55b4319 100644 --- a/src/test/java/chess/domain/piece/KingTest.java +++ b/src/test/java/chess/domain/piece/type/KingTest.java @@ -1,6 +1,8 @@ -package chess.domain.piece; +package chess.domain.piece.type; import chess.domain.board.Position; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -8,7 +10,7 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Set; +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -23,12 +25,13 @@ void find_paths_success(String targetPosition) { Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new King(Color.WHITE); + Path expected = new Path(Collections.emptyList()); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).isEmpty(); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @@ -40,9 +43,9 @@ void find_paths_invalid_target(String invalidTarget) { Position target = Position.of(invalidTarget); Piece piece = new King(Color.WHITE); - //when //then + //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @Test @@ -51,17 +54,17 @@ void find_available_attack_positions() { //given Position position = Position.of("d4"); Piece king = new King(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("d3"), Position.of("d5"), - Position.of("c3"), Position.of("c4"), Position.of("c5"), - Position.of("e3"), Position.of("e4"), Position.of("e5") + Collection expected = Arrays.asList( + new Path(Position.of("d3")), new Path(Position.of("d5")), + new Path(Position.of("c3")), new Path(Position.of("c4")), new Path(Position.of("c5")), + new Path(Position.of("e3")), new Path(Position.of("e4")), new Path(Position.of("e5")) ); //when - Collection availableAttackPositions = king.findAvailableAttackPositions(position); + Collection availableAttackPaths = king.findAttackPaths(position); //then - assertThat(availableAttackPositions) + assertThat(availableAttackPaths) .hasSize(expected.size()) .containsAll(expected); } diff --git a/src/test/java/chess/domain/piece/KnightTest.java b/src/test/java/chess/domain/piece/type/KnightTest.java similarity index 70% rename from src/test/java/chess/domain/piece/KnightTest.java rename to src/test/java/chess/domain/piece/type/KnightTest.java index 1f26c41..f15bd8f 100644 --- a/src/test/java/chess/domain/piece/KnightTest.java +++ b/src/test/java/chess/domain/piece/type/KnightTest.java @@ -1,6 +1,8 @@ -package chess.domain.piece; +package chess.domain.piece.type; import chess.domain.board.Position; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -8,7 +10,7 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Set; +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -23,12 +25,13 @@ void find_paths_success(String targetPosition) { Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new Knight(Color.WHITE); + Path expected = new Path(Collections.emptyList()); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).isEmpty(); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @@ -42,7 +45,7 @@ void find_paths_invalid_target(String invalidTarget) { //when //then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @Test @@ -51,13 +54,13 @@ void find_available_attack_positions() { //given Position position = Position.of("d4"); Piece knight = new Knight(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("c6"), Position.of("c2"), Position.of("e6"), Position.of("e2"), - Position.of("b5"), Position.of("b3"), Position.of("f5"), Position.of("f3") + Collection expected = Arrays.asList( + new Path(Position.of("c6")), new Path(Position.of("c2")), new Path(Position.of("e6")), new Path(Position.of("e2")), + new Path(Position.of("b5")), new Path(Position.of("b3")), new Path(Position.of("f5")), new Path(Position.of("f3")) ); //when - Collection availableAttackPositions = knight.findAvailableAttackPositions(position); + Collection availableAttackPositions = knight.findAttackPaths(position); //then assertThat(availableAttackPositions) diff --git a/src/test/java/chess/domain/piece/type/MovePatternTest.java b/src/test/java/chess/domain/piece/type/MovePatternTest.java deleted file mode 100644 index 20809be..0000000 --- a/src/test/java/chess/domain/piece/type/MovePatternTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package chess.domain.piece.type; - -import chess.domain.piece.Color; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.Collection; - -import static chess.domain.piece.type.MoveUnit.*; -import static org.assertj.core.api.Assertions.assertThat; - -class MovePatternTest { - - @Test - @DisplayName("색상에 따라 폰 패턴을 반환한다.") - void pawn_pattern_black() { - // given - Color color = Color.BLACK; - - // when - MovePattern movePattern = MovePattern.pawnPattern(color); - Collection moveUnits = movePattern.finiteMoveUnits(); - - // then - assertThat(moveUnits) - .containsExactlyInAnyOrder(BLACK_PAWN_INITIAL_SOUTH, SOUTH_EAST, SOUTH_WEST, SOUTH); - } - - @Test - @DisplayName("색상에 따라 폰 패턴을 반환한다.") - void pawn_pattern_white() { - // given - Color color = Color.WHITE; - - // when - MovePattern movePattern = MovePattern.pawnPattern(color); - Collection moveUnits = movePattern.finiteMoveUnits(); - - // then - assertThat(moveUnits) - .containsExactlyInAnyOrder(WHITE_PAWN_INITIAL_NORTH, NORTH_EAST, NORTH_WEST, NORTH); - } -} diff --git a/src/test/java/chess/domain/piece/PawnTest.java b/src/test/java/chess/domain/piece/type/PawnTest.java similarity index 76% rename from src/test/java/chess/domain/piece/PawnTest.java rename to src/test/java/chess/domain/piece/type/PawnTest.java index 13e79a6..64addec 100644 --- a/src/test/java/chess/domain/piece/PawnTest.java +++ b/src/test/java/chess/domain/piece/type/PawnTest.java @@ -1,6 +1,8 @@ -package chess.domain.piece; +package chess.domain.piece.type; import chess.domain.board.Position; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -8,7 +10,7 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Set; +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -23,28 +25,30 @@ void find_paths_success_move_count_one_on_initial_move(String sourcePosition, St Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); Piece piece = new Pawn(color); + Path expected = new Path(Collections.emptyList()); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).isEmpty(); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @CsvSource({"b2, b4, WHITE, b3", "b7, b5, BLACK, b6"}) @DisplayName("최초 이동시 2칸 전진하면 지나가는 경로를 반환한다.") - void find_paths_success_move_count_two_on_initial_move(String sourcePosition, String targetPosition, Color color, String expected) { + void find_paths_success_move_count_two_on_initial_move(String sourcePosition, String targetPosition, Color color, String expectedPosition) { //given Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); Piece piece = new Pawn(color); + Path expected = new Path(Position.of(expectedPosition)); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).containsOnly(Position.of(expected)); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @@ -56,12 +60,13 @@ void find_paths_success_move_count_one(String sourcePosition, String targetPosit Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); Piece piece = new Pawn(color); + Path expected = new Path(Collections.emptyList()); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).isEmpty(); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @@ -73,9 +78,9 @@ void find_paths_fail_move_invalid_count_on_initial_move(String sourcePosition, S Position target = Position.of(targetPosition); Piece piece = new Pawn(color); - //when //then + //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @ParameterizedTest @@ -87,9 +92,9 @@ void find_paths_fail_move_invalid_count(String sourcePosition, String targetPosi Position target = Position.of(targetPosition); Piece piece = new Pawn(color); - //when //then + //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @ParameterizedTest @@ -103,7 +108,7 @@ void find_paths_fail_move_backward(String sourcePosition, String targetPosition, //when //then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @Test @@ -112,15 +117,15 @@ void find_available_attack_positions() { //given Position position = Position.of("d4"); Piece pawn = new Pawn(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("c5"), Position.of("e5") + Collection expected = Arrays.asList( + new Path(Position.of("c5")), new Path(Position.of("e5")) ); //when - Collection availableAttackPositions = pawn.findAvailableAttackPositions(position); + Collection availableAttackPaths = pawn.findAttackPaths(position); //then - assertThat(availableAttackPositions) + assertThat(availableAttackPaths) .hasSize(expected.size()) .containsAll(expected); } diff --git a/src/test/java/chess/domain/piece/type/PieceTest.java b/src/test/java/chess/domain/piece/type/PieceTest.java new file mode 100644 index 0000000..b3c8440 --- /dev/null +++ b/src/test/java/chess/domain/piece/type/PieceTest.java @@ -0,0 +1,55 @@ +package chess.domain.piece.type; + +import chess.domain.player.Color; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PieceTest { + + @ParameterizedTest + @CsvSource({"WHITE, true", "BLACK, false"}) + @DisplayName("색상을 인자로 받아 객체를 생성한다.") + void create(Color color, boolean expected) { + //given, when + Piece piece = new Pawn(color); + + //then + assertThat(piece.isWhite()).isEqualTo(expected); + } + + @Test + @DisplayName("폰인지 확인한다.") + void is_pawn() { + // given + Piece pawn = new Pawn(Color.WHITE); + Piece rook = new Rook(Color.BLACK); + + // when + boolean isPawn = pawn.isPawn(); + boolean isNotPawn = rook.isNotPawn(); + + // then + assertThat(isPawn).isTrue(); + assertThat(isNotPawn).isTrue(); + } + + @Test + @DisplayName("킹인지 확인한다.") + void is_king() { + // given + Piece pawn = new Pawn(Color.WHITE); + Piece king = new King(Color.BLACK); + + // when + boolean isKing = king.isKing(); + boolean isNotKing = pawn.isKing(); + + // then + assertThat(isKing).isTrue(); + assertThat(isNotKing).isFalse(); + } +} diff --git a/src/test/java/chess/domain/piece/type/PieceTypeTest.java b/src/test/java/chess/domain/piece/type/PieceTypeTest.java index 8c1acd7..6964ea2 100644 --- a/src/test/java/chess/domain/piece/type/PieceTypeTest.java +++ b/src/test/java/chess/domain/piece/type/PieceTypeTest.java @@ -1,6 +1,6 @@ package chess.domain.piece.type; -import chess.domain.piece.*; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/QueenTest.java b/src/test/java/chess/domain/piece/type/QueenTest.java similarity index 51% rename from src/test/java/chess/domain/piece/QueenTest.java rename to src/test/java/chess/domain/piece/type/QueenTest.java index 774517f..cdfc1a5 100644 --- a/src/test/java/chess/domain/piece/QueenTest.java +++ b/src/test/java/chess/domain/piece/type/QueenTest.java @@ -1,42 +1,38 @@ -package chess.domain.piece; +package chess.domain.piece.type; -import chess.domain.board.File; -import chess.domain.board.Rank; import chess.domain.board.Position; -import org.assertj.core.groups.Tuple; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import java.util.Arrays; import java.util.Collection; -import java.util.Set; -import java.util.stream.Stream; +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.groups.Tuple.tuple; class QueenTest { @ParameterizedTest - @MethodSource("createParametersForDiagonal") + @CsvSource({"b6, c5", "b2, c3", "f2, e3", "f6, e5"}) @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") - void find_paths_success_diagonal(String targetPosition, Tuple expected) { + void find_paths_success_diagonal(String targetPosition, String expectedPosition) { //given Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new Queen(Color.WHITE); + Path expected = new Path(Position.of(expectedPosition)); //when - Set paths = piece.findPath(source, target); + Path path = piece.findMovePath(source, target); //then - assertThat(paths).extracting("file", "rank") - .containsOnly(expected); + assertThat(path).isEqualTo(expected); } @ParameterizedTest @@ -47,12 +43,13 @@ void find_paths_success_cardinal(String targetPosition) { Position source = Position.of("d4"); Position target = Position.of(targetPosition); Piece piece = new Queen(Color.WHITE); + Path expected = new Path(Collections.emptyList()); //when - Set paths = piece.findPath(source, target); + Path paths = piece.findMovePath(source, target); //then - assertThat(paths).isEmpty(); + assertThat(paths).isEqualTo(expected); } @ParameterizedTest @@ -66,7 +63,7 @@ void find_paths_invalid_target(String invalidTarget) { //when //then assertThatIllegalArgumentException() - .isThrownBy(() -> piece.findPath(source, target)); + .isThrownBy(() -> piece.findMovePath(source, target)); } @Test @@ -75,28 +72,25 @@ void find_available_attack_positions() { //given Position position = Position.of("d4"); Piece queen = new Queen(Color.WHITE); - Collection expected = Arrays.asList( - Position.of("a1"), Position.of("b2"), Position.of("c3"), Position.of("e5"), Position.of("f6"), Position.of("g7"), Position.of("h8"), - Position.of("a7"), Position.of("b6"), Position.of("c5"), Position.of("e3"), Position.of("f2"), Position.of("g1"), - Position.of("d1"), Position.of("d2"), Position.of("d3"), Position.of("d5"), Position.of("d6"), Position.of("d7"), Position.of("d8"), - Position.of("a4"), Position.of("b4"), Position.of("c4"), Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4") + Collection expected = Arrays.asList( + // diagonal + new Path(Position.of("c3"), Position.of("b2"), Position.of("a1")), + new Path(Position.of("e5"), Position.of("f6"), Position.of("g7"), Position.of("h8")), + new Path(Position.of("c5"), Position.of("b6"), Position.of("a7")), + new Path(Position.of("e3"), Position.of("f2"), Position.of("g1")), + // cardinal + new Path(Position.of("d3"), Position.of("d2"), Position.of("d1")), + new Path(Position.of("d5"), Position.of("d6"), Position.of("d7"), Position.of("d8")), + new Path(Position.of("c4"), Position.of("b4"), Position.of("a4")), + new Path(Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4")) ); //when - Collection availableAttackPositions = queen.findAvailableAttackPositions(position); + Collection availableAttackPaths = queen.findAttackPaths(position); //then - assertThat(availableAttackPositions) + assertThat(availableAttackPaths) .hasSize(expected.size()) .containsAll(expected); } - - private static Stream createParametersForDiagonal() { - return Stream.of( - Arguments.of("b6", tuple(File.c, Rank.R5)), - Arguments.of("b2", tuple(File.c, Rank.R3)), - Arguments.of("f2", tuple(File.e, Rank.R3)), - Arguments.of("f6", tuple(File.e, Rank.R5)) - ); - } } diff --git a/src/test/java/chess/domain/piece/type/RookTest.java b/src/test/java/chess/domain/piece/type/RookTest.java new file mode 100644 index 0000000..833a95a --- /dev/null +++ b/src/test/java/chess/domain/piece/type/RookTest.java @@ -0,0 +1,69 @@ +package chess.domain.piece.type; + +import chess.domain.board.Position; +import chess.domain.piece.move.Path; +import chess.domain.player.Color; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.Arrays; +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class RookTest { + + @ParameterizedTest + @CsvSource({"d2, d3", "d6, d5", "b4, c4", "f4, e4"}) + @DisplayName("출발과 도착 위치가 주어지면 지나가는 경로를 반환한다.") + void find_paths_success(String targetPosition, String expectedPosition) { + //given + Position source = Position.of("d4"); + Position target = Position.of(targetPosition); + Piece piece = new Rook(Color.WHITE); + Path expected = new Path(Position.of(expectedPosition)); + + //when + Path path = piece.findMovePath(source, target); + + //then + assertThat(path).isEqualTo(expected); + } + + @Test + @DisplayName("도착 위치가 이동할 수 없는 경로일 경우 예외가 발생한다.") + void find_paths_invalid_target() { + //given + Position source = Position.of("c1"); + Position target = Position.of("f5"); + Piece piece = new Rook(Color.WHITE); + + //when //then + assertThatIllegalArgumentException().isThrownBy(() -> piece.findMovePath(source, target)); + } + + @Test + @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") + void find_available_attack_positions() { + //given + Position position = Position.of("d4"); + Piece rook = new Rook(Color.WHITE); + Collection expected = Arrays.asList( + new Path(Position.of("d3"), Position.of("d2"), Position.of("d1")), + new Path(Position.of("d5"), Position.of("d6"), Position.of("d7"), Position.of("d8")), + new Path(Position.of("c4"), Position.of("b4"), Position.of("a4")), + new Path(Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4")) + ); + + //when + Collection availableAttackPaths = rook.findAttackPaths(position); + + //then + assertThat(availableAttackPaths) + .hasSize(expected.size()) + .containsAll(expected); + } +} diff --git a/src/test/java/chess/domain/player/AttackPositionsTest.java b/src/test/java/chess/domain/player/AttackPositionsTest.java deleted file mode 100644 index 84ad239..0000000 --- a/src/test/java/chess/domain/player/AttackPositionsTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package chess.domain.player; - -import chess.domain.board.Position; -import chess.domain.piece.Color; -import chess.domain.piece.Knight; -import chess.domain.piece.Piece; -import chess.domain.piece.PieceFactory; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class AttackPositionsTest { - - @ParameterizedTest - @ValueSource(strings = {"a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3"}) - @DisplayName("공격 가능한 위치들을 표시한다.") - void create(String key) { - //given - Map pieces = PieceFactory.createPieces(Color.WHITE); - AttackPositions attackPositions = new AttackPositions(pieces); - Position position = Position.of(key); - - //when - boolean isEmpty = attackPositions.isEmpty(position); - - //then - assertThat(isEmpty).isFalse(); - } - - @Test - @DisplayName("공격 가능한 위치들을 갱신한다.") - void update() { - //given - Map pieces = PieceFactory.createPieces(Color.WHITE); - AttackPositions attackPositions = new AttackPositions(pieces); - Position before = Position.of("b1"); - Position current = Position.of("c3"); - - //when - attackPositions.update(before, current, new Knight(Color.WHITE)); - - //then - assertThat(attackPositions.isEmpty(Position.of("a3"))).isFalse(); - assertThat(attackPositions.isEmpty(Position.of("b1"))).isFalse(); - assertThat(attackPositions.isEmpty(Position.of("c3"))).isFalse(); - assertThat(attackPositions.isEmpty(Position.of("b5"))).isFalse(); - assertThat(attackPositions.isEmpty(Position.of("d5"))).isFalse(); - } -} diff --git a/src/test/java/chess/domain/player/ColorTest.java b/src/test/java/chess/domain/player/ColorTest.java new file mode 100644 index 0000000..1f16b59 --- /dev/null +++ b/src/test/java/chess/domain/player/ColorTest.java @@ -0,0 +1,35 @@ +package chess.domain.player; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ColorTest { + + @Test + @DisplayName("백에서 흑으로 색을 반전한다.") + void next_turn_white() { + // given + Color color = Color.WHITE; + + // when + Color next = color.flip(); + + // then + assertThat(next.isBlack()).isTrue(); + } + + @Test + @DisplayName("흑에서 백으로 색을 반전한다.") + void next_turn_black() { + // given + Color color = Color.BLACK; + + // when + Color next = color.flip(); + + // then + assertThat(next.isWhite()).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/player/PlayerTest.java index f900a6a..bae1b30 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/player/PlayerTest.java @@ -1,13 +1,14 @@ package chess.domain.player; import chess.domain.board.Position; -import chess.domain.piece.Color; +import chess.domain.piece.move.Path; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import java.util.Set; +import java.util.Arrays; +import java.util.Collection; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -15,23 +16,41 @@ public class PlayerTest { @ParameterizedTest - @CsvSource({"b2, true", "b3, false"}) - @DisplayName("피스 색상을 넣어서 플레이어 객체를 생성한다.") - void create_with_color(String key, boolean expected) { + @CsvSource({"WHITE, a1, a8", "BLACK, a8, a1"}) + @DisplayName("색상에 따라 플레이어 객체를 생성한다.") + void create_with_color(Color color, String presentPosition, String notPresentPosition) { //given - Position position = Position.of(key); - Color color = Color.WHITE; + Position present = Position.of(presentPosition); + Position notPresent = Position.of(notPresentPosition); // when Player player = new Player(color); //then - assertThat(player.hasPieceOn(position)).isEqualTo(expected); + assertThat(player.hasPieceOn(present)).isTrue(); + assertThat(player.hasNoPieceOn(notPresent)).isTrue(); + } + + @ParameterizedTest + @CsvSource({"WHITE, b2, b3", "BLACK, d7, d6"}) + @DisplayName("기물을 움직인다.") + void update_board(Color color, String sourcePosition, String targetPosition) { + //given + Player player = new Player(color); + Position source = Position.of(sourcePosition); + Position target = Position.of(targetPosition); + + //when + player.move(source, target); + + //then + assertThat(player.hasNoPieceOn(source)).isTrue(); + assertThat(player.hasPieceOn(target)).isTrue(); } @ParameterizedTest @CsvSource({"WHITE, b3, b4", "BLACK, b6, b5"}) - @DisplayName("시작 위치에 기물이 존재하지 않을 경우 예외가 발생한다.") + @DisplayName("이동시킬 기물이 존재하지 않을 경우 예외가 발생한다.") void update_source_position_empty(Color color, String sourcePosition, String targetPosition) { //given Player player = new Player(color); @@ -45,95 +64,99 @@ void update_source_position_empty(Color color, String sourcePosition, String tar } @ParameterizedTest - @CsvSource({"WHITE, b2, b3", "BLACK, d7, d6"}) - @DisplayName("기물을 움직인다.") - void update_board(Color color, String sourcePosition, String targetPosition) { + @CsvSource({"WHITE, b2, b4, b3", "BLACK, d7, d5, d6"}) + @DisplayName("기물 이동 경로를 반환한다.") + void find_paths(Color color, String sourcePosition, String targetPosition, String expectedPosition) { //given Player player = new Player(color); Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); + Path expected = new Path(Position.of(expectedPosition)); //when - player.move(source, target); + Path path = player.findMovePath(source, target); //then - assertThat(player.hasPieceOn(source)).isFalse(); - assertThat(player.hasPieceOn(target)).isTrue(); + assertThat(path).isEqualTo(expected); } @ParameterizedTest - @CsvSource({"WHITE, b2, b4, b3", "BLACK, d7, d5, d6"}) - @DisplayName("이동 경로를 반환한다.") - void find_paths(Color color, String sourcePosition, String targetPosition, String expected) { - //given + @CsvSource({"WHITE, c3, b2, d2, b1", "BLACK, c6, b7, d7, b8"}) + @DisplayName("공격 가능한 모든 경로를 반환한다.") + void find_attack_paths(Color color, String targetPosition, String leftPath, String rightPath, String blocked) { + // given Player player = new Player(color); - Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); - Position path = Position.of(expected); + Collection expected = Arrays.asList( + new Path(Position.of(leftPath)), + new Path(Position.of(rightPath)) + ); + player.wasAttackedBy(Position.of(blocked)); - //when - Set paths = player.findPaths(source, target); + // when + Collection attackPaths = player.findAttackPaths(target); - //then - assertThat(paths).containsOnly(path); + // then + assertThat(attackPaths) + .hasSize(expected.size()) + .containsAll(expected); } @ParameterizedTest - @CsvSource({"WHITE, e1, e2", "BLACK, e8, e7"}) - @DisplayName("주어진 위치에 킹이 있는지 확인한다.") - void has_king_on(Color color, String kingPosition, String notKingPosition) { + @CsvSource({"WHITE, e2", "BLACK, e7"}) + @DisplayName("적에게 공격받은 경우 기물을 제거한다.") + void was_attacked_by(Color color, String targetPosition) { // given Player player = new Player(color); + Position target = Position.of(targetPosition); // when - boolean isKing = player.hasKingOn(Position.of(kingPosition)); - boolean isNotKing = player.hasKingOn(Position.of(notKingPosition)); + player.wasAttackedBy(target); // then - assertThat(isKing).isTrue(); - assertThat(isNotKing).isFalse(); + assertThat(player.hasNoPieceOn(target)).isTrue(); } @ParameterizedTest - @CsvSource({"WHITE, b3, e6", "BLACK, e6, b3"}) - @DisplayName("주어진 위치를 공격할 수 있는지 확인한다.") - void can_attack(Color color, String attackPosition, String notAttackPosition) { + @CsvSource({"WHITE, e1, e2", "BLACK, e8, e7"}) + @DisplayName("주어진 위치에 킹이 있는지 확인한다.") + void has_king_on(Color color, String kingPosition, String notKingPosition) { // given Player player = new Player(color); // when - boolean can = player.canAttack(Position.of(attackPosition)); - boolean cannot = player.canAttack(Position.of(notAttackPosition)); + boolean isKing = player.hasKingOn(Position.of(kingPosition)); + boolean isNotKing = player.hasKingOn(Position.of(notKingPosition)); // then - assertThat(can).isTrue(); - assertThat(cannot).isFalse(); + assertThat(isKing).isTrue(); + assertThat(isNotKing).isFalse(); } @Test - @DisplayName("현재 남아있는 피스의 점수 합을 구한다.") - void sum_scores() { + @DisplayName("킹이 존재하는지 반환한다.") + void is_king_dead() { //given Player player = new Player(Color.WHITE); + player.wasAttackedBy(Position.of("e1")); //when - double sum = player.calculateScores(); + boolean kingDead = player.isKingDead(); //then - assertThat(sum).isEqualTo(38); + assertThat(kingDead).isTrue(); } @Test - @DisplayName("킹이 존재하는지 반환한다.") - void is_king_dead() { + @DisplayName("현재 남아있는 피스의 점수 합을 구한다.") + void sum_scores() { //given Player player = new Player(Color.WHITE); - player.isUnderAttack(Position.of("e1")); //when - boolean kingDead = player.isKingDead(); + double sum = player.calculateScores(); //then - assertThat(kingDead).isTrue(); + assertThat(sum).isEqualTo(38); } } diff --git a/src/test/java/chess/domain/player/PositionTest.java b/src/test/java/chess/domain/player/PositionTest.java deleted file mode 100644 index 8138862..0000000 --- a/src/test/java/chess/domain/player/PositionTest.java +++ /dev/null @@ -1,121 +0,0 @@ -package chess.domain.player; - -import chess.domain.board.File; -import chess.domain.board.Position; -import chess.domain.board.Rank; -import chess.domain.piece.type.MoveUnit; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class PositionTest { - - @Test - @DisplayName("가로, 세로 인자에 해당하는 위치를 반환한다.") - void from_file_and_rank() { - //given - File file = File.a; - Rank rank = Rank.R1; - - //when - Position position = Position.from(file, rank); - - //then - assertThat(position).extracting("file", "rank") - .containsOnly(file, rank); - } - - @Test - @DisplayName("문자열 키에 해당하는 위치를 반환한다.") - void of_valid_key() { - //given - File file = File.a; - Rank rank = Rank.R1; - String key = file.name() + rank.getIndex(); - - //when - Position position = Position.of(key); - - //then - assertThat(position).extracting("file", "rank") - .containsOnly(file, rank); - } - - @ParameterizedTest - @ValueSource(strings = {"a0", "j1"}) - @DisplayName("유효하지 않은 키로 검색하면 예외를 던진다.") - void of_invalid_key(String key) { - //given, when, then - assertThrows(IllegalArgumentException.class, () -> { - Position.of(key); - }); - } - - - @Test - @DisplayName("거쳐가는 모든 위치를 반환한다.") - void find_passing_positions() { - // given - Position source = Position.of("d4"); - Position target = Position.of("d7"); - MoveUnit moveUnit = MoveUnit.NORTH; - - // when - Set positions = source.findPassingPositions(target, moveUnit); - - // then - assertThat(positions) - .hasSize(2) - .containsExactlyInAnyOrder(Position.of("d5"), Position.of("d6")); - } - - @ParameterizedTest - @MethodSource("createParams") - @DisplayName("이동 가능한 위치를 반환한다.") - void find_available_positions(boolean isFinite, Collection expected) { - // given - Position position = Position.of("d4"); - MoveUnit moveUnit = MoveUnit.EAST; - - // when - Collection positions = position.findAvailablePositions(moveUnit, isFinite); - - // then - assertThat(positions) - .hasSize(expected.size()) - .containsAll(expected); - } - - @Test - @DisplayName("랭크가 동일한지 확인한다.") - void has_same_rank() { - // given - Rank rank = Rank.R1; - Position position = Position.of("a1"); - - // when - boolean hasSameRank = position.hasSameRank(rank); - - // then - assertThat(hasSameRank).isTrue(); - } - - private static Stream createParams() { - return Stream.of( - Arguments.of(false, Arrays.asList(Position.of("e4"), Position.of("f4"), Position.of("g4"), Position.of("h4"))), - Arguments.of(true, Collections.singletonList(Position.of("e4"))) - ); - } -} From 9931625d7ec583cdb35aac3d5635ef2de56ab1cb Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Mon, 6 Sep 2021 05:37:47 +0900 Subject: [PATCH 33/38] =?UTF-8?q?test:=20=EA=B3=B5=EA=B2=A9=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=ED=83=90=EC=83=89=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/chess/domain/piece/move/MoveUnits.java | 12 +++++++----- src/main/java/chess/domain/piece/move/Path.java | 16 ++++++++-------- .../java/chess/domain/piece/type/BishopTest.java | 4 ++-- .../java/chess/domain/piece/type/KingTest.java | 12 +++++++----- .../java/chess/domain/piece/type/KnightTest.java | 8 ++++---- .../java/chess/domain/piece/type/PawnTest.java | 6 +++--- .../java/chess/domain/piece/type/QueenTest.java | 4 ++-- .../java/chess/domain/piece/type/RookTest.java | 4 ++-- 8 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/java/chess/domain/piece/move/MoveUnits.java b/src/main/java/chess/domain/piece/move/MoveUnits.java index 2bee6cd..fc29d9f 100644 --- a/src/main/java/chess/domain/piece/move/MoveUnits.java +++ b/src/main/java/chess/domain/piece/move/MoveUnits.java @@ -42,15 +42,17 @@ public Collection findReachableFinitePaths(final Position source) { moveUnits.stream() .filter(source::isMovable) - .forEach(moveUnit -> addNewPath(source, moveUnit, paths)); + .forEach(moveUnit -> { + Path path = buildPath(source, moveUnit); + paths.add(path); + }); return paths; } - private void addNewPath(Position source, MoveUnit moveUnit, Collection paths) { + private Path buildPath(Position source, MoveUnit moveUnit) { Position target = source.move(moveUnit); - Path path = new Path(source, target); - paths.add(path); + return new Path(source, target); } public Collection findReachableInfinitePaths(final Position source) { @@ -60,4 +62,4 @@ public Collection findReachableInfinitePaths(final Position source) { .map(Path::new) .collect(Collectors.toList()); } -} +} \ No newline at end of file diff --git a/src/main/java/chess/domain/piece/move/Path.java b/src/main/java/chess/domain/piece/move/Path.java index 87d7f27..8b00cfd 100644 --- a/src/main/java/chess/domain/piece/move/Path.java +++ b/src/main/java/chess/domain/piece/move/Path.java @@ -9,41 +9,41 @@ public class Path { private final List positions; - public Path(Position position) { + public Path(final Position position) { this(Collections.singletonList(position)); } - public Path(Position... positions) { + public Path(final Position... positions) { this(Arrays.asList(positions)); } - public Path(List positions) { + public Path(final List positions) { this.positions = Collections.unmodifiableList(new ArrayList<>(positions)); } - public boolean contains(Position position) { + public boolean contains(final Position position) { return positions.contains(position); } - public Path getPositionsUntilTarget(Position target) { + public Path getPositionsUntilTarget(final Position target) { List positionsBeforeTarget = new ArrayList<>(); for (Position position : positions) { if (position.isSame(target)) { break; } - positionsBeforeTarget.add(position); + positionsBeforeTarget.add(position); } return new Path(positionsBeforeTarget); } - public boolean isNotBlockedBy(Player player) { + public boolean isNotBlockedBy(final Player player) { return positions.stream() .noneMatch(player::hasPieceOn); } - public boolean isBlockedBy(Player player) { + public boolean isBlockedBy(final Player player) { return positions.stream() .anyMatch(player::hasPieceOn); } diff --git a/src/test/java/chess/domain/piece/type/BishopTest.java b/src/test/java/chess/domain/piece/type/BishopTest.java index 2addfd4..4138f8f 100644 --- a/src/test/java/chess/domain/piece/type/BishopTest.java +++ b/src/test/java/chess/domain/piece/type/BishopTest.java @@ -49,7 +49,7 @@ void find_paths_invalid_target() { @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece bishop = new Bishop(Color.WHITE); Collection expected = Arrays.asList( new Path(Position.of("c3"), Position.of("b2"), Position.of("a1")), @@ -59,7 +59,7 @@ void find_available_attack_positions() { ); //when - Collection availableAttackPaths = bishop.findAttackPaths(position); + Collection availableAttackPaths = bishop.findAttackPaths(source); //then assertThat(availableAttackPaths) diff --git a/src/test/java/chess/domain/piece/type/KingTest.java b/src/test/java/chess/domain/piece/type/KingTest.java index 55b4319..b5af125 100644 --- a/src/test/java/chess/domain/piece/type/KingTest.java +++ b/src/test/java/chess/domain/piece/type/KingTest.java @@ -52,16 +52,18 @@ void find_paths_invalid_target(String invalidTarget) { @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece king = new King(Color.WHITE); Collection expected = Arrays.asList( - new Path(Position.of("d3")), new Path(Position.of("d5")), - new Path(Position.of("c3")), new Path(Position.of("c4")), new Path(Position.of("c5")), - new Path(Position.of("e3")), new Path(Position.of("e4")), new Path(Position.of("e5")) + new Path(source, Position.of("d3")), new Path(source, Position.of("d5")), + new Path(source, Position.of("c3")), new Path(source, Position.of("c4")), new Path(source, Position.of("c5")), + new Path(source, Position.of("e3")), new Path(source, Position.of("e4")), new Path(source, Position.of("e5")) ); //when - Collection availableAttackPaths = king.findAttackPaths(position); + Collection availableAttackPaths = king.findAttackPaths(source); + + //then assertThat(availableAttackPaths) diff --git a/src/test/java/chess/domain/piece/type/KnightTest.java b/src/test/java/chess/domain/piece/type/KnightTest.java index f15bd8f..9e0ff26 100644 --- a/src/test/java/chess/domain/piece/type/KnightTest.java +++ b/src/test/java/chess/domain/piece/type/KnightTest.java @@ -52,15 +52,15 @@ void find_paths_invalid_target(String invalidTarget) { @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece knight = new Knight(Color.WHITE); Collection expected = Arrays.asList( - new Path(Position.of("c6")), new Path(Position.of("c2")), new Path(Position.of("e6")), new Path(Position.of("e2")), - new Path(Position.of("b5")), new Path(Position.of("b3")), new Path(Position.of("f5")), new Path(Position.of("f3")) + new Path(source, Position.of("c6")), new Path(source, Position.of("c2")), new Path(source, Position.of("e6")), new Path(source, Position.of("e2")), + new Path(source, Position.of("b5")), new Path(source, Position.of("b3")), new Path(source, Position.of("f5")), new Path(source, Position.of("f3")) ); //when - Collection availableAttackPositions = knight.findAttackPaths(position); + Collection availableAttackPositions = knight.findAttackPaths(source); //then assertThat(availableAttackPositions) diff --git a/src/test/java/chess/domain/piece/type/PawnTest.java b/src/test/java/chess/domain/piece/type/PawnTest.java index 64addec..0f698ca 100644 --- a/src/test/java/chess/domain/piece/type/PawnTest.java +++ b/src/test/java/chess/domain/piece/type/PawnTest.java @@ -115,14 +115,14 @@ void find_paths_fail_move_backward(String sourcePosition, String targetPosition, @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece pawn = new Pawn(Color.WHITE); Collection expected = Arrays.asList( - new Path(Position.of("c5")), new Path(Position.of("e5")) + new Path(source, Position.of("c5")), new Path(source, Position.of("e5")) ); //when - Collection availableAttackPaths = pawn.findAttackPaths(position); + Collection availableAttackPaths = pawn.findAttackPaths(source); //then assertThat(availableAttackPaths) diff --git a/src/test/java/chess/domain/piece/type/QueenTest.java b/src/test/java/chess/domain/piece/type/QueenTest.java index cdfc1a5..801adbd 100644 --- a/src/test/java/chess/domain/piece/type/QueenTest.java +++ b/src/test/java/chess/domain/piece/type/QueenTest.java @@ -70,7 +70,7 @@ void find_paths_invalid_target(String invalidTarget) { @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece queen = new Queen(Color.WHITE); Collection expected = Arrays.asList( // diagonal @@ -86,7 +86,7 @@ void find_available_attack_positions() { ); //when - Collection availableAttackPaths = queen.findAttackPaths(position); + Collection availableAttackPaths = queen.findAttackPaths(source); //then assertThat(availableAttackPaths) diff --git a/src/test/java/chess/domain/piece/type/RookTest.java b/src/test/java/chess/domain/piece/type/RookTest.java index 833a95a..9828f1b 100644 --- a/src/test/java/chess/domain/piece/type/RookTest.java +++ b/src/test/java/chess/domain/piece/type/RookTest.java @@ -49,7 +49,7 @@ void find_paths_invalid_target() { @DisplayName("입력받은 위치에서 공격 가능한 위치들을 반환해준다.") void find_available_attack_positions() { //given - Position position = Position.of("d4"); + Position source = Position.of("d4"); Piece rook = new Rook(Color.WHITE); Collection expected = Arrays.asList( new Path(Position.of("d3"), Position.of("d2"), Position.of("d1")), @@ -59,7 +59,7 @@ void find_available_attack_positions() { ); //when - Collection availableAttackPaths = rook.findAttackPaths(position); + Collection availableAttackPaths = rook.findAttackPaths(source); //then assertThat(availableAttackPaths) From 2026dacb1b547032fc306f2a4ad58be58fce85a4 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Wed, 15 Sep 2021 16:37:45 +0900 Subject: [PATCH 34/38] =?UTF-8?q?refactor:=20Player=20->=20Team=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=AA=85=EC=B9=AD=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +-- .../controller/ConsoleChessController.java | 2 +- src/main/java/chess/domain/board/Board.java | 66 +++++++++---------- .../chess/domain/piece/move/MovePattern.java | 2 +- .../java/chess/domain/piece/move/Path.java | 10 +-- .../java/chess/domain/piece/type/Bishop.java | 2 +- .../java/chess/domain/piece/type/King.java | 2 +- .../java/chess/domain/piece/type/Knight.java | 2 +- .../java/chess/domain/piece/type/Pawn.java | 2 +- .../java/chess/domain/piece/type/Piece.java | 2 +- .../chess/domain/piece/type/PieceFactory.java | 6 +- .../java/chess/domain/piece/type/Queen.java | 2 +- .../java/chess/domain/piece/type/Rook.java | 2 +- .../chess/domain/{player => team}/Color.java | 2 +- .../chess/domain/{player => team}/Scores.java | 2 +- .../{player/Player.java => team/Team.java} | 6 +- src/main/java/chess/service/ChessService.java | 4 +- .../java/chess/view/ConsoleOutputView.java | 2 +- src/main/java/chess/view/OutputView.java | 2 +- .../java/chess/domain/board/BoardTest.java | 6 +- .../chess/domain/piece/move/PathTest.java | 12 ++-- .../chess/domain/piece/type/BishopTest.java | 2 +- .../chess/domain/piece/type/KingTest.java | 2 +- .../chess/domain/piece/type/KnightTest.java | 2 +- .../chess/domain/piece/type/PawnTest.java | 2 +- .../chess/domain/piece/type/PieceTest.java | 2 +- .../domain/piece/type/PieceTypeTest.java | 2 +- .../chess/domain/piece/type/QueenTest.java | 2 +- .../chess/domain/piece/type/RookTest.java | 2 +- .../domain/{player => team}/ColorTest.java | 2 +- .../PlayerTest.java => team/TeamTest.java} | 56 ++++++++-------- 31 files changed, 109 insertions(+), 109 deletions(-) rename src/main/java/chess/domain/{player => team}/Color.java (91%) rename src/main/java/chess/domain/{player => team}/Scores.java (93%) rename src/main/java/chess/domain/{player/Player.java => team/Team.java} (97%) rename src/test/java/chess/domain/{player => team}/ColorTest.java (96%) rename src/test/java/chess/domain/{player/PlayerTest.java => team/TeamTest.java} (73%) diff --git a/README.md b/README.md index 515bdf9..e17dbb1 100644 --- a/README.md +++ b/README.md @@ -36,24 +36,24 @@ - [x] 기물 이동 - [x] 예외 - - [x] 움직일 기물이 플레이어 소유가 아닌 경우 + - [x] 움직일 기물이 자신 팀 소유가 아닌 경우 - [x] `source`와 `target`이 같은 위치인 경우 - [x] 본인의 기물을 공격하는 경우 - [x] 이동 경로가 다른 기물에 가로막힌 경우 - [x] `King`: `target`이 적이 공격 가능한 위치인 경우 - [x] `Pawn`: `target`에 적이 없는데 대각선으로 이동하는 경우 - - [x] 플레이어 기물 이동 + - [x] 자신의 기물 이동 - [x] 공격인 경우 적 기물 제거 - [x] 차례 변경 - [x] 현재 차례 관리 - [x] 위치 정보 - [x] 특정 위치가 비었는지 확인 - [x] 특정 위치에 있는 기물 확인 -- [x] 각 플레이어 점수 계산 +- [x] 각 팀 점수 계산 - [x] 승자 확인 - [x] 예외: 두 `King`이 모두 살아 있는 경우 -#### `Player` +#### `Team` - [x] 기물 이동 - [x] 기물 이동 경로 검색 diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index f6d62ee..f36a477 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,7 +1,7 @@ package chess.controller; import chess.domain.command.Command; -import chess.domain.player.Scores; +import chess.domain.team.Scores; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; import chess.service.ChessService; diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index 958e32e..e1c1de1 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -3,23 +3,23 @@ import chess.domain.command.MoveParameters; import chess.domain.piece.move.Path; import chess.domain.piece.type.Piece; -import chess.domain.player.Color; -import chess.domain.player.Player; -import chess.domain.player.Scores; +import chess.domain.team.Color; +import chess.domain.team.Scores; +import chess.domain.team.Team; import chess.exception.EmptyPositionException; -import static chess.domain.player.Color.BLACK; -import static chess.domain.player.Color.WHITE; +import static chess.domain.team.Color.BLACK; +import static chess.domain.team.Color.WHITE; public class Board { - private final Player whitePlayer; - private final Player blackPlayer; + private final Team whiteTeam; + private final Team blackTeam; private Color currentTurn; public Board() { - this.whitePlayer = new Player(WHITE); - this.blackPlayer = new Player(BLACK); + this.whiteTeam = new Team(WHITE); + this.blackTeam = new Team(BLACK); this.currentTurn = WHITE; } @@ -40,7 +40,7 @@ private void validateParameters(final Position source, final Position target) { } private void validateSourceOwner(final Position source) { - if (currentPlayer().hasNoPieceOn(source)) { + if (currentTeam().hasNoPieceOn(source)) { throw new IllegalArgumentException("자신의 기물만 움직일 수 있습니다."); } } @@ -52,36 +52,36 @@ private void validateSamePosition(final Position source, final Position target) } private void validateTargetOwner(final Position target) { - if (currentPlayer().hasPieceOn(target)) { + if (currentTeam().hasPieceOn(target)) { throw new IllegalArgumentException("자신의 기물이 있는 곳으로 이동할 수 없습니다."); } } private void validateKingMovable(final Position source, final Position target) { - if (currentPlayer().hasKingOn(source) && canEnemyAttack(target)) { + if (currentTeam().hasKingOn(source) && canEnemyAttack(target)) { throw new IllegalArgumentException("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } } private boolean canEnemyAttack(final Position target) { - return enemyPlayer().findAttackPaths(target).stream() - .anyMatch(path -> path.isNotBlockedBy(whitePlayer) && path.isNotBlockedBy(blackPlayer)); + return enemyTeam().findAttackPaths(target).stream() + .anyMatch(path -> path.isNotBlockedBy(whiteTeam) && path.isNotBlockedBy(blackTeam)); } private void movePiece(final Position source, final Position target) { - if (currentPlayer().isPawnAttacking(source, target) && enemyPlayer().hasNoPieceOn(target)) { + if (currentTeam().isPawnAttacking(source, target) && enemyTeam().hasNoPieceOn(target)) { throw new IllegalArgumentException("폰은 공격 대상이 있는 경우에만 대각선으로 이동할 수 있습니다."); } - Path path = currentPlayer().findMovePath(source, target); + Path path = currentTeam().findMovePath(source, target); validatePathNotBlocked(path); - enemyPlayer().wasAttackedBy(target); - currentPlayer().move(source, target); + enemyTeam().wasAttackedBy(target); + currentTeam().move(source, target); } private void validatePathNotBlocked(final Path path) { - if (path.isBlockedBy(whitePlayer) || path.isBlockedBy(blackPlayer)) { + if (path.isBlockedBy(whiteTeam) || path.isBlockedBy(blackTeam)) { throw new IllegalArgumentException("다른 기물을 통과하여 이동할 수 없습니다."); } } @@ -91,19 +91,19 @@ public Piece findBy(final Position position) { throw new EmptyPositionException(); } - if (whitePlayer.hasPieceOn(position)) { - return whitePlayer.findPieceBy(position); + if (whiteTeam.hasPieceOn(position)) { + return whiteTeam.findPieceBy(position); } - return blackPlayer.findPieceBy(position); + return blackTeam.findPieceBy(position); } private boolean isEmpty(Position position) { - return whitePlayer.hasNoPieceOn(position) && blackPlayer.hasNoPieceOn(position); + return whiteTeam.hasNoPieceOn(position) && blackTeam.hasNoPieceOn(position); } public Scores getScores() { - double whiteScore = whitePlayer.calculateScores(); - double blackScore = blackPlayer.calculateScores(); + double whiteScore = whiteTeam.calculateScores(); + double blackScore = blackTeam.calculateScores(); return new Scores(whiteScore, blackScore); } @@ -113,31 +113,31 @@ public Color getWinner() { throw new IllegalStateException("King이 잡히지 않아 승자가 없습니다."); } - if (whitePlayer.isKingDead()) { + if (whiteTeam.isKingDead()) { return BLACK; } return WHITE; } public boolean isBothKingAlive() { - return whitePlayer.isKingAlive() && blackPlayer.isKingAlive(); + return whiteTeam.isKingAlive() && blackTeam.isKingAlive(); } public Color getCurrentTurn() { return currentTurn; } - private Player currentPlayer() { + private Team currentTeam() { if (currentTurn.isWhite()) { - return whitePlayer; + return whiteTeam; } - return blackPlayer; + return blackTeam; } - private Player enemyPlayer() { + private Team enemyTeam() { if (currentTurn.isWhite()) { - return blackPlayer; + return blackTeam; } - return whitePlayer; + return whiteTeam; } } diff --git a/src/main/java/chess/domain/piece/move/MovePattern.java b/src/main/java/chess/domain/piece/move/MovePattern.java index 8c74b05..5d089a1 100644 --- a/src/main/java/chess/domain/piece/move/MovePattern.java +++ b/src/main/java/chess/domain/piece/move/MovePattern.java @@ -1,7 +1,7 @@ package chess.domain.piece.move; import chess.domain.board.Position; -import chess.domain.player.Color; +import chess.domain.team.Color; import java.util.Collection; diff --git a/src/main/java/chess/domain/piece/move/Path.java b/src/main/java/chess/domain/piece/move/Path.java index 8b00cfd..81f571b 100644 --- a/src/main/java/chess/domain/piece/move/Path.java +++ b/src/main/java/chess/domain/piece/move/Path.java @@ -1,7 +1,7 @@ package chess.domain.piece.move; import chess.domain.board.Position; -import chess.domain.player.Player; +import chess.domain.team.Team; import java.util.*; @@ -38,14 +38,14 @@ public Path getPositionsUntilTarget(final Position target) { return new Path(positionsBeforeTarget); } - public boolean isNotBlockedBy(final Player player) { + public boolean isNotBlockedBy(final Team team) { return positions.stream() - .noneMatch(player::hasPieceOn); + .noneMatch(team::hasPieceOn); } - public boolean isBlockedBy(final Player player) { + public boolean isBlockedBy(final Team team) { return positions.stream() - .anyMatch(player::hasPieceOn); + .anyMatch(team::hasPieceOn); } public boolean isNotEmpty() { diff --git a/src/main/java/chess/domain/piece/type/Bishop.java b/src/main/java/chess/domain/piece/type/Bishop.java index 0834202..68cce3d 100644 --- a/src/main/java/chess/domain/piece/type/Bishop.java +++ b/src/main/java/chess/domain/piece/type/Bishop.java @@ -1,7 +1,7 @@ package chess.domain.piece.type; import chess.domain.piece.move.MovePattern; -import chess.domain.player.Color; +import chess.domain.team.Color; public class Bishop extends Piece { diff --git a/src/main/java/chess/domain/piece/type/King.java b/src/main/java/chess/domain/piece/type/King.java index 5c897f7..05cddc0 100644 --- a/src/main/java/chess/domain/piece/type/King.java +++ b/src/main/java/chess/domain/piece/type/King.java @@ -1,7 +1,7 @@ package chess.domain.piece.type; import chess.domain.piece.move.MovePattern; -import chess.domain.player.Color; +import chess.domain.team.Color; public class King extends Piece { diff --git a/src/main/java/chess/domain/piece/type/Knight.java b/src/main/java/chess/domain/piece/type/Knight.java index e83799b..108eae7 100644 --- a/src/main/java/chess/domain/piece/type/Knight.java +++ b/src/main/java/chess/domain/piece/type/Knight.java @@ -1,7 +1,7 @@ package chess.domain.piece.type; import chess.domain.piece.move.MovePattern; -import chess.domain.player.Color; +import chess.domain.team.Color; public class Knight extends Piece { diff --git a/src/main/java/chess/domain/piece/type/Pawn.java b/src/main/java/chess/domain/piece/type/Pawn.java index b6c185f..ca75f34 100644 --- a/src/main/java/chess/domain/piece/type/Pawn.java +++ b/src/main/java/chess/domain/piece/type/Pawn.java @@ -5,7 +5,7 @@ import chess.domain.piece.move.MovePattern; import chess.domain.piece.move.MoveUnit; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import java.util.Collection; import java.util.List; diff --git a/src/main/java/chess/domain/piece/type/Piece.java b/src/main/java/chess/domain/piece/type/Piece.java index 132d5f9..3d7dab5 100644 --- a/src/main/java/chess/domain/piece/type/Piece.java +++ b/src/main/java/chess/domain/piece/type/Piece.java @@ -5,7 +5,7 @@ import chess.domain.piece.move.MovePattern; import chess.domain.piece.move.MoveUnit; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import java.util.Collection; import java.util.List; diff --git a/src/main/java/chess/domain/piece/type/PieceFactory.java b/src/main/java/chess/domain/piece/type/PieceFactory.java index 705b4cd..81d29cc 100644 --- a/src/main/java/chess/domain/piece/type/PieceFactory.java +++ b/src/main/java/chess/domain/piece/type/PieceFactory.java @@ -3,14 +3,14 @@ import chess.domain.board.File; import chess.domain.board.Position; import chess.domain.board.Rank; -import chess.domain.player.Color; +import chess.domain.team.Color; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import static chess.domain.player.Color.BLACK; -import static chess.domain.player.Color.WHITE; +import static chess.domain.team.Color.BLACK; +import static chess.domain.team.Color.WHITE; public class PieceFactory { diff --git a/src/main/java/chess/domain/piece/type/Queen.java b/src/main/java/chess/domain/piece/type/Queen.java index c11b5bc..6a051b5 100644 --- a/src/main/java/chess/domain/piece/type/Queen.java +++ b/src/main/java/chess/domain/piece/type/Queen.java @@ -1,7 +1,7 @@ package chess.domain.piece.type; import chess.domain.piece.move.MovePattern; -import chess.domain.player.Color; +import chess.domain.team.Color; public class Queen extends Piece { diff --git a/src/main/java/chess/domain/piece/type/Rook.java b/src/main/java/chess/domain/piece/type/Rook.java index ee0c373..c88dc6a 100644 --- a/src/main/java/chess/domain/piece/type/Rook.java +++ b/src/main/java/chess/domain/piece/type/Rook.java @@ -1,7 +1,7 @@ package chess.domain.piece.type; import chess.domain.piece.move.MovePattern; -import chess.domain.player.Color; +import chess.domain.team.Color; public class Rook extends Piece { diff --git a/src/main/java/chess/domain/player/Color.java b/src/main/java/chess/domain/team/Color.java similarity index 91% rename from src/main/java/chess/domain/player/Color.java rename to src/main/java/chess/domain/team/Color.java index 2f88839..a716fb4 100644 --- a/src/main/java/chess/domain/player/Color.java +++ b/src/main/java/chess/domain/team/Color.java @@ -1,4 +1,4 @@ -package chess.domain.player; +package chess.domain.team; public enum Color { diff --git a/src/main/java/chess/domain/player/Scores.java b/src/main/java/chess/domain/team/Scores.java similarity index 93% rename from src/main/java/chess/domain/player/Scores.java rename to src/main/java/chess/domain/team/Scores.java index 91d1a4a..74b28b8 100644 --- a/src/main/java/chess/domain/player/Scores.java +++ b/src/main/java/chess/domain/team/Scores.java @@ -1,4 +1,4 @@ -package chess.domain.player; +package chess.domain.team; public class Scores { diff --git a/src/main/java/chess/domain/player/Player.java b/src/main/java/chess/domain/team/Team.java similarity index 97% rename from src/main/java/chess/domain/player/Player.java rename to src/main/java/chess/domain/team/Team.java index 2596fe9..211591b 100644 --- a/src/main/java/chess/domain/player/Player.java +++ b/src/main/java/chess/domain/team/Team.java @@ -1,4 +1,4 @@ -package chess.domain.player; +package chess.domain.team; import chess.domain.board.File; import chess.domain.board.Position; @@ -14,11 +14,11 @@ import java.util.Map; import java.util.stream.Collectors; -public class Player { +public class Team { private final Map pieces; - public Player(final Color color) { + public Team(final Color color) { pieces = new HashMap<>(PieceFactory.initialPieces(color)); } diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 71ed65a..67bfff8 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -3,8 +3,8 @@ import chess.domain.board.Board; import chess.domain.command.Command; import chess.domain.command.MoveParameters; -import chess.domain.player.Color; -import chess.domain.player.Scores; +import chess.domain.team.Color; +import chess.domain.team.Scores; import chess.dto.BoardDto; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 13597a4..05d4a99 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.domain.player.Scores; +import chess.domain.team.Scores; import java.util.Map; diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index 50eeef5..bb21abe 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,6 +1,6 @@ package chess.view; -import chess.domain.player.Scores; +import chess.domain.team.Scores; import java.util.Map; diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/board/BoardTest.java index dd00394..4f0ff05 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/board/BoardTest.java @@ -4,8 +4,8 @@ import chess.domain.piece.type.Pawn; import chess.domain.piece.type.Piece; import chess.domain.piece.type.Rook; -import chess.domain.player.Color; -import chess.domain.player.Scores; +import chess.domain.team.Color; +import chess.domain.team.Scores; import chess.exception.EmptyPositionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -89,7 +89,7 @@ void find_by_empty() { } @Test - @DisplayName("각 플레이어의 점수를 계산한다.") + @DisplayName("각 팀의 점수를 계산한다.") void get_scores() { //given Board board = new Board(); diff --git a/src/test/java/chess/domain/piece/move/PathTest.java b/src/test/java/chess/domain/piece/move/PathTest.java index dbef426..16ea877 100644 --- a/src/test/java/chess/domain/piece/move/PathTest.java +++ b/src/test/java/chess/domain/piece/move/PathTest.java @@ -1,8 +1,8 @@ package chess.domain.piece.move; import chess.domain.board.Position; -import chess.domain.player.Color; -import chess.domain.player.Player; +import chess.domain.team.Color; +import chess.domain.team.Team; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,15 +26,15 @@ void get_positions_until_target() { } @Test - @DisplayName("경로가 플레이어의 기물로 막혀있는지 확인한다.") + @DisplayName("경로가 기물로 막혀있는지 확인한다.") void is_blocked_by() { // given Path path = new Path(Position.of("c4"), Position.of("c3"), Position.of("c2")); - Player player = new Player(Color.WHITE); + Team team = new Team(Color.WHITE); // when - boolean blockedBy = path.isBlockedBy(player); - boolean notBlockedBy = path.isNotBlockedBy(player); + boolean blockedBy = path.isBlockedBy(team); + boolean notBlockedBy = path.isNotBlockedBy(team); // then assertThat(blockedBy).isTrue(); diff --git a/src/test/java/chess/domain/piece/type/BishopTest.java b/src/test/java/chess/domain/piece/type/BishopTest.java index 4138f8f..af62af0 100644 --- a/src/test/java/chess/domain/piece/type/BishopTest.java +++ b/src/test/java/chess/domain/piece/type/BishopTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/KingTest.java b/src/test/java/chess/domain/piece/type/KingTest.java index b5af125..a911308 100644 --- a/src/test/java/chess/domain/piece/type/KingTest.java +++ b/src/test/java/chess/domain/piece/type/KingTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/KnightTest.java b/src/test/java/chess/domain/piece/type/KnightTest.java index 9e0ff26..5a5b6dc 100644 --- a/src/test/java/chess/domain/piece/type/KnightTest.java +++ b/src/test/java/chess/domain/piece/type/KnightTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/PawnTest.java b/src/test/java/chess/domain/piece/type/PawnTest.java index 0f698ca..46f8246 100644 --- a/src/test/java/chess/domain/piece/type/PawnTest.java +++ b/src/test/java/chess/domain/piece/type/PawnTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/PieceTest.java b/src/test/java/chess/domain/piece/type/PieceTest.java index b3c8440..7ec21b2 100644 --- a/src/test/java/chess/domain/piece/type/PieceTest.java +++ b/src/test/java/chess/domain/piece/type/PieceTest.java @@ -1,6 +1,6 @@ package chess.domain.piece.type; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/PieceTypeTest.java b/src/test/java/chess/domain/piece/type/PieceTypeTest.java index 6964ea2..900df37 100644 --- a/src/test/java/chess/domain/piece/type/PieceTypeTest.java +++ b/src/test/java/chess/domain/piece/type/PieceTypeTest.java @@ -1,6 +1,6 @@ package chess.domain.piece.type; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/QueenTest.java b/src/test/java/chess/domain/piece/type/QueenTest.java index 801adbd..8128d23 100644 --- a/src/test/java/chess/domain/piece/type/QueenTest.java +++ b/src/test/java/chess/domain/piece/type/QueenTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/piece/type/RookTest.java b/src/test/java/chess/domain/piece/type/RookTest.java index 9828f1b..fc76d9c 100644 --- a/src/test/java/chess/domain/piece/type/RookTest.java +++ b/src/test/java/chess/domain/piece/type/RookTest.java @@ -2,7 +2,7 @@ import chess.domain.board.Position; import chess.domain.piece.move.Path; -import chess.domain.player.Color; +import chess.domain.team.Color; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/chess/domain/player/ColorTest.java b/src/test/java/chess/domain/team/ColorTest.java similarity index 96% rename from src/test/java/chess/domain/player/ColorTest.java rename to src/test/java/chess/domain/team/ColorTest.java index 1f16b59..a83662e 100644 --- a/src/test/java/chess/domain/player/ColorTest.java +++ b/src/test/java/chess/domain/team/ColorTest.java @@ -1,4 +1,4 @@ -package chess.domain.player; +package chess.domain.team; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/player/PlayerTest.java b/src/test/java/chess/domain/team/TeamTest.java similarity index 73% rename from src/test/java/chess/domain/player/PlayerTest.java rename to src/test/java/chess/domain/team/TeamTest.java index bae1b30..020eb28 100644 --- a/src/test/java/chess/domain/player/PlayerTest.java +++ b/src/test/java/chess/domain/team/TeamTest.java @@ -1,4 +1,4 @@ -package chess.domain.player; +package chess.domain.team; import chess.domain.board.Position; import chess.domain.piece.move.Path; @@ -13,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -public class PlayerTest { +public class TeamTest { @ParameterizedTest @CsvSource({"WHITE, a1, a8", "BLACK, a8, a1"}) - @DisplayName("색상에 따라 플레이어 객체를 생성한다.") + @DisplayName("색상에 따라 팀 객체를 생성한다.") void create_with_color(Color color, String presentPosition, String notPresentPosition) { //given Position present = Position.of(presentPosition); Position notPresent = Position.of(notPresentPosition); // when - Player player = new Player(color); + Team team = new Team(color); //then - assertThat(player.hasPieceOn(present)).isTrue(); - assertThat(player.hasNoPieceOn(notPresent)).isTrue(); + assertThat(team.hasPieceOn(present)).isTrue(); + assertThat(team.hasNoPieceOn(notPresent)).isTrue(); } @ParameterizedTest @@ -36,16 +36,16 @@ void create_with_color(Color color, String presentPosition, String notPresentPos @DisplayName("기물을 움직인다.") void update_board(Color color, String sourcePosition, String targetPosition) { //given - Player player = new Player(color); + Team team = new Team(color); Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); //when - player.move(source, target); + team.move(source, target); //then - assertThat(player.hasNoPieceOn(source)).isTrue(); - assertThat(player.hasPieceOn(target)).isTrue(); + assertThat(team.hasNoPieceOn(source)).isTrue(); + assertThat(team.hasPieceOn(target)).isTrue(); } @ParameterizedTest @@ -53,13 +53,13 @@ void update_board(Color color, String sourcePosition, String targetPosition) { @DisplayName("이동시킬 기물이 존재하지 않을 경우 예외가 발생한다.") void update_source_position_empty(Color color, String sourcePosition, String targetPosition) { //given - Player player = new Player(color); + Team team = new Team(color); Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> player.move(source, target)) + .isThrownBy(() -> team.move(source, target)) .withMessage("해당 위치에 기물이 존재하지 않습니다."); } @@ -68,13 +68,13 @@ void update_source_position_empty(Color color, String sourcePosition, String tar @DisplayName("기물 이동 경로를 반환한다.") void find_paths(Color color, String sourcePosition, String targetPosition, String expectedPosition) { //given - Player player = new Player(color); + Team team = new Team(color); Position source = Position.of(sourcePosition); Position target = Position.of(targetPosition); Path expected = new Path(Position.of(expectedPosition)); //when - Path path = player.findMovePath(source, target); + Path path = team.findMovePath(source, target); //then assertThat(path).isEqualTo(expected); @@ -85,16 +85,16 @@ void find_paths(Color color, String sourcePosition, String targetPosition, Strin @DisplayName("공격 가능한 모든 경로를 반환한다.") void find_attack_paths(Color color, String targetPosition, String leftPath, String rightPath, String blocked) { // given - Player player = new Player(color); + Team team = new Team(color); Position target = Position.of(targetPosition); Collection expected = Arrays.asList( new Path(Position.of(leftPath)), new Path(Position.of(rightPath)) ); - player.wasAttackedBy(Position.of(blocked)); + team.wasAttackedBy(Position.of(blocked)); // when - Collection attackPaths = player.findAttackPaths(target); + Collection attackPaths = team.findAttackPaths(target); // then assertThat(attackPaths) @@ -107,14 +107,14 @@ void find_attack_paths(Color color, String targetPosition, String leftPath, Stri @DisplayName("적에게 공격받은 경우 기물을 제거한다.") void was_attacked_by(Color color, String targetPosition) { // given - Player player = new Player(color); + Team team = new Team(color); Position target = Position.of(targetPosition); // when - player.wasAttackedBy(target); + team.wasAttackedBy(target); // then - assertThat(player.hasNoPieceOn(target)).isTrue(); + assertThat(team.hasNoPieceOn(target)).isTrue(); } @ParameterizedTest @@ -122,11 +122,11 @@ void was_attacked_by(Color color, String targetPosition) { @DisplayName("주어진 위치에 킹이 있는지 확인한다.") void has_king_on(Color color, String kingPosition, String notKingPosition) { // given - Player player = new Player(color); + Team team = new Team(color); // when - boolean isKing = player.hasKingOn(Position.of(kingPosition)); - boolean isNotKing = player.hasKingOn(Position.of(notKingPosition)); + boolean isKing = team.hasKingOn(Position.of(kingPosition)); + boolean isNotKing = team.hasKingOn(Position.of(notKingPosition)); // then assertThat(isKing).isTrue(); @@ -137,11 +137,11 @@ void has_king_on(Color color, String kingPosition, String notKingPosition) { @DisplayName("킹이 존재하는지 반환한다.") void is_king_dead() { //given - Player player = new Player(Color.WHITE); - player.wasAttackedBy(Position.of("e1")); + Team team = new Team(Color.WHITE); + team.wasAttackedBy(Position.of("e1")); //when - boolean kingDead = player.isKingDead(); + boolean kingDead = team.isKingDead(); //then assertThat(kingDead).isTrue(); @@ -151,10 +151,10 @@ void is_king_dead() { @DisplayName("현재 남아있는 피스의 점수 합을 구한다.") void sum_scores() { //given - Player player = new Player(Color.WHITE); + Team team = new Team(Color.WHITE); //when - double sum = player.calculateScores(); + double sum = team.calculateScores(); //then assertThat(sum).isEqualTo(38); From 8a8ccbb6b2af9322e7fa6a9beca92f2c136ba8b4 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Wed, 15 Sep 2021 16:51:29 +0900 Subject: [PATCH 35/38] =?UTF-8?q?refactor:=20Team=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/board/Board.java | 4 +- src/main/java/chess/domain/team/Team.java | 10 +- .../chess/domain/piece/move/PathTest.java | 3 +- src/test/java/chess/domain/team/TeamTest.java | 104 +++++++++--------- 4 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/board/Board.java index e1c1de1..75e8811 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/board/Board.java @@ -18,8 +18,8 @@ public class Board { private Color currentTurn; public Board() { - this.whiteTeam = new Team(WHITE); - this.blackTeam = new Team(BLACK); + this.whiteTeam = Team.white(); + this.blackTeam = Team.black(); this.currentTurn = WHITE; } diff --git a/src/main/java/chess/domain/team/Team.java b/src/main/java/chess/domain/team/Team.java index 211591b..20ad855 100644 --- a/src/main/java/chess/domain/team/Team.java +++ b/src/main/java/chess/domain/team/Team.java @@ -18,7 +18,15 @@ public class Team { private final Map pieces; - public Team(final Color color) { + public static Team white() { + return new Team(Color.WHITE); + } + + public static Team black() { + return new Team(Color.BLACK); + } + + private Team(final Color color) { pieces = new HashMap<>(PieceFactory.initialPieces(color)); } diff --git a/src/test/java/chess/domain/piece/move/PathTest.java b/src/test/java/chess/domain/piece/move/PathTest.java index 16ea877..fa53432 100644 --- a/src/test/java/chess/domain/piece/move/PathTest.java +++ b/src/test/java/chess/domain/piece/move/PathTest.java @@ -1,7 +1,6 @@ package chess.domain.piece.move; import chess.domain.board.Position; -import chess.domain.team.Color; import chess.domain.team.Team; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -30,7 +29,7 @@ void get_positions_until_target() { void is_blocked_by() { // given Path path = new Path(Position.of("c4"), Position.of("c3"), Position.of("c2")); - Team team = new Team(Color.WHITE); + Team team = Team.white(); // when boolean blockedBy = path.isBlockedBy(team); diff --git a/src/test/java/chess/domain/team/TeamTest.java b/src/test/java/chess/domain/team/TeamTest.java index 020eb28..6809643 100644 --- a/src/test/java/chess/domain/team/TeamTest.java +++ b/src/test/java/chess/domain/team/TeamTest.java @@ -4,8 +4,6 @@ import chess.domain.piece.move.Path; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import java.util.Arrays; import java.util.Collection; @@ -15,30 +13,43 @@ public class TeamTest { - @ParameterizedTest - @CsvSource({"WHITE, a1, a8", "BLACK, a8, a1"}) - @DisplayName("색상에 따라 팀 객체를 생성한다.") - void create_with_color(Color color, String presentPosition, String notPresentPosition) { + @Test + @DisplayName("WHITE 팀 객체를 생성한다.") + void create_with_color() { //given - Position present = Position.of(presentPosition); - Position notPresent = Position.of(notPresentPosition); + Position present = Position.of("a1"); + Position notPresent = Position.of("a8"); // when - Team team = new Team(color); + Team team = Team.white(); //then assertThat(team.hasPieceOn(present)).isTrue(); assertThat(team.hasNoPieceOn(notPresent)).isTrue(); } - @ParameterizedTest - @CsvSource({"WHITE, b2, b3", "BLACK, d7, d6"}) + @Test + @DisplayName("BLACK 팀 객체를 생성한다.") + void create_black_team() { + //given + Position present = Position.of("a8"); + Position notPresent = Position.of("a1"); + + // when + Team team = Team.black(); + + //then + assertThat(team.hasPieceOn(present)).isTrue(); + assertThat(team.hasNoPieceOn(notPresent)).isTrue(); + } + + @Test @DisplayName("기물을 움직인다.") - void update_board(Color color, String sourcePosition, String targetPosition) { + void update_board() { //given - Team team = new Team(color); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); + Team team = Team.white(); + Position source = Position.of("b2"); + Position target = Position.of("b3"); //when team.move(source, target); @@ -48,14 +59,13 @@ void update_board(Color color, String sourcePosition, String targetPosition) { assertThat(team.hasPieceOn(target)).isTrue(); } - @ParameterizedTest - @CsvSource({"WHITE, b3, b4", "BLACK, b6, b5"}) + @Test @DisplayName("이동시킬 기물이 존재하지 않을 경우 예외가 발생한다.") - void update_source_position_empty(Color color, String sourcePosition, String targetPosition) { + void update_source_position_empty() { //given - Team team = new Team(color); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); + Team team = Team.white(); + Position source = Position.of("b3"); + Position target = Position.of("b4"); //when, then assertThatIllegalArgumentException() @@ -63,15 +73,14 @@ void update_source_position_empty(Color color, String sourcePosition, String tar .withMessage("해당 위치에 기물이 존재하지 않습니다."); } - @ParameterizedTest - @CsvSource({"WHITE, b2, b4, b3", "BLACK, d7, d5, d6"}) + @Test @DisplayName("기물 이동 경로를 반환한다.") - void find_paths(Color color, String sourcePosition, String targetPosition, String expectedPosition) { + void find_paths() { //given - Team team = new Team(color); - Position source = Position.of(sourcePosition); - Position target = Position.of(targetPosition); - Path expected = new Path(Position.of(expectedPosition)); + Team team = Team.white(); + Position source = Position.of("b2"); + Position target = Position.of("b4"); + Path expected = new Path(Position.of("b3")); //when Path path = team.findMovePath(source, target); @@ -80,18 +89,17 @@ void find_paths(Color color, String sourcePosition, String targetPosition, Strin assertThat(path).isEqualTo(expected); } - @ParameterizedTest - @CsvSource({"WHITE, c3, b2, d2, b1", "BLACK, c6, b7, d7, b8"}) + @Test @DisplayName("공격 가능한 모든 경로를 반환한다.") - void find_attack_paths(Color color, String targetPosition, String leftPath, String rightPath, String blocked) { + void find_attack_paths() { // given - Team team = new Team(color); - Position target = Position.of(targetPosition); + Team team = Team.white(); + Position target = Position.of("c3"); Collection expected = Arrays.asList( - new Path(Position.of(leftPath)), - new Path(Position.of(rightPath)) + new Path(Position.of("b2")), + new Path(Position.of("d2")) ); - team.wasAttackedBy(Position.of(blocked)); + team.wasAttackedBy(Position.of("b1")); // when Collection attackPaths = team.findAttackPaths(target); @@ -102,13 +110,12 @@ void find_attack_paths(Color color, String targetPosition, String leftPath, Stri .containsAll(expected); } - @ParameterizedTest - @CsvSource({"WHITE, e2", "BLACK, e7"}) + @Test @DisplayName("적에게 공격받은 경우 기물을 제거한다.") - void was_attacked_by(Color color, String targetPosition) { + void was_attacked_by() { // given - Team team = new Team(color); - Position target = Position.of(targetPosition); + Team team = Team.white(); + Position target = Position.of("e2"); // when team.wasAttackedBy(target); @@ -117,16 +124,15 @@ void was_attacked_by(Color color, String targetPosition) { assertThat(team.hasNoPieceOn(target)).isTrue(); } - @ParameterizedTest - @CsvSource({"WHITE, e1, e2", "BLACK, e8, e7"}) + @Test @DisplayName("주어진 위치에 킹이 있는지 확인한다.") - void has_king_on(Color color, String kingPosition, String notKingPosition) { + void has_king_on() { // given - Team team = new Team(color); + Team team = Team.white(); // when - boolean isKing = team.hasKingOn(Position.of(kingPosition)); - boolean isNotKing = team.hasKingOn(Position.of(notKingPosition)); + boolean isKing = team.hasKingOn(Position.of("e1")); + boolean isNotKing = team.hasKingOn(Position.of("e2")); // then assertThat(isKing).isTrue(); @@ -137,7 +143,7 @@ void has_king_on(Color color, String kingPosition, String notKingPosition) { @DisplayName("킹이 존재하는지 반환한다.") void is_king_dead() { //given - Team team = new Team(Color.WHITE); + Team team = Team.white(); team.wasAttackedBy(Position.of("e1")); //when @@ -151,7 +157,7 @@ void is_king_dead() { @DisplayName("현재 남아있는 피스의 점수 합을 구한다.") void sum_scores() { //given - Team team = new Team(Color.WHITE); + Team team = Team.white(); //when double sum = team.calculateScores(); From ea4b38fa92b576237b12fa289a6fd798a12e3a8b Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Wed, 15 Sep 2021 17:26:00 +0900 Subject: [PATCH 36/38] =?UTF-8?q?refactor:=20Board=20=E2=86=92=20ChessGame?= =?UTF-8?q?=20=EC=9C=BC=EB=A1=9C=20=EB=AA=85=EC=B9=AD=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{board/Board.java => ChessGame.java} | 9 +- src/main/java/chess/dto/BoardDto.java | 10 +-- src/main/java/chess/service/ChessService.java | 18 ++-- .../BoardTest.java => ChessGameTest.java} | 89 ++++++++++--------- .../chess/domain/piece/type/KingTest.java | 1 - 5 files changed, 64 insertions(+), 63 deletions(-) rename src/main/java/chess/domain/{board/Board.java => ChessGame.java} (96%) rename src/test/java/chess/domain/{board/BoardTest.java => ChessGameTest.java} (60%) diff --git a/src/main/java/chess/domain/board/Board.java b/src/main/java/chess/domain/ChessGame.java similarity index 96% rename from src/main/java/chess/domain/board/Board.java rename to src/main/java/chess/domain/ChessGame.java index 75e8811..d0eea80 100644 --- a/src/main/java/chess/domain/board/Board.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -1,5 +1,6 @@ -package chess.domain.board; +package chess.domain; +import chess.domain.board.Position; import chess.domain.command.MoveParameters; import chess.domain.piece.move.Path; import chess.domain.piece.type.Piece; @@ -11,13 +12,13 @@ import static chess.domain.team.Color.BLACK; import static chess.domain.team.Color.WHITE; -public class Board { +public class ChessGame { private final Team whiteTeam; private final Team blackTeam; private Color currentTurn; - public Board() { + public ChessGame() { this.whiteTeam = Team.white(); this.blackTeam = Team.black(); this.currentTurn = WHITE; @@ -86,7 +87,7 @@ private void validatePathNotBlocked(final Path path) { } } - public Piece findBy(final Position position) { + public Piece findPieceBy(final Position position) { if (isEmpty(position)) { throw new EmptyPositionException(); } diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index 44190d1..c68cd11 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -1,6 +1,6 @@ package chess.dto; -import chess.domain.board.Board; +import chess.domain.ChessGame; import chess.domain.board.Position; import chess.domain.piece.type.Piece; import chess.domain.piece.type.PieceType; @@ -16,22 +16,22 @@ private BoardDto() { private static final String EMPTY_PIECE = "."; - public static Map of(Board board) { + public static Map of(ChessGame chessGame) { Map pieceOnPositions = new HashMap<>(); Position.names().forEach(positionKey -> { - String pieceName = findPieceName(positionKey, board); + String pieceName = findPieceName(chessGame, positionKey); pieceOnPositions.put(positionKey, pieceName); }); return pieceOnPositions; } - private static String findPieceName(String positionKey, Board board) { + private static String findPieceName(ChessGame chessGame, String positionKey) { Position position = Position.of(positionKey); try { - Piece piece = board.findBy(position); + Piece piece = chessGame.findPieceBy(position); return PieceType.findNameBy(piece); } catch (EmptyPositionException e) { return EMPTY_PIECE; diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 67bfff8..3d5c54d 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,6 +1,6 @@ package chess.service; -import chess.domain.board.Board; +import chess.domain.ChessGame; import chess.domain.command.Command; import chess.domain.command.MoveParameters; import chess.domain.team.Color; @@ -13,10 +13,10 @@ public class ChessService { - private final Board board; + private final ChessGame chessGame; public ChessService() { - this.board = new Board(); + this.chessGame = new ChessGame(); } public void run(Command command) { @@ -39,7 +39,7 @@ public void run(Command command) { } public boolean isGameRunning() { - return board.isBothKingAlive(); + return chessGame.isBothKingAlive(); } public boolean isGameFinished() { @@ -47,23 +47,23 @@ public boolean isGameFinished() { } public void movePiece(MoveParameters parameters) { - board.move(parameters); + chessGame.move(parameters); } public Scores getScores() { - return board.getScores(); + return chessGame.getScores(); } public Map getBoardDto() { - return BoardDto.of(board); + return BoardDto.of(chessGame); } public String getCurrentTurnDto() { - return board.getCurrentTurn().name(); + return chessGame.getCurrentTurn().name(); } public String getWinnerDto() { - Color color = board.getWinner(); + Color color = chessGame.getWinner(); return color.name(); } } diff --git a/src/test/java/chess/domain/board/BoardTest.java b/src/test/java/chess/domain/ChessGameTest.java similarity index 60% rename from src/test/java/chess/domain/board/BoardTest.java rename to src/test/java/chess/domain/ChessGameTest.java index 4f0ff05..93b6e61 100644 --- a/src/test/java/chess/domain/board/BoardTest.java +++ b/src/test/java/chess/domain/ChessGameTest.java @@ -1,5 +1,6 @@ -package chess.domain.board; +package chess.domain; +import chess.domain.board.Position; import chess.domain.command.MoveParameters; import chess.domain.piece.type.Pawn; import chess.domain.piece.type.Piece; @@ -15,7 +16,7 @@ import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertThrows; -public class BoardTest { +public class ChessGameTest { @ParameterizedTest @CsvSource({"b3, b4, 자신의 기물만 움직일 수 있습니다.", // 빈칸 @@ -27,12 +28,12 @@ public class BoardTest { @DisplayName("이동할 수 없는 경우 예외가 발생한다.") void move_exception(String source, String target, String exceptionMessage) { //given - Board board = new Board(); + ChessGame chessGame = new ChessGame(); MoveParameters moveParameters = new MoveParameters(source, target); //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters)) + .isThrownBy(() -> chessGame.move(moveParameters)) .withMessage(exceptionMessage); } @@ -41,18 +42,18 @@ void move_exception(String source, String target, String exceptionMessage) { @DisplayName("상대방이 킹의 목적지를 공격 가능한 경우 예외가 발생한다.") void move_king_invalid_target(String source, String target) { //given - Board board = new Board(); - board.move(new MoveParameters("e2", "e4")); - board.move(new MoveParameters("c7", "c5")); - board.move(new MoveParameters("d2", "d4")); - board.move(new MoveParameters("d8", "a5")); - board.move(new MoveParameters("e1", "e2")); - board.move(new MoveParameters("h7", "h5")); + ChessGame chessGame = new ChessGame(); + chessGame.move(new MoveParameters("e2", "e4")); + chessGame.move(new MoveParameters("c7", "c5")); + chessGame.move(new MoveParameters("d2", "d4")); + chessGame.move(new MoveParameters("d8", "a5")); + chessGame.move(new MoveParameters("e1", "e2")); + chessGame.move(new MoveParameters("h7", "h5")); MoveParameters moveParameters = new MoveParameters(source, target); //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> board.move(moveParameters)) + .isThrownBy(() -> chessGame.move(moveParameters)) .withMessage("킹은 상대방이 공격 가능한 위치로 이동할 수 없습니다."); } @@ -64,9 +65,9 @@ void find_by() { Position blackRookPosition = Position.of("a8"); //when - Board board = new Board(); - Piece whitePawn = board.findBy(whitePawnPosition); - Piece blackRook = board.findBy(blackRookPosition); + ChessGame chessGame = new ChessGame(); + Piece whitePawn = chessGame.findPieceBy(whitePawnPosition); + Piece blackRook = chessGame.findPieceBy(blackRookPosition); //then assertThat(whitePawn).isInstanceOf(Pawn.class); @@ -82,28 +83,28 @@ void find_by_empty() { Position emptyPosition = Position.of("d5"); //when - Board board = new Board(); + ChessGame chessGame = new ChessGame(); //then - assertThrows(EmptyPositionException.class, () -> board.findBy(emptyPosition)); + assertThrows(EmptyPositionException.class, () -> chessGame.findPieceBy(emptyPosition)); } @Test @DisplayName("각 팀의 점수를 계산한다.") void get_scores() { //given - Board board = new Board(); - board.move(new MoveParameters("a2", "a3")); - board.move(new MoveParameters("e7", "e5")); - board.move(new MoveParameters("d2", "d4")); - board.move(new MoveParameters("e5", "e4")); - board.move(new MoveParameters("b2", "b3")); - board.move(new MoveParameters("a7", "a6")); - board.move(new MoveParameters("f2", "f3")); - board.move(new MoveParameters("e4", "f3")); + ChessGame chessGame = new ChessGame(); + chessGame.move(new MoveParameters("a2", "a3")); + chessGame.move(new MoveParameters("e7", "e5")); + chessGame.move(new MoveParameters("d2", "d4")); + chessGame.move(new MoveParameters("e5", "e4")); + chessGame.move(new MoveParameters("b2", "b3")); + chessGame.move(new MoveParameters("a7", "a6")); + chessGame.move(new MoveParameters("f2", "f3")); + chessGame.move(new MoveParameters("e4", "f3")); //when - Scores scores = board.getScores(); + Scores scores = chessGame.getScores(); //then assertThat(scores.getWhiteScore()).isEqualTo(37); @@ -114,15 +115,15 @@ void get_scores() { @DisplayName("흑팀 킹이 죽으면 백팀을 승자로 반환한다.") void get_winner_white() { //given - Board board = new Board(); - board.move(new MoveParameters("e2", "e4")); - board.move(new MoveParameters("f7", "f5")); - board.move(new MoveParameters("d1", "h5")); - board.move(new MoveParameters("a7", "a5")); - board.move(new MoveParameters("h5", "e8")); + ChessGame chessGame = new ChessGame(); + chessGame.move(new MoveParameters("e2", "e4")); + chessGame.move(new MoveParameters("f7", "f5")); + chessGame.move(new MoveParameters("d1", "h5")); + chessGame.move(new MoveParameters("a7", "a5")); + chessGame.move(new MoveParameters("h5", "e8")); //when - Color winner = board.getWinner(); + Color winner = chessGame.getWinner(); //then assertThat(winner.isWhite()).isTrue(); @@ -132,16 +133,16 @@ void get_winner_white() { @DisplayName("백팀 킹이 죽으면 흑팀을 승자로 반환한다.") void get_winner_black() { //given - Board board = new Board(); - board.move(new MoveParameters("f2", "f4")); - board.move(new MoveParameters("e7", "e5")); - board.move(new MoveParameters("a2", "a4")); - board.move(new MoveParameters("d8", "h4")); - board.move(new MoveParameters("b2", "b4")); - board.move(new MoveParameters("h4", "e1")); + ChessGame chessGame = new ChessGame(); + chessGame.move(new MoveParameters("f2", "f4")); + chessGame.move(new MoveParameters("e7", "e5")); + chessGame.move(new MoveParameters("a2", "a4")); + chessGame.move(new MoveParameters("d8", "h4")); + chessGame.move(new MoveParameters("b2", "b4")); + chessGame.move(new MoveParameters("h4", "e1")); //when - Color winner = board.getWinner(); + Color winner = chessGame.getWinner(); //then assertThat(winner.isBlack()).isTrue(); @@ -151,11 +152,11 @@ void get_winner_black() { @DisplayName("흑백 킹이 모두 살아있는 경우 승자를 요청하면 예외를 반환한다.") void get_winner_exception() { //given - Board board = new Board(); + ChessGame chessGame = new ChessGame(); //when, then assertThatIllegalStateException() - .isThrownBy(board::getWinner) + .isThrownBy(chessGame::getWinner) .withMessage("King이 잡히지 않아 승자가 없습니다."); } } diff --git a/src/test/java/chess/domain/piece/type/KingTest.java b/src/test/java/chess/domain/piece/type/KingTest.java index a911308..83e9334 100644 --- a/src/test/java/chess/domain/piece/type/KingTest.java +++ b/src/test/java/chess/domain/piece/type/KingTest.java @@ -64,7 +64,6 @@ void find_available_attack_positions() { Collection availableAttackPaths = king.findAttackPaths(source); - //then assertThat(availableAttackPaths) .hasSize(expected.size()) From 46a6daa4f0b72ce65c36bbc8e5598757a4121c89 Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Wed, 15 Sep 2021 17:44:29 +0900 Subject: [PATCH 37/38] =?UTF-8?q?refactor:=20=EA=B8=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/chess/domain/ChessGame.java | 4 ++-- src/main/java/chess/domain/team/Team.java | 4 ++-- src/test/java/chess/domain/team/TeamTest.java | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/ChessGame.java index d0eea80..d20ac07 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/ChessGame.java @@ -77,8 +77,8 @@ private void movePiece(final Position source, final Position target) { Path path = currentTeam().findMovePath(source, target); validatePathNotBlocked(path); - enemyTeam().wasAttackedBy(target); - currentTeam().move(source, target); + enemyTeam().removePiece(target); + currentTeam().movePiece(source, target); } private void validatePathNotBlocked(final Path path) { diff --git a/src/main/java/chess/domain/team/Team.java b/src/main/java/chess/domain/team/Team.java index 20ad855..796323f 100644 --- a/src/main/java/chess/domain/team/Team.java +++ b/src/main/java/chess/domain/team/Team.java @@ -30,7 +30,7 @@ private Team(final Color color) { pieces = new HashMap<>(PieceFactory.initialPieces(color)); } - public void move(final Position source, final Position target) { + public void movePiece(final Position source, final Position target) { Piece sourcePiece = findPieceBy(source); pieces.remove(source); pieces.put(target, sourcePiece); @@ -51,7 +51,7 @@ public Collection findAttackPaths(final Position target) { .collect(Collectors.toList()); } - public void wasAttackedBy(final Position position) { + public void removePiece(final Position position) { if (hasNoPieceOn(position)) { return; } diff --git a/src/test/java/chess/domain/team/TeamTest.java b/src/test/java/chess/domain/team/TeamTest.java index 6809643..8564c9f 100644 --- a/src/test/java/chess/domain/team/TeamTest.java +++ b/src/test/java/chess/domain/team/TeamTest.java @@ -52,7 +52,7 @@ void update_board() { Position target = Position.of("b3"); //when - team.move(source, target); + team.movePiece(source, target); //then assertThat(team.hasNoPieceOn(source)).isTrue(); @@ -69,7 +69,7 @@ void update_source_position_empty() { //when, then assertThatIllegalArgumentException() - .isThrownBy(() -> team.move(source, target)) + .isThrownBy(() -> team.movePiece(source, target)) .withMessage("해당 위치에 기물이 존재하지 않습니다."); } @@ -99,7 +99,7 @@ void find_attack_paths() { new Path(Position.of("b2")), new Path(Position.of("d2")) ); - team.wasAttackedBy(Position.of("b1")); + team.removePiece(Position.of("b1")); // when Collection attackPaths = team.findAttackPaths(target); @@ -118,7 +118,7 @@ void was_attacked_by() { Position target = Position.of("e2"); // when - team.wasAttackedBy(target); + team.removePiece(target); // then assertThat(team.hasNoPieceOn(target)).isTrue(); @@ -144,7 +144,7 @@ void has_king_on() { void is_king_dead() { //given Team team = Team.white(); - team.wasAttackedBy(Position.of("e1")); + team.removePiece(Position.of("e1")); //when boolean kingDead = team.isKingDead(); From b57459347e4b4f0211cc94437cdec6dea22e6dad Mon Sep 17 00:00:00 2001 From: Jiwoo Kim Date: Wed, 29 Sep 2021 18:21:24 +0900 Subject: [PATCH 38/38] =?UTF-8?q?refactor:=20=ED=94=8C=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EC=9D=98=20=ED=94=8C=EB=A0=88=EC=9D=B4=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ConsoleChessController.java | 5 +- .../chess/domain/{ => game}/ChessGame.java | 12 ++--- src/main/java/chess/domain/game/Player.java | 38 ++++++++++++++ src/main/java/chess/domain/game/Turn.java | 52 +++++++++++++++++++ src/main/java/chess/dto/BoardDto.java | 2 +- .../chess/{domain/team => dto}/Scores.java | 2 +- src/main/java/chess/dto/TurnDto.java | 30 +++++++++++ src/main/java/chess/service/ChessService.java | 9 ++-- .../java/chess/view/ConsoleOutputView.java | 11 ++-- src/main/java/chess/view/OutputView.java | 5 +- .../domain/{ => game}/ChessGameTest.java | 4 +- src/test/java/chess/domain/game/TurnTest.java | 52 +++++++++++++++++++ 12 files changed, 200 insertions(+), 22 deletions(-) rename src/main/java/chess/domain/{ => game}/ChessGame.java (95%) create mode 100644 src/main/java/chess/domain/game/Player.java create mode 100644 src/main/java/chess/domain/game/Turn.java rename src/main/java/chess/{domain/team => dto}/Scores.java (93%) create mode 100644 src/main/java/chess/dto/TurnDto.java rename src/test/java/chess/domain/{ => game}/ChessGameTest.java (99%) create mode 100644 src/test/java/chess/domain/game/TurnTest.java diff --git a/src/main/java/chess/controller/ConsoleChessController.java b/src/main/java/chess/controller/ConsoleChessController.java index f36a477..6f9eb4d 100644 --- a/src/main/java/chess/controller/ConsoleChessController.java +++ b/src/main/java/chess/controller/ConsoleChessController.java @@ -1,7 +1,8 @@ package chess.controller; import chess.domain.command.Command; -import chess.domain.team.Scores; +import chess.dto.Scores; +import chess.dto.TurnDto; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; import chess.service.ChessService; @@ -40,7 +41,7 @@ public void run() { private void runOneTurn() { try { - String currentTurn = chessService.getCurrentTurnDto(); + TurnDto currentTurn = chessService.getCurrentTurnDto(); outputView.printTurn(currentTurn); Command command = new Command(inputView.getCommand()); diff --git a/src/main/java/chess/domain/ChessGame.java b/src/main/java/chess/domain/game/ChessGame.java similarity index 95% rename from src/main/java/chess/domain/ChessGame.java rename to src/main/java/chess/domain/game/ChessGame.java index d20ac07..3550817 100644 --- a/src/main/java/chess/domain/ChessGame.java +++ b/src/main/java/chess/domain/game/ChessGame.java @@ -1,12 +1,12 @@ -package chess.domain; +package chess.domain.game; import chess.domain.board.Position; import chess.domain.command.MoveParameters; import chess.domain.piece.move.Path; import chess.domain.piece.type.Piece; import chess.domain.team.Color; -import chess.domain.team.Scores; import chess.domain.team.Team; +import chess.dto.Scores; import chess.exception.EmptyPositionException; import static chess.domain.team.Color.BLACK; @@ -16,12 +16,12 @@ public class ChessGame { private final Team whiteTeam; private final Team blackTeam; - private Color currentTurn; + private final Turn currentTurn; public ChessGame() { this.whiteTeam = Team.white(); this.blackTeam = Team.black(); - this.currentTurn = WHITE; + this.currentTurn = Turn.initialTurn(); } public void move(final MoveParameters moveParameters) { @@ -30,7 +30,7 @@ public void move(final MoveParameters moveParameters) { validateParameters(source, target); movePiece(source, target); - currentTurn = currentTurn.flip(); + currentTurn.next(); } private void validateParameters(final Position source, final Position target) { @@ -124,7 +124,7 @@ public boolean isBothKingAlive() { return whiteTeam.isKingAlive() && blackTeam.isKingAlive(); } - public Color getCurrentTurn() { + public Turn getCurrentTurn() { return currentTurn; } diff --git a/src/main/java/chess/domain/game/Player.java b/src/main/java/chess/domain/game/Player.java new file mode 100644 index 0000000..b0d2feb --- /dev/null +++ b/src/main/java/chess/domain/game/Player.java @@ -0,0 +1,38 @@ +package chess.domain.game; + +import chess.domain.team.Color; + +public class Player { + + private static final String DEFAULT_WHITE_NAME = "WHITE"; + private static final String DEFAULT_BLACK_NAME = "BLACK"; + + private final String name; + + public static Player of(final String name) { + return new Player(name); + } + + public static Player defaultPlayer(final Color color) { + if (color.isWhite()) { + return defaultWhite(); + } + return defaultBlack(); + } + + private static Player defaultWhite() { + return Player.of(DEFAULT_WHITE_NAME); + } + + private static Player defaultBlack() { + return Player.of(DEFAULT_BLACK_NAME); + } + + private Player(final String name) { + this.name = name; + } + + public String name() { + return name; + } +} diff --git a/src/main/java/chess/domain/game/Turn.java b/src/main/java/chess/domain/game/Turn.java new file mode 100644 index 0000000..e47358f --- /dev/null +++ b/src/main/java/chess/domain/game/Turn.java @@ -0,0 +1,52 @@ +package chess.domain.game; + +import chess.domain.team.Color; + +import java.util.*; + +public class Turn { + + private final Map> players; + private Color color; + private int count; + + public static Turn initialTurn(final Map> players) { + return new Turn(players); + } + + public static Turn initialTurn() { + return initialTurn(defaultPlayers()); + } + + private Turn(final Map> players) { + this.players = Collections.unmodifiableMap(players); + this.color = Color.WHITE; + this.count = 0; + } + + private static Map> defaultPlayers() { + Map> map = new EnumMap<>(Color.class); + Arrays.stream(Color.values()) + .forEach(color -> map.put(color, Collections.singletonList(Player.defaultPlayer(color)))); + return map; + } + + public void next() { + color = color.flip(); + count++; + } + + public boolean isWhite() { + return color.isWhite(); + } + + public Color team() { + return color; + } + + public Player player() { + List candidates = this.players.get(color); + int index = count / 2 % candidates.size(); + return candidates.get(index); + } +} diff --git a/src/main/java/chess/dto/BoardDto.java b/src/main/java/chess/dto/BoardDto.java index c68cd11..a9a2dd1 100644 --- a/src/main/java/chess/dto/BoardDto.java +++ b/src/main/java/chess/dto/BoardDto.java @@ -1,7 +1,7 @@ package chess.dto; -import chess.domain.ChessGame; import chess.domain.board.Position; +import chess.domain.game.ChessGame; import chess.domain.piece.type.Piece; import chess.domain.piece.type.PieceType; import chess.exception.EmptyPositionException; diff --git a/src/main/java/chess/domain/team/Scores.java b/src/main/java/chess/dto/Scores.java similarity index 93% rename from src/main/java/chess/domain/team/Scores.java rename to src/main/java/chess/dto/Scores.java index 74b28b8..322e3f6 100644 --- a/src/main/java/chess/domain/team/Scores.java +++ b/src/main/java/chess/dto/Scores.java @@ -1,4 +1,4 @@ -package chess.domain.team; +package chess.dto; public class Scores { diff --git a/src/main/java/chess/dto/TurnDto.java b/src/main/java/chess/dto/TurnDto.java new file mode 100644 index 0000000..40d406d --- /dev/null +++ b/src/main/java/chess/dto/TurnDto.java @@ -0,0 +1,30 @@ +package chess.dto; + +import chess.domain.game.Player; +import chess.domain.game.Turn; +import chess.domain.team.Color; + +public class TurnDto { + + private final String team; + private final String playerName; + + private TurnDto(String team, String playerName) { + this.team = team; + this.playerName = playerName; + } + + public static TurnDto of(Turn currentTurn) { + Color color = currentTurn.team(); + Player player = currentTurn.player(); + return new TurnDto(color.name(), player.name()); + } + + public String getTeam() { + return team; + } + + public String getPlayerName() { + return playerName; + } +} diff --git a/src/main/java/chess/service/ChessService.java b/src/main/java/chess/service/ChessService.java index 3d5c54d..af2bad1 100644 --- a/src/main/java/chess/service/ChessService.java +++ b/src/main/java/chess/service/ChessService.java @@ -1,11 +1,12 @@ package chess.service; -import chess.domain.ChessGame; import chess.domain.command.Command; import chess.domain.command.MoveParameters; +import chess.domain.game.ChessGame; import chess.domain.team.Color; -import chess.domain.team.Scores; import chess.dto.BoardDto; +import chess.dto.Scores; +import chess.dto.TurnDto; import chess.exception.ForcedTerminationException; import chess.exception.ScoresRequestedException; @@ -58,8 +59,8 @@ public Map getBoardDto() { return BoardDto.of(chessGame); } - public String getCurrentTurnDto() { - return chessGame.getCurrentTurn().name(); + public TurnDto getCurrentTurnDto() { + return TurnDto.of(chessGame.getCurrentTurn()); } public String getWinnerDto() { diff --git a/src/main/java/chess/view/ConsoleOutputView.java b/src/main/java/chess/view/ConsoleOutputView.java index 05d4a99..b3df4ab 100644 --- a/src/main/java/chess/view/ConsoleOutputView.java +++ b/src/main/java/chess/view/ConsoleOutputView.java @@ -1,13 +1,14 @@ package chess.view; -import chess.domain.team.Scores; +import chess.dto.Scores; +import chess.dto.TurnDto; import java.util.Map; public class ConsoleOutputView implements OutputView { private static final String HEADER = "> "; - private static final String TURN_FORMAT = HEADER + "%s의 차례입니다.%n"; + private static final String TURN_FORMAT = HEADER + "%s팀의 %s 차례입니다.%n"; private static final String WINNER_FORMAT = HEADER + "%s의 승리입니다. 축하합니다.%n"; private static final int FILE = 0; @@ -62,8 +63,10 @@ public void printScores(final Scores scores) { } @Override - public void printTurn(String currentTurn) { - System.out.printf(TURN_FORMAT, currentTurn); + public void printTurn(TurnDto currentTurn) { + String team = currentTurn.getTeam(); + String playerName = currentTurn.getPlayerName(); + System.out.printf(TURN_FORMAT, team, playerName); } @Override diff --git a/src/main/java/chess/view/OutputView.java b/src/main/java/chess/view/OutputView.java index bb21abe..947fd37 100644 --- a/src/main/java/chess/view/OutputView.java +++ b/src/main/java/chess/view/OutputView.java @@ -1,6 +1,7 @@ package chess.view; -import chess.domain.team.Scores; +import chess.dto.Scores; +import chess.dto.TurnDto; import java.util.Map; @@ -11,7 +12,7 @@ public interface OutputView { void printScores(Scores scores); - void printTurn(String currentTurn); + void printTurn(TurnDto currentTurn); void printWinner(String winner); diff --git a/src/test/java/chess/domain/ChessGameTest.java b/src/test/java/chess/domain/game/ChessGameTest.java similarity index 99% rename from src/test/java/chess/domain/ChessGameTest.java rename to src/test/java/chess/domain/game/ChessGameTest.java index 93b6e61..9813b81 100644 --- a/src/test/java/chess/domain/ChessGameTest.java +++ b/src/test/java/chess/domain/game/ChessGameTest.java @@ -1,4 +1,4 @@ -package chess.domain; +package chess.domain.game; import chess.domain.board.Position; import chess.domain.command.MoveParameters; @@ -6,7 +6,7 @@ import chess.domain.piece.type.Piece; import chess.domain.piece.type.Rook; import chess.domain.team.Color; -import chess.domain.team.Scores; +import chess.dto.Scores; import chess.exception.EmptyPositionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/chess/domain/game/TurnTest.java b/src/test/java/chess/domain/game/TurnTest.java new file mode 100644 index 0000000..8107f13 --- /dev/null +++ b/src/test/java/chess/domain/game/TurnTest.java @@ -0,0 +1,52 @@ +package chess.domain.game; + +import chess.domain.team.Color; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TurnTest { + + @Test + @DisplayName("플레이 차례인 플레이어를 반환한다.") + void player_turn() { + // given + Turn turn = Turn.initialTurn(); + + // when + turn.next(); + Player player = turn.player(); + + // then + assertThat(player.name()).isEqualTo("BLACK"); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8}) + @DisplayName("플레이어가 여러 명일 때 플레이 차례인 플레이어를 반환한다.") + void player_turn_multiple(int count) { + // given + List names = Arrays.asList("w1", "b1", "w2", "b2"); + Map> players = new EnumMap<>(Color.class); + players.put(Color.WHITE, Arrays.asList(Player.of(names.get(0)), Player.of(names.get(2)))); + players.put(Color.BLACK, Arrays.asList(Player.of(names.get(1)), Player.of(names.get(3)))); + Turn turn = Turn.initialTurn(players); + + // when + for (int i = 0; i < count; i++) { + turn.next(); + } + Player player = turn.player(); + + // then + assertThat(player.name()).isEqualTo(names.get(count % 4)); + } +}