Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1, 2단계 - 체스] 져니(이지원) 미션 제출합니다. #485

Merged
merged 102 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
5274392
docs: 기능 명세서를 작성한다
Cl8D Mar 14, 2023
03ee6d5
feat(Position) : 체스판 목표 위치 반환 기능을 구현한다
Cl8D Mar 14, 2023
21c2ca1
feat(CampType): 입력받은 위치에 따라 진영을 나누는 기능을 추가한다
Cl8D Mar 14, 2023
0d3d52b
feat(Camp): 입력받은 위치 정보를 검증하고, 진영을 나눈다
Cl8D Mar 14, 2023
70d1806
feat: 체스판 목표 위치 반환 기능을 구현한다
Cl8D Mar 14, 2023
b74cd59
feat: 입력받은 위치에 따라 진영을 나누는 기능을 추가한다
Cl8D Mar 14, 2023
dc81448
feat: 입력받은 위치 정보를 검증하고, 진영을 나눈다
Cl8D Mar 14, 2023
1a837b3
feat: 진영을 검은색과 흰색으로 분리한다
Cl8D Mar 15, 2023
71a8a9b
feat: 체스말의 종류를 관리한다
Cl8D Mar 15, 2023
248d295
feat: 체스판을 초기화한다
Cl8D Mar 15, 2023
03c9a07
refactor: 도메인별로 패키지를 분리한다
Cl8D Mar 15, 2023
8c8c198
feat: 체스판 초기화 시 체스말에 대한 정보만 저장한다
Cl8D Mar 15, 2023
d340443
feat: 사용자가 입력한 명령어를 검증한다
Cl8D Mar 15, 2023
a55dd78
feat: 체스말이 흰색 진영인지 확인한다
Cl8D Mar 15, 2023
f073c49
feat: 게임을 시작하면 체스판을 초기화한다
Cl8D Mar 15, 2023
fdff963
feat: 입력받은 위치를 정수값으로 변환한다
Cl8D Mar 15, 2023
af16004
feat: 특정 위치에 체스말이 존재하는지 확인한다
Cl8D Mar 15, 2023
6ae4743
feat: 특정 위치에 존재하는 체스말을 반환한다
Cl8D Mar 15, 2023
0d03670
feat: 퀸이 이동할 수 있는 모든 경로를 확인하고, 이동 가능한지 판단한다
Cl8D Mar 15, 2023
1b8fd8c
feat: 가능 방향에 체스말이 존재하는지 검증하는 기능을 Move의 책임으로 변경한다
Cl8D Mar 15, 2023
4472050
feat: 움직임을 표현하는 인터페이스를 추가한다
Cl8D Mar 15, 2023
2ea85ee
feat: 룩이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
aecdcf9
test: CampType의 수정에 따라 테스트를 변경한다
Cl8D Mar 15, 2023
a7d2f08
test: 퀸, 룩의 테스트 조건을 수정한다
Cl8D Mar 15, 2023
0d8d1c0
feat: 비숍이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
2063c36
refactor: 각 체스말들이 최대 이동 거리를 상수화한다
Cl8D Mar 15, 2023
62f5fb5
feat: 킹이 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
26a7030
feat: 나이트가 이동할 수 있는지 판단한다
Cl8D Mar 15, 2023
0bbac7f
feat: 폰이 이동할 수 있는지 판단한다
Cl8D Mar 16, 2023
f2d3668
feat: 입력받은 위치에 대한 검증을 PositionConverter의 역할로 위임한다
Cl8D Mar 16, 2023
ed9ac2b
refactor: 명령어를 입력받는 메서드의 네이밍을 변경한다
Cl8D Mar 16, 2023
fd4667c
feat: 예외가 발생하면 재시도하는 기능을 제거한다
Cl8D Mar 16, 2023
144ce4e
feat: 플레이할 진영을 번갈아가며 반환한다
Cl8D Mar 16, 2023
7f0ebd0
feat: 위치가 입력받은 제한값을 넘어가는지 판단한다.
Cl8D Mar 16, 2023
ed54a8c
feat: 목표 위치로 이동하기 위한 단위 벡터를 계산한다
Cl8D Mar 16, 2023
c07d5eb
feat: 특정 위치에 존재하는 체스말을 제거한다
Cl8D Mar 16, 2023
1f137d1
feat: 입력받은 위치에 체스말을 둔다
Cl8D Mar 16, 2023
0f01ec7
feat: 특정 체스말이 시작 위치에서 종료 위치까지 이동 가능한지 판단한다
Cl8D Mar 16, 2023
22beb9e
feat: 입력받은 위치를 복사한 새로운 위치를 반환한다
Cl8D Mar 16, 2023
d556de0
feat: 특정 말의 종류에 따라서 시작 위치에서 종료 위치로 이동 가능한지 판단한다
Cl8D Mar 16, 2023
f2bb1c1
test: rank와 file의 위치를 수정한다
Cl8D Mar 16, 2023
edb495e
feat: 체스말의 진영과 종류를 판단하는 기능을 추가한다
Cl8D Mar 16, 2023
be1433f
feat: 현재 위치의 rank가 입력받은 rank보다 큰지 판단한다.
Cl8D Mar 16, 2023
f07c41d
feat: 첫 턴에 WHITE 진영의 폰과 나이트만 움직이는지 검증한다.
Cl8D Mar 16, 2023
4e4f5b0
feat: 체스말이 입력받은 진영에 속하는지 판단한다
Cl8D Mar 16, 2023
689228d
feat: 체스말이 시작 위치에서 도착 위치로 이동 가능한지 판단한다
Cl8D Mar 16, 2023
547e146
feat: 각 말에 대한 객체를 분리한다
Cl8D Mar 16, 2023
27ca651
test: 각 말이 이동 가능한지 테스트한다
Cl8D Mar 16, 2023
045aca6
refactor: 이동 가능한지 판단하는 메서드에 대해 분리한다
Cl8D Mar 16, 2023
7605501
Merge remote-tracking branch 'origin/step1' into step1
Cl8D Mar 16, 2023
77a5101
refactor: chessGame에서 체스판을 만드는 로직을 수정한다
Cl8D Mar 16, 2023
19f8ede
feat: 체스판의 생성 로직을 변경한다
Cl8D Mar 16, 2023
7bcec7b
feat: 체스말의 움직임 전략을 static하게 관리한다
Cl8D Mar 16, 2023
11ce918
refactor: 불필요한 파일을 제거한다
Cl8D Mar 16, 2023
45b6bf4
feat: 사용자가 입력한 명령어에 대한 관리 방식을 변경한다
Cl8D Mar 16, 2023
7578073
feat: 게임의 상태를 start, end, move로 관리한다
Cl8D Mar 16, 2023
3250f4f
feat: 체스 게임의 시작 기능을 추가하고, view를 static하게 관리한다
Cl8D Mar 16, 2023
b7c8649
test: 테스트 케이스에 대한 오류를 제거한다
Cl8D Mar 17, 2023
c7b3c4c
test: 게임의 진행 상태에 대해 테스트 코드를 작성한다
Cl8D Mar 17, 2023
1e46715
feat: 나이트의 움직임 조건에 대해서 변경한다
Cl8D Mar 17, 2023
ee5169c
feat: 체스 게임을 진행하는 기능을 추가한다
Cl8D Mar 17, 2023
f8876f1
feat: 예외 발생 시 재입력을 받도록 수정한다
Cl8D Mar 17, 2023
921b7ee
feat: 체스말이 이동 가능한 위치에 대해 구하는 로직을 수정한다
Cl8D Mar 17, 2023
dc5b490
feat: 체스말의 공격 가능 여부와 시작 지점, 종료 지점까지의 경로가 가능한지 판단하는 기능을 추가한다
Cl8D Mar 17, 2023
d4f94a0
feat: 각각의 체스말에 대해서 위임하는 메서드를 추가한다
Cl8D Mar 17, 2023
95a7c9e
test: 폰의 이동을 테스트하는 경우를 수정한다
Cl8D Mar 17, 2023
5e405a6
feat: 체스 게임을 진행하는 전반적인 로직을 수정한다
Cl8D Mar 17, 2023
334312a
feat: 이동 가능한 위치에 대해서 Location 클래스로 관리한다
Cl8D Mar 17, 2023
48511d1
refactor: 체스 게임의 실행 메서드의 접근 제어자를 private로 변경한다
Cl8D Mar 17, 2023
062a2d4
refactor: 패키지에 대한 이동을 진행한다
Cl8D Mar 17, 2023
e19dabf
refactor: 상수에 대한 매직 넘버 처리를 진행한다
Cl8D Mar 17, 2023
bc1d707
feat: position에 대한 getter를 제거한다
Cl8D Mar 17, 2023
320e7cf
feat: 종료 시에는 체스판이 출력되지 않도록 수정한다
Cl8D Mar 17, 2023
6872471
test: 불필요한 로깅을 제거한다
Cl8D Mar 20, 2023
4fcc154
test: 기존의 진영에서 다른 진영으로 턴을 변경하는 기능을 테스트한다
Cl8D Mar 20, 2023
50dcdb3
feat: PositionConverter의 생성자를 private로 막는다
Cl8D Mar 20, 2023
972fb93
test: 각 체스말이 움직일 수 있는 위치를 반환하는 기능을 테스트한다
Cl8D Mar 20, 2023
e3e4fab
refactor: Moveable 인터페이스와 Move#getAllPositions의 네이밍을 수정한다
Cl8D Mar 20, 2023
69ded27
feat: 뷰와 도메인의 의존성을 줄인다
Cl8D Mar 20, 2023
c0a18ac
refactor: move 패키지를 piece의 하위 패키지로 이동한다
Cl8D Mar 20, 2023
b8b123a
feat: 현재 위치와 입력받은 위치가 동일한지 판단하는 기능을 추가한다
Cl8D Mar 20, 2023
947f6a0
feat: position이 over 되었는지에 대한 조건을 position에서 관리하도록 변경한다
Cl8D Mar 20, 2023
5a2d334
refactor: 각 체스말들에 대한 구체 클래스를 생성하고, MoveRule을 주입받는 형식으로 변경한다
Cl8D Mar 20, 2023
671085f
refactor: 패키지 이동을 진행한다
Cl8D Mar 20, 2023
f857825
feat: chessGame에서 폰에 대한 이동을 검증하는 기능을 제거한다
Cl8D Mar 20, 2023
3807a70
feat: 각 체스말이 가로 막혔을 때의 행위를 체스판에서 진행하도록 변경한다
Cl8D Mar 20, 2023
9a26d3d
refactor: chessGame 로직의 파라미터 수를 줄인다
Cl8D Mar 20, 2023
fb354ed
refactor: 컨트롤러와 start, end, move 상태에 대한 이름을 변경한다
Cl8D Mar 20, 2023
5d34902
refactor: 잘못된 테스트 코드 네이밍 수정
Cl8D Mar 21, 2023
64c1803
refactor: 메서드의 파라미터 수를 줄인다
Cl8D Mar 21, 2023
a1438cb
feat: 폰의 공격 조건에 대하여 추가한다
Cl8D Mar 21, 2023
fc3e731
feat: 폰을 이동시키는 행위에 대해 검증 기능을 추가한다
Cl8D Mar 21, 2023
1b2d7e9
test: 각 말의 공격에 대한 테스트를 추가한다
Cl8D Mar 21, 2023
02cbed3
refactor: 진영을 비교하는 기능의 네이밍을 변경한다
Cl8D Mar 22, 2023
aa3786a
refactor: 각 기물의 이동에 대한 클래스 이름을 변경한다
Cl8D Mar 22, 2023
cf6475c
refactor: Status 인터페이스의 네이밍을 변경한다
Cl8D Mar 22, 2023
36ee143
feat: 위치에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
eb36fae
feat: 기물에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
26e3db4
feat: 체스판에 대한 서비스 레이어를 추가한다
Cl8D Mar 22, 2023
8814a1a
feat: 도메인과 뷰의 의존성을 제거한다
Cl8D Mar 22, 2023
d75f190
feat: 필드에 대해서 연쇄 호출을 제거한다
Cl8D Mar 22, 2023
1753cf5
fix: 체스말의 존재 검증 로직을 수정한다
Cl8D Mar 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/main/java/chess/domain/chess/ChessGame.java
Cl8D marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,14 @@ private void validateTargetSameCamp(final Position target) {

private boolean canAttack(final Position source, final Position target) {
final Piece sourcePiece = chessBoard.checkPiece(source);
if (sourcePiece.isPawn() && sourcePiece.canAttack(source, target) && !chessBoard.contains(target)) {
throw new IllegalArgumentException("공격할 수 있는 위치가 아닙니다.");
}
return sourcePiece.canAttack(source, target);
final boolean isTargetExist = chessBoard.contains(target);
return sourcePiece.canAttack(source, target, isTargetExist);
}

private boolean canMove(final Position source, final Position target) {
final Piece piece = chessBoard.checkPiece(source);
return piece.canMove(source, target);
boolean isTargetExist = chessBoard.contains(target);
return piece.canMove(source, target, isTargetExist);
}

private void movePiece(final Position source, final Position target) {
Expand Down
33 changes: 20 additions & 13 deletions src/main/java/chess/domain/piece/Piece.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,37 @@ public boolean isSameCamp(final CampType diffType) {
return campType == diffType;
}

public boolean canMove(final Position source, final Position target) {
public boolean canMove(final Position source, final Position target, final boolean isTargetExist) {
if (pieceType == PieceType.PAWN && moveRule.canMove(source, target)) {
return validatePawnFirstMove(source, target);
return validatePawnMove(source, target, isTargetExist);
}
return moveRule.canMove(source, target);
}

private boolean validatePawnFirstMove(final Position source, final Position target) {
if ((isSameCamp(CampType.WHITE) && source.isRankSame(WHITE_PAWN_FIRST_MOVE)) ||
(isSameCamp(CampType.BLACK) && source.isRankSame(BLACK_PAWN_FIRST_MOVE))) {
return true;
public boolean canAttack(final Position source, final Position target, final boolean isTargetExist) {
if (pieceType == PieceType.PAWN && moveRule.canAttack(source, target) && !isTargetExist) {
throw new IllegalArgumentException("폰이 공격할 수 있는 위치가 아닙니다.");
}
if (Math.abs(target.calculateRankGap(source)) != PAWN_FIRST_MOVE) {
throw new IllegalArgumentException("폰은 처음 이후 1칸만 전진할 수 있습니다.");
return moveRule.canAttack(source, target);
}

private boolean validatePawnMove(final Position source, final Position target, final boolean isTargetExist) {
if (isPawnFirstMove(source)) {
return !isTargetExist;
}
return true;
return validatePawnOneMove(source, target, isTargetExist);
}

public boolean canAttack(final Position source, final Position target) {
return moveRule.canAttack(source, target);
private boolean isPawnFirstMove(final Position source) {
return (isSameCamp(CampType.WHITE) && source.isRankSame(WHITE_PAWN_FIRST_MOVE)) ||
(isSameCamp(CampType.BLACK) && source.isRankSame(BLACK_PAWN_FIRST_MOVE));
}

public boolean isPawn() {
return pieceType == PieceType.PAWN;
private boolean validatePawnOneMove(final Position source, final Position target, final boolean isTargetExist) {
if (Math.abs(target.calculateRankGap(source)) != PAWN_FIRST_MOVE) {
throw new IllegalArgumentException("폰은 처음 이후 1칸만 전진할 수 있습니다.");
}
return !isTargetExist;
}

@Override
Expand Down
115 changes: 115 additions & 0 deletions src/test/java/chess/domain/piece/PieceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package chess.domain.piece;

import chess.domain.chess.CampType;
import chess.domain.piece.move.piece.KingMove;
import chess.domain.piece.move.piece.PawnMove;
import chess.domain.piece.move.piece.QueenMove;
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.assertThatThrownBy;

class PieceTest {

@Test
@DisplayName("두 체스말이 동일한 진영인지 확인한다.")
void compareCamp() {
// given
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());
final Piece king = new Piece(PieceType.KING, CampType.BLACK, new KingMove());
final Piece queen = new Piece(PieceType.QUEEN, CampType.WHITE, new QueenMove());

// when
final boolean actualFalse = pawn.compareCamp(king);
final boolean actualTrue = pawn.compareCamp(queen);

// then
assertThat(actualFalse)
.isFalse();

assertThat(actualTrue)
.isTrue();
}

@Test
@DisplayName("체스말이 입력받은 진영에 속하는지 판단한다.")
void isSameCamp() {
// given
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());

// when
boolean actualTrue = pawn.isSameCamp(CampType.WHITE);
boolean actualFalse = pawn.isSameCamp(CampType.BLACK);

// then
assertThat(actualTrue)
.isTrue();

assertThat(actualFalse)
.isFalse();
}

@ParameterizedTest(name = "폰은 처음에 기물이 없는 곳으로 최대 2칸 이동할 수 있다.")
@CsvSource(value = {"2:false:true", "3:false:true", "4:false:false",
"2:true:false", "3:true:false", "4:true:false"}, delimiter = ':')
void pawn_canMove(final int targetRank, final boolean isTargetExist, final boolean expected) {
// given
final Position source = new Position(1, 0);
final Position target = new Position(targetRank, 0);
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());

// when
boolean actual = pawn.canMove(source, target, isTargetExist);

// then
assertThat(actual)
.isSameAs(expected);
}

@Test
@DisplayName("폰은 처음 이동이 아니면 1칸만 이동이 가능하다.")
void pawn_canMoveFail() {
// given
final Position source = new Position(2, 0);
final Position target = new Position(4, 0);
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());

// when, then
assertThatThrownBy(() -> pawn.canMove(source, target, false))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("폰은 처음 이후 1칸만 전진할 수 있습니다.");
}

@Test
@DisplayName("폰은 공격 가능한 위치에 상대 기물이 존재하지 않는 경우 예외가 발생한다.")
void pawn_canAttackFail() {
// given
final Position source = new Position(1, 1);
final Position target = new Position(2, 2);
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());

// when, then
assertThatThrownBy(() -> pawn.canAttack(source, target, false))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("폰이 공격할 수 있는 위치가 아닙니다.");
}

@Test
@DisplayName("폰은 공격 가능한 위치에 상대 기물이 존재하는 경우 공격할 수 있다.")
void pawn_canAttack() {
// given
final Position source = new Position(1, 1);
final Position target = new Position(2, 2);
final Piece pawn = new Piece(PieceType.PAWN, CampType.WHITE, new PawnMove());

// when
boolean actual = pawn.canAttack(source, target, true);

// then
assertThat(actual)
.isTrue();
}
}