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단계 - 체스] 켈리(양성욱) 미션 제출합니다. #15

Open
wants to merge 54 commits into
base: kelly6bf
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a05391b
docs: 기능 명세서 초안 작성
kelly6bf Mar 19, 2024
672926e
feat(position): Position 인스턴스 생성 로직 구현
kelly6bf Mar 19, 2024
3bffae7
feat(inputView): 사용자로부터 명령을 입력받는 기능 구현
kelly6bf Mar 19, 2024
65a1a43
fix(position): 위치 객체에서 체스 도메인 용어를 사용하도록 변경
kelly6bf Mar 19, 2024
ea61d0e
feat(board): 체스판의 말을 시작 위치로 배치하는 기능 구현
kelly6bf Mar 19, 2024
00cde4d
feat(outputView): 체스판 출력 기능 구현
kelly6bf Mar 19, 2024
989bcde
docs(readme): 2단계 기능 요구사항 추가
kelly6bf Mar 20, 2024
71873d6
refactor(/position): 위치정보와 관련된 클래스들을 한 패키지로 모음
kelly6bf Mar 20, 2024
f568096
refactor(position): Position 생성자 파라미터 순서 변경
kelly6bf Mar 20, 2024
c08c946
feat(unitVector): 8방위를 나타내는 단위 벡터 객체 구현
kelly6bf Mar 20, 2024
024c0ec
feat(position): 현재 위치에서 백터를 누적한 새로운 위치를 생성하는 로직 구현
kelly6bf Mar 20, 2024
2c97912
feat(orthogonalMoveStrategy): 직선 방향으로 이동 가능 여부를 확인하는 전략 구현
kelly6bf Mar 20, 2024
2474fb9
feat(continuousMoveStrategy): 이동 가능한 방향벡터와 이동 횟수 제한을 가지는 연속 이동 전략 구현
kelly6bf Mar 20, 2024
244b111
refactor(boardInitializer):체스판 초기화 책임 분리
kelly6bf Mar 20, 2024
76b901f
refactor(continuousMoveStrategy): 스트림 내의 Predicate 메서드 분리
kelly6bf Mar 20, 2024
d5a7cc6
feat(board): 체스 말을 이동시키는 기능 구현
kelly6bf Mar 20, 2024
eab0b5c
feat(knightMoveStrategy): 나이트 이동 전략 구현
kelly6bf Mar 20, 2024
b08219b
feat(pawnMoveStrategy): 폰의 이동 전략 구현
kelly6bf Mar 20, 2024
685eac4
test(boardTest): 기물을 이동시키는 로직의 테스트 추가
kelly6bf Mar 20, 2024
832de3e
refactor(board): 이동 불가능한 경우별 예외에 메시지 구분
kelly6bf Mar 20, 2024
161743d
feat(inputView): 사용자의 명령을 입력받는 기능 구현
kelly6bf Mar 20, 2024
ba7a380
feat(chessController): 팀을 번갈아 가며 게임을 진행하는 기능 구현
kelly6bf Mar 20, 2024
a87e56b
test(knightMoveStrategyTest): 나이트 움직임에 대한 테스트 코드 추가
kelly6bf Mar 21, 2024
80b5cc2
test(pawnMoveStrategyTest): 폰 움직임에 대한 테스트 코드 추가
kelly6bf Mar 21, 2024
b9c04c9
style(positionTest): 테스트 설명 수정
kelly6bf Mar 21, 2024
e4dbb49
feat(pieceFactory): PieceType을 전달받아 올바른 Piece를 생성하는 객체 추가
kelly6bf Mar 21, 2024
e4f2c11
test(boardTest): Board 출발지 -> 목적지 기물 배치 성공 & 실패 테스트 추가
kelly6bf Mar 21, 2024
5df2629
refactor(continuousMoveStrategy): 메서드 이름 변경
kelly6bf Mar 21, 2024
78937a5
style(position): 미사용 TODO 삭제
kelly6bf Mar 21, 2024
2c67d48
refactor(chessController): 사용자 입력에 따라 게임을 진행하는 기능 구현
kelly6bf Mar 21, 2024
42f87da
refactor(requestDto): 명령 인자에서 Optional 제거
kelly6bf Mar 21, 2024
a99a608
fix(pawnMoveStrategy): 폰이 직선 이동에서 상대 기물을 공격하는 버그 수정
kelly6bf Mar 21, 2024
71d0c77
refactor: position 도메인 객체 수정
kelly6bf Mar 22, 2024
4db0dee
refactor: direction 도메인 추상화
kelly6bf Mar 23, 2024
5a29fd5
feat: Bishop 구현
kelly6bf Mar 23, 2024
1d3217d
feat: Rook 구현
kelly6bf Mar 23, 2024
a99b95b
feat: King 구현
kelly6bf Mar 23, 2024
6b75092
feat: Queen 구현
kelly6bf Mar 23, 2024
d8af75b
feat: Knight 구현
kelly6bf Mar 23, 2024
d2a273e
feat: Pawn 구현
kelly6bf Mar 23, 2024
0de34b7
refactor: board 패키지 생성
kelly6bf Mar 23, 2024
332efb5
refactor: Piece 도메인 클래스에 Type 추가
kelly6bf Mar 23, 2024
261b714
remove: 기존 코드 제거
kelly6bf Mar 23, 2024
c25b647
refactor: piece 패키지 정리
kelly6bf Mar 24, 2024
e9a1feb
refactor: board 도메인 개선
kelly6bf Mar 24, 2024
225b8d5
refactor: view & dto 개선
kelly6bf Mar 24, 2024
42dab99
docs: 기능 명세서 개선
kelly6bf Mar 24, 2024
10efcaa
refactor: 상태 페턴 적용
kelly6bf Mar 25, 2024
b2def81
other: 상태에 Controller를 넘기는 케이스 코드 추가
kelly6bf Mar 25, 2024
ccadc31
refactor: import 와일드 카드 제거
kelly6bf Mar 25, 2024
ae3d5fb
refactor: 기물 이동 메서드명 변경 및 불필요한 메서드 분리 제거
kelly6bf Mar 25, 2024
80a4ea6
refactor: Piece 이동 시 Board 클래스를 입력받도록 리펙토링
kelly6bf Mar 26, 2024
6425ad6
refactor: Bishop & Rook의 방향 유효성 검증을 CommonMovementDirection에 위임
kelly6bf Mar 26, 2024
cc8c233
refactor: BoardInitializer 개선
kelly6bf Mar 26, 2024
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
4 changes: 4 additions & 0 deletions .gitmessage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@


