Skip to content

[1,2 단계 - 체스] 제나(위예나) 미션 제출합니다.#486

Merged
jnsorn merged 89 commits intowoowacourse:yenaweefrom
yenawee:step1
Mar 26, 2023
Merged

[1,2 단계 - 체스] 제나(위예나) 미션 제출합니다.#486
jnsorn merged 89 commits intowoowacourse:yenaweefrom
yenawee:step1

Conversation

@yenawee
Copy link
Copy Markdown

@yenawee yenawee commented Mar 17, 2023

안녕하세요 또링 ! 😊

체스 미션을 구현해서 리뷰 요청 드려요 !!
에러 발생 시 재입력 기능이 구현되지 않아서 게임 실행 스크립트를 작성해봤습니다 .. 😿

start
move a2 a4 (white, pawn 2칸)
move b8 a6 (black, knight)
move a4 a5 (white, pawn 1칸)
move b7 b6 (black, pawn 1칸)
move a5 b6 (white, pawn 공격)
move c7 c6 (black, pawn 1칸)
move a1 a6 (white, rook 공격)
move c6 c5 (black, pawn 1칸)
move d2 d4 (white, pawn 2칸)
move e7 e5 (black, pawn 2칸)
move c1 h6 (white, bishop)
move d8 h4 (black, queen)
move g1 f3 (white, knight)
move e8 e7 (black, king)

리뷰 감사드립니다 🙇‍♀️

Cl8D and others added 30 commits March 14, 2023 17:41
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
Co-authored-by: yenawee <yaena0319@naver.com>
@jnsorn jnsorn self-requested a review March 18, 2023 09:57
Copy link
Copy Markdown

@jnsorn jnsorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 제나! 리뷰가 늦었습니다. 🥲
전반적으로 아주 잘 구현해주신 것 같다는 생각이 드네요.
이전에 학습한 내용도 적절하게 잘 적용해주신 것 같아요.
몇가지 코멘트 남겼는데 확인해보시고, 편하게 DM이나 코멘트 남겨주세요.
이번주도 화이팅입니다. ♟️