Co-authored-by: Hyunguk Ryu <[email protected]>

63 changes: 63 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## 프로그램 흐름

- 사용자로부터 게임 시작 여부를 입력받는다.
- `start`를 입력받으면 게임을 생성하고 초기 체스판의 상태를 출력한다.
- `end`를 입력받으면 프로그램을 종료한다.
- 기물 이동 여부를 입력받는다.
- `move b2 b4`와 같이 입력받으면 `b2`에 존재하는 기물을 `b4`로 이동시킨다.
- `end`를 입력받으면 프로그램을 종료한다.

## 도메인별 기능

### Piece

- [x] 출발지 & 목적지 정보를 토대로 기물이 이동할 수 있는지 검사한다.
- [x] 입력받은 색상을 토대로 자신의 적 여부를 반환한다.

#### Rook

- 상/하/좌/우 거리 제한 없이 이동 가능
- 비었거나 적 기물이 존재하는 칸으로만 이동 가능
- 이동 경로에 기물이 존재하면 이동 불가

#### Bishop

- 대각선 방향 거리 제한 없이 이동 가능
- 비었거나 적 기물이 존재하는 칸으로만 이동 가능
- 이동 경로에 기물이 존재하면 이동 불가

#### Queen

- 상/하/좌/우, 대각선 방향 거리 제한 없이 이동 가능
- 비었거나 적 기물이 존재하는 칸으로만 이동 가능
- 이동 경로에 기물이 존재하면 이동 불가

#### King

- 상/하/좌/우, 대각선 방향 중 한 칸만 이동 가능
- 비었거나 적 기물이 존재하는 칸으로만 이동 가능

#### Knight

- 상/하/좌/우 중 한 칸 이동 후 전진 방향의 대각석으로 한 칸 이동 가능
- 비었거나 적 기물이 존재하는 칸으로만 이동 가능
- 이동 경로에 기물이 존재해도 이동 가능

#### Pawn

- 특정 조건에 따라 이동 선택
- 기본적으로 한 칸 전진 가능
- 출발지가 초기 위치일 경우 두 칸 전진 가능
- 전진 경로 혹은 목적지에 기물이 존재하면 이동 불가
- 전진 방향 대각선 한 칸에 적 기물이 존재시 이동 가능

## Board

- [x] 특정 색상의 기물 이동
- `출발지` == `도착지`인 경우 예외 처리
- 출발지에 기물이 존재하지 않으면 예외 처리
- 상대 색상의 기물을 이동시키려 할 시 예외 처리

## ChessGame

- [x] `흰색` 플레이어와 `검은색` 플레이어의 게임 순서 관리 및 `Board`에 기물 이동 요청 전달
12 changes: 12 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import controller.GameController;
import domain.game.ChessGame;
import view.InputView;
import view.OutputView;

public class Application {
public static void main(String[] args) {
ChessGame chessGame = ChessGame.initGame();
GameController gameController = new GameController(new InputView(), new OutputView(), chessGame);
gameController.run();
}
}
62 changes: 62 additions & 0 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package controller;

import domain.game.ChessGame;
import domain.game.GameCommand;
import domain.game.GameCommandType;
import dto.BoardDto;
import view.InputView;
import view.OutputView;

public class GameController {
private final InputView inputView;
private final OutputView outputView;
private final ChessGame chessGame;

public GameController(final InputView inputView, final OutputView outputView, final ChessGame chessGame) {
this.inputView = inputView;
this.outputView = outputView;
this.chessGame = chessGame;
}

public void run() {
initGame();

if (chessGame.isGameRunning()) {
gameStart();
}
}

private void initGame() {
try {
GameCommand gameCommand = inputCommand();
gameCommand.execute(chessGame);
} catch (Exception e) {
outputView.printErrorMessage(e.getMessage());
initGame();
}
}

private GameCommand inputCommand() {
String[] inputValues = inputView.inputCommand().split(" ");
return GameCommandType.of(inputValues);
}

private void gameStart() {
outputView.printWelcomeMessage();
while (chessGame.isGameRunning()) {
BoardDto boardDto = BoardDto.from(chessGame.piecePositions());
outputView.printBoard(boardDto);
playTurn();
}
}

private void playTurn() {
try {
GameCommand gameCommand = inputCommand();
gameCommand.execute(chessGame);
} catch (Exception e) {
outputView.printErrorMessage(e.getMessage());
playTurn();
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/domain/board/Board.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package domain.board;

import domain.piece.Piece;
import domain.piece.PieceColor;

import java.util.Collections;
import java.util.Map;

public class Board {
private final Map<Position, Piece> piecePositions;

public Board(final Map<Position, Piece> piecePositions) {
this.piecePositions = piecePositions;
}

public void movePiece(final PieceColor pieceColor, final Position source, final Position destination) {
validatePosition(pieceColor, source, destination);

Piece targetPiece = piecePositions.get(source);
targetPiece.move(source, destination, this);

piecePositions.put(destination, targetPiece);
piecePositions.remove(source);
}

private void validatePosition(final PieceColor pieceColor, final Position source, final Position destination) {
if (source.equals(destination)) {
throw new IllegalArgumentException("출발지와 목적지가 같을 수 없습니다.");
}

if (!piecePositions.containsKey(source)) {
throw new IllegalArgumentException("출발지에 기물이 존재하지 않습니다.");
}

if (!piecePositions.get(source).isTeam(pieceColor)) {
throw new IllegalArgumentException("상대방의 기물을 이동시킬 수 없습니다.");
}
}

public boolean existPiece(final Position position) {
return piecePositions.containsKey(position);
}

public boolean existTeamColor(final Position position, final PieceColor teamColor) {
return piecePositions.get(position).isTeam(teamColor);

}

public Map<Position, Piece> piecePositions() {
return Collections.unmodifiableMap(piecePositions);
}
}
87 changes: 87 additions & 0 deletions src/main/java/domain/board/BoardInitializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package domain.board;

import domain.piece.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static domain.board.File.D;
import static domain.board.File.E;
import static domain.board.File.A;
import static domain.board.File.B;
import static domain.board.File.C;
import static domain.board.File.F;
import static domain.board.File.G;
import static domain.board.File.H;
import static domain.board.Rank.ONE;
import static domain.board.Rank.TWO;
import static domain.board.Rank.SEVEN;
import static domain.board.Rank.EIGHT;
import static domain.piece.PieceColor.BLACK;
import static domain.piece.PieceColor.WHITE;

public class BoardInitializer {
public static Board initBoard() {
return new Board(new HashMap<>(initPieces()));
}

private static Map<Position, Piece> initPieces() {
final Map<Position, Piece> piecePositions = new HashMap<>();
initKing(piecePositions);
initQueen(piecePositions);
initBishop(piecePositions);
initKnight(piecePositions);
initRook(piecePositions);
initBlackPawns(piecePositions);
initWhitePawns(piecePositions);

return piecePositions;
}

private static void initKing(final Map<Position, Piece> piecePositions) {
piecePositions.put(position(E, ONE), new King(WHITE));
piecePositions.put(position(E, EIGHT), new King(BLACK));
}

private static void initQueen(final Map<Position, Piece> piecePositions) {
piecePositions.put(position(D, ONE), new Queen(WHITE));
piecePositions.put(position(D, EIGHT), new Queen(BLACK));
}

private static void initBishop(final Map<Position, Piece> piecePositions) {
piecePositions.put(position(C, ONE), new Bishop(WHITE));
piecePositions.put(position(F, ONE), new Bishop(WHITE));
piecePositions.put(position(C, EIGHT), new Bishop(BLACK));
piecePositions.put(position(F, EIGHT), new Bishop(BLACK));
}

private static void initKnight(final Map<Position, Piece> piecePositions) {
piecePositions.put(position(B, ONE), new Knight(WHITE));
piecePositions.put(position(G, ONE), new Knight(WHITE));
piecePositions.put(position(B, EIGHT), new Knight(BLACK));
piecePositions.put(position(G, EIGHT), new Knight(BLACK));
}

private static void initRook(final Map<Position, Piece> piecePositions) {
piecePositions.put(position(A, ONE), new Rook(WHITE));
piecePositions.put(position(H, ONE), new Rook(WHITE));
piecePositions.put(position(A, EIGHT), new Rook(BLACK));
piecePositions.put(position(H, EIGHT), new Rook(BLACK));

}

private static void initWhitePawns(final Map<Position, Piece> piecePositions) {
Arrays.stream(File.values())
.forEach(file -> piecePositions.put(position(file, TWO), new Pawn(WHITE)));
}

private static void initBlackPawns(final Map<Position, Piece> piecePositions) {
Arrays.stream(File.values())
.forEach(file -> piecePositions.put(position(file, SEVEN), new Pawn(BLACK)));
}

private static Position position(final File file, final Rank rank) {
return new Position(file, rank);
}
}
38 changes: 38 additions & 0 deletions src/main/java/domain/board/File.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package domain.board;

import java.util.Arrays;

public enum File {
A(0),
B(1),
C(2),
D(3),
E(4),
F(5),
G(6),
H(7);

private final int index;

File(final int index) {
this.index = index;
}

public static File of(final String value) {
return Arrays.stream(values())
.filter(file -> file.name().equals(value.toUpperCase()))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 File 값입니다."));
}

public static File of(final int index) {
return Arrays.stream(values())
.filter(file -> file.getIndex() == index)
.findAny()
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 인덱스입니다."));
}

public int getIndex() {
return this.index;
}
}
40 changes: 40 additions & 0 deletions src/main/java/domain/board/Position.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package domain.board;

import domain.piece.MovementDirection;

public record Position(File file, Rank rank) {
private static final int POSITIONS_VALUES_SIZE = 2;
private static final int FILE_INDEX = 0;
private static final int RANK_INDEX = 1;

public static Position of(final String value) {
String[] positionValues = value.split("");
validatePositionValue(positionValues);

File file = File.of(positionValues[FILE_INDEX]);
Rank rank = Rank.of(positionValues[RANK_INDEX]);

return new Position(file, rank);
}

private static void validatePositionValue(final String[] values) {
if (values.length != POSITIONS_VALUES_SIZE) {
throw new IllegalArgumentException("잘못된 위치값입니다.");
}
}

public Position next(final MovementDirection movementDirection) {
File nextFile = File.of(this.columnIndex() + movementDirection.getColumnDistance());
Rank nextRank = Rank.of(this.rowIndex() + movementDirection.getRowDistance());

return new Position(nextFile, nextRank);
}

public int rowIndex() {
return rank.getIndex();
}

public int columnIndex() {
return file.getIndex();
}
}
Loading