Comment on lines +19 to +27
private void start(final ChessGame chessGame) {
Status gameStatus = new Start(chessGame);
while (gameStatus.isRun()) {
List<String> commands = InputView.getCommand();
final Command command = Command.findCommand(commands);
gameStatus = gameStatus.checkCommand(command);
OutputView.printBoard(chessGame.getChessBoard());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Controller에서 최대한 로직을 덜어냈네요! 특히, 상태에 따라 행위가 달라지는 부분도 별도 객체에 책임을 위임하니 Controller가 최소한의 행위만 하고 있는 것 같아요. 💯

Comment on lines +46 to +52
private static char getPieceName(final Map<Position, Piece> board, final int rank, final int file) {
final Position position = new Position(rank, file);
if (board.containsKey(position)) {
return PieceName.findMessage(board.get(position));
}
return BLANK;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view에서 출력을 위해 Position 객체를 생성하고 있네요!
view와 model간의 의존성을 제거해보면 어떨까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view 와 domain 간의 의존성을 제거하기 위해서 ChessBoardDTO 를 만들어서 String 으로 보드 정보를 변환해 출력하는 것으로 변경했습니다.
근데 rank 와 file 순서대로 반복문 돌리면서 Position 에 기물이 있는지 확인하고 있으면 symbol을, 없으면 empty 를 반환하는 로직에서 계속해서 Position 이 생성되어서 계속 객체가 생성되는 것을 방지하기 위해 Position 을 캐싱하는 방법을 적용했습니다.. 다른 방법이 감이 안왔고 😥Position 이 체스판 수만큼 64개 캐싱해두면 불필요하게 객체 생성이 되는 걸 방지할 수 있어서 해당 방법을 적용해봤는데 혹시 더 좋은 방법이 있을까요ㅜㅜ ?? Map 이 순서가 있게 저장되면 좋을텐데 순서가 없어서 Position 순서대로 정렬할 수 가 없어서 방법을 찾기가 힘들었습니다 ㅠㅠ

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChessBoardDto에서는 Position을 꼭 객체로 가지고 있지 않아도 될 것 같아요. DTO 생성시에는 Map<Position, Piece>을 받더라도, DTO 내부에서는 Map<String, String>과 같은 형태로 표현할 수 있지 않을까요?


public static Command findCommand(final List<String> commands) {
final CommandType type = Arrays.stream(CommandType.values())
.filter(e -> e.name().equalsIgnoreCase(commands.get(0)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0은 어떤 의미일까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

명령어 리스트 중 첫번째 명령어(start, move, end)만 뽑아서 검증하기 위한 의미였습니다! 매직넘버 상수화 했습니다.

Comment on lines +3 to +6
public enum CampType {
BLACK,
WHITE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Camp라는 용어가 체스 용어인가요?!ㅎㅎ

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

체스에서 각 플레이어의 팀을 뜻하는 용어가 없어보여서 '진영' 이라는 뜻의 Camp 를 사용했습니다!
다시 보니 TeamColor 로 정의하는게 더 직관적으로 이해하기 좋을 거 같아 이름 변경했습니다 😀

}

private Status move(final Command command) {
validateCommand(command);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move에서 입력값 검증과 move 행위 두 가지를 하고 있는 것 같아요.
검증은 checkCommand에서 하는게 더 적절해보이는데 제나 생각은 어떠세요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

또링 말씀대로 checkCommand 에서 검증해까지 해주고 move 에서는 move 행위만 해주는게 더 적절하다고 생각해 메소드 위치 이동했습니다 !!

Comment on lines +21 to +30
public boolean contains(final Position possiblePosition) {
return board.containsKey(possiblePosition);
}

public Piece checkPiece(final Position source) {
if (contains(source)) {
return board.get(source);
}
throw new IllegalArgumentException("체스말이 존재하는 위치를 입력해 주세요.");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소한 네이밍 관련 의견인데요,

  1. contains 메서드 입장에선 매개변수가 possiblePosition인지 몰라도 될 것 같아요. position 정도로 간단하게 표현하는건 어떨까요?
  2. piece를 반환하고 있으니 check보단 get이나 find가 어울릴 것 같아요.

Comment on lines +57 to +68
/**
* 폰의 이동 규칙을 관리한다.
* 1) 첫 시도 - 대각선 위치에 다른 편의 기물 있음 : 공격
* 2) 첫 시도 - 대각선 위치에 기물 없음 : 최대 2칸까지 이동 가능, 가능한 위치면 이동
* 3) 그 외 - 대각선 위치에 기물 있음 : 공격
* 4) 그 외 - 대각선 위치에 기물 없음 : 앞으로 1칸 이동
*
* @param source 시작 위치
* @param target 종료 위치
* @param piece 체스 말
* TODO : 여기서 책임을 가지는 건 무의미한 것 같은데, 어떻게 분리하지?
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO에도 적어주신것처럼, pawn의 공격 및 이동 규칙이 ChessGame에 있는데 더 적절한 객체로 옮겨보면 어떨까요? 혹시 고민되는 부분이 있다면, DM 남겨주세요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pawn 을 BlackPawn 과 WhitePawn 으로 분리하고
Piece 의 canMove 에 매개변수로 targetPiece 를 추가해서 Pawn 에서 이동 및 공격 가능 여부를 판단할 수 있게끔 리팩토링 했습니다.
다른 기물들은 canMove 메소드 사용 시에 targetPiece 가 필요가 없는데 Pawn 때문에 매개변수 하나가 추가되었습니다..🥲 Piece 가 Position 을 가지고 있지 않게 설계했고 Piece 에 chessBoard 정보를 넘기고 싶지 않아서 Piece 에서는 가능한 경로에 target 이 포함되어있는지 확인하는 책임만 주었고 chessBoard 에서 target까지 가는 경로에 기물이 있는지 판단하는 책임을 주어서 (isPossibleRoute 메소드) 이 방식이 그나마 제일 코드가 깔끔해져서 이렇게 수정했습니다..

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 Piece가 Team을 가지고 있는데, Pawn을 BlackPawn과 WhitePawn으로 분리할 필요가 있을까요?
하나의 Pawn으로도 표현할 수 있을 것 같아요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlackPawn 과 WhitePawn 은 움직이는 방향이 달라서 이동 가능한 위치를 계산하는 Direction 이 각각 DOWN, UP 으로 다르고 공격할 수 있는 방향도 각각 아래 대각선, 위 대각선으로 달라서 Pawn 하나로 관리할 시에는 canMove 와 canAttack 메소드에서 if 문으로 분기처리가 필요해서 분리를 하였습니다.!

1,2,3 단계까지는 분리해서 구현하는게 더 코드가 깔끔해보였는데.. 4단계 때 DB 에서 select 로 piecetype 을 받아와서 Piece 를 생성하는 부분에서

 public static Piece toPiece(final PieceType pieceType, final TeamColor color) {
        if (pieceType == PAWN) {
            return getPawn(color);
        }
        return Arrays.stream(values())
                .filter(it -> it.name().equalsIgnoreCase(pieceType.name()))
                .map(it -> it.toPiece.apply(pieceType, color))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("기물을 찾을 수 없습니다."));
    }

 private static Pawn getPawn(TeamColor color) {
        if (color == TeamColor.BLACK) {
            return new BlackPawn(PieceType.PAWN);
        }
        return new WhitePawn(PieceType.PAWN);
    }

이렇게 분기처리가 필요해져서 다시 하나로 합칠 필요성도 보입니다 😥


import java.util.List;

public enum Direction {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방향을 관리하는 객체가 있군요 👍

Comment on lines +7 to +9
public abstract class Piece {
private final PieceType pieceType;
private final CampType campType;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Piece가 Movable을 가지고 있을꺼라 생각했는데, 조금 다르게 구현하셨군요!
Piece가 Movable을 갖지 않는 이유가 궁금한데, 얘기해주실수 있나요~?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Piece 와 PieceMove 들을 각각 TDD 로 만들면서 합치는 과정에서 추상화 계층이 불필요하게 나눠진듯합니다 사실 급하게 구현해서 기억이 잘 안납니다 😿
Movable 인터페이스를 Piece 에서 구현하는 식으로 변경했습니다!! (canMove 메소드 오버라이드)

import java.util.List;
import java.util.Set;

public final class Move {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move 객체의 책임은 무엇일까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move 에서는 piece 가 갈 수 있는 Position 들의 Set 을 반환하는 역할을 하고 있었는데 네이밍이 역할에 맞지 않다는 것을 확인해서 PossibleDestination 으로 일급 컬렉션 객체로 수정했습니다 !

@yenawee
Copy link
Copy Markdown
Author

yenawee commented Mar 23, 2023

안녕하세요 또링! 리뷰 반영이 늦었습니다ㅜㅜ
가장 큰 변경사항으로는 Piece 의 이동 가능 규칙 추상화 수준을 좀 더 간단하게 수정했습니다. 또 ChessGame 에서 piece 가 Pawn 인지 Knight 인지 묻지 않고도 piece.canMove() 만을 사용해 타겟이 기물이 이동 가능한 위치인지 판단하고 chessBoard 에서 타겟까지 가는 경로에 기물이 있는지 판단해서 chessGame 에서는 piece 를 이동만 시키도록 했습니다 !!
Readme 에 적어놨는데 반영 못한 사항은 3단계 진행하면서 추가로 적용하도록 하겠습니다.

리뷰 감사합니다 🙇‍♀️

Copy link
Copy Markdown

@jnsorn jnsorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요, 제나! 피드백 잘 반영해주셨네요 👍
코멘트도 잘 남겨주셔서 의도 파악하기에도 수월했습니다.
추가로 코멘트 남겼는데, 3,4단계 진행하면서 같이 반영해주시면 좋을 것 같아요!
남은 오후도 (..♟️와 함께.....) 잘 보내시고, 다음 단계에서 만나요 👋

movePiece(source, target, piece);
}

private void validateCamp(final Piece piece) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여긴 아직 용어가 안바뀐 것 같아요 👀

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!!

Comment on lines +57 to +68
/**
* 폰의 이동 규칙을 관리한다.
* 1) 첫 시도 - 대각선 위치에 다른 편의 기물 있음 : 공격
* 2) 첫 시도 - 대각선 위치에 기물 없음 : 최대 2칸까지 이동 가능, 가능한 위치면 이동
* 3) 그 외 - 대각선 위치에 기물 있음 : 공격
* 4) 그 외 - 대각선 위치에 기물 없음 : 앞으로 1칸 이동
*
* @param source 시작 위치
* @param target 종료 위치
* @param piece 체스 말
* TODO : 여기서 책임을 가지는 건 무의미한 것 같은데, 어떻게 분리하지?
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 Piece가 Team을 가지고 있는데, Pawn을 BlackPawn과 WhitePawn으로 분리할 필요가 있을까요?
하나의 Pawn으로도 표현할 수 있을 것 같아요!

Comment on lines +32 to +39
static ChessBoardFactory getInstance(final ChessGame chessGame) {
if (CACHE.containsKey(chessGame)) {
return CACHE.get(chessGame);
}
final ChessBoardFactory chessBoardFactory = new ChessBoardFactory();
CACHE.put(chessGame, chessBoardFactory);
return chessBoardFactory;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 제가 위에 코멘트를 살짝 잘못 단 것 같네요. 😭현재 ChessBoardFactory를 캐싱하여 사용하고 있는데 왜 캐싱을 사용하는지 이유가 궁금했어요! Factory가 캐시로 관리되어야 하는 이유가 있을까요~?

Comment on lines +46 to +52
private static char getPieceName(final Map<Position, Piece> board, final int rank, final int file) {
final Position position = new Position(rank, file);
if (board.containsKey(position)) {
return PieceName.findMessage(board.get(position));
}
return BLANK;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChessBoardDto에서는 Position을 꼭 객체로 가지고 있지 않아도 될 것 같아요. DTO 생성시에는 Map<Position, Piece>을 받더라도, DTO 내부에서는 Map<String, String>과 같은 형태로 표현할 수 있지 않을까요?

Comment on lines +60 to +72
public boolean isUpsideDiagonalPosition(Position target) {
int fileGap = target.file - this.file;
int rankGap = target.rank - this.rank;
return Math.abs(fileGap) == 1 && rankGap == 1;
}

public boolean isDownSideDiagonalPosition(Position target) {
int fileGap = target.file - this.file;
int rankGap = target.rank - this.rank;
return Math.abs(fileGap) == 1 && rankGap == -1;

}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

방향과 관련된 로직은 Direction 객체를 이용해보면 어떨까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 로직을 Direction 객체를 이용해 판단하는 것으로 수정했습니다 !

@jnsorn jnsorn merged commit 721e1bc into woowacourse:yenawee Mar 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants