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

Feat/level1 #1

Merged
merged 44 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
33c122b
docs: 기능구현 목록 작성
kimtaesoo99 Jun 29, 2023
ac07697
feat: 기물의 공통 속성을 가지는 추상 클래스 추가
kimtaesoo99 Jun 29, 2023
a45f16c
feat: 기물의 구현체 비숍 추가
kimtaesoo99 Jun 29, 2023
bd7c2d4
feat: 기물의 구현체 킹 추가
kimtaesoo99 Jun 29, 2023
20a79ca
feat: 기물의 나이트 추가
kimtaesoo99 Jun 29, 2023
5aa94a5
feat: 기물의 구현체 폰 추가
kimtaesoo99 Jun 29, 2023
afa3851
feat: 기물의 구현체 퀸 추가
kimtaesoo99 Jun 29, 2023
3a92fc8
feat: 기물의 구현체 룩 추가
kimtaesoo99 Jun 29, 2023
61bb2ef
feat: Location을 컬럼을 나타내는 File 추가
kimtaesoo99 Jun 29, 2023
5c82e6f
feat: Location을 로우를 나타내는 Rank 추가
kimtaesoo99 Jun 29, 2023
3d06229
feat: 기물의 이름과 팀을 나타내는 State 추가
kimtaesoo99 Jun 29, 2023
d03e694
feat: rank, file을 가지는 Location 추가
kimtaesoo99 Jun 29, 2023
6967868
feat: 체스판을 초기화해주는 initBoard 추가
kimtaesoo99 Jun 29, 2023
1806ee9
feat: 방향을 나타내는 Direction 추가
kimtaesoo99 Jun 29, 2023
177a4cb
feat: 입력을 관리하는 Command 추가
kimtaesoo99 Jun 29, 2023
5741b32
feat: Location을 키로 Pieces를 벨류로 하는 맵을 관리하는 일급컬렉션 Board 추가
kimtaesoo99 Jun 29, 2023
96fdbe2
feat: 출력을 담당하는 OutputView추가
kimtaesoo99 Jun 29, 2023
e25119a
feat: 입력을 담당하는 InputView추가
kimtaesoo99 Jun 29, 2023
f65812f
feat: 입력의 검증을 위한 InputValidation추가
kimtaesoo99 Jun 29, 2023
b1bf8be
feat: 예외 메시지를 담당하는 Error 추가
kimtaesoo99 Jun 29, 2023
ef911de
feat: 전체적인 흐름을 담당하는 컨트롤러 추가
kimtaesoo99 Jun 29, 2023
55ad847
feat: 시작을 알리는 Main 추가
kimtaesoo99 Jun 29, 2023
1461e0c
test: 기물 테스트 추가
kimtaesoo99 Jun 29, 2023
3245948
feat: 체스판의 상태를 보내주는 DTO 추가
kimtaesoo99 Jun 29, 2023
2ba5b9a
test: 상태를 표현하는 테스트 추가
kimtaesoo99 Jun 29, 2023
bd5fb4d
test: 체스판을 초기화 테스트 추가
kimtaesoo99 Jun 29, 2023
b9c73b3
chore: 설정 추가
kimtaesoo99 Jun 29, 2023
fa6c6b8
test: 체스판 테스트 추가
kimtaesoo99 Jun 29, 2023
cbd5ad0
refactor: public 메서드로 설정하였습니다.
kimtaesoo99 Jul 1, 2023
5087472
refactor: 출력을 위한 로직을 DTO에 위임하였습니다.
kimtaesoo99 Jul 1, 2023
eff0320
refactor: 패키징이동 및 출력시 보여지는 로직 추가
kimtaesoo99 Jul 1, 2023
f3f146a
refactor: 패키징이동 및 백,흑 순서를 보장하도록 수정
kimtaesoo99 Jul 1, 2023
2a6aa29
refactor: 패키징이동 및 개행적용
kimtaesoo99 Jul 1, 2023
d750454
refactor: 예외 추가및 패키지 이동
kimtaesoo99 Jul 1, 2023
0690c52
refactor: 패키지 이동
kimtaesoo99 Jul 1, 2023
bed7daf
remove: 입력값 검증을 command로 위임
kimtaesoo99 Jul 1, 2023
490ff2e
remove: 패키지 이동 및 로직 단순화
kimtaesoo99 Jul 1, 2023
c6626e6
remove: 패키지 이동 및 public 메서드로 설정
kimtaesoo99 Jul 1, 2023
3c0859b
remove: 패키지 이동 및 로직 단순화
kimtaesoo99 Jul 1, 2023
20f2584
remove: 패키지 이동
kimtaesoo99 Jul 1, 2023
6177d2c
remove: 패키지 이동 및 예외 로직 추가
kimtaesoo99 Jul 1, 2023
932097c
test: 추가적인 예외 처리
kimtaesoo99 Jul 1, 2023
94a3649
feat: 스플릿을 해주는 객체 추가
kimtaesoo99 Jul 1, 2023
cc24b10
test: import 추가
kimtaesoo99 Jul 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ plugins {
id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}

test {
useJUnitPlatform()
}
}
6 changes: 6 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
import controller.Controller;

public class Main {

Choose a reason for hiding this comment

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

도메인의 패키지 구조를 역할별로 나눠보면 어떨까?

public static void main(String[] args) {
Controller controller = new Controller();
controller.start();
}
}
39 changes: 39 additions & 0 deletions src/main/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 체스

- 체스판 위에 알맞은 순서대로 기물을 배치시킴
- 입력에 따라 해당 위치의 기물을 옮김

- 도메인 설계

## 체스판(8x8) 체스칸을 관리하는 일급 컬렉션
- [x] 맵으로 체스칸을 관리
- [x] 정적팩토리 메서드를 사용하여 초기화를 통해 기물을 배치
- [x] 해당 위치의 기물이 이동가능한지
- [x] 입력으로 들어온 위치에 기물이 있는지
- [x] 시작점과 끝점의 기물의 색상이 같은지
- [x] 시작점에서 이동가능한 위치인지
- [x] 이동경로에 기물이 있는지
- [x] 현재 체스판 상황을 반환

## 기물
- [x] 이동할수 있는지
- [x] 해당 말이 검정색인지
- [x] 이름을 반환
- [x] 검은색 기물인지
- [x] 나이트인지
- [x] 폰인지


## 위치 (인스턴스 변수 가로(Rank(, 세로(File)를 가짐 - Rank,File은 체스 용어)
- [x] 같은 위치인지
- [x] 같은 file인지
- [x] 같은 rank인지
- [x] rank 이름을 반환
- [x] file 이름을 반환

## 방향 enum
- [x] 같은 방향인지
- [x] 이동하려는 방향이 어느쪽인지
- [x] 해당 방향이 위또는 아래인지
- [x] row를 반환
- [x] column을 반환
70 changes: 70 additions & 0 deletions src/main/java/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package controller;

import domain.Board;
import domain.Command;
import domain.Location;
import dto.BoardStatusResponse;
import service.InitBoard;
import view.InputView;
import view.OutputView;

public class Controller {

Choose a reason for hiding this comment

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

커밋로그를 보니깐 전반적으로 TDD가 이뤄지지 않은 것 같은데 이유가 있었을까?

Copy link
Owner Author

Choose a reason for hiding this comment

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

커밋로그를 보니깐 전반적으로 TDD가 이뤄지지 않은 것 같은데 이유가 있었을까?

처음 시작시 TDD를 진행하였으나, 중간에 설계가 잘못되었다는 것을 느끼고 수정을 하면서 TDD를 제대로 진행하지 못하였습니다. 아직 설계가 부족하다고 느꼈습니다.


private static final String SEPARATOR = " ";
private static final int PRE_INDEX = 1;
private static final int MOVE_INDEX = 2;
private static final int RANK_INDEX = 0;
private static final int FILE_INDEX = 1;

private Board board;

public void start() {
OutputView.printStart();
startGame();
}

private void startGame() {
String input = InputView.readFirstCommand();
Command command = Command.from(input);

while (!command.isEnd()) {
checkCommand(command, input);
input = InputView.readCommand();
command = Command.from(input);
}
}

private void checkCommand(final Command command, final String input) {
if (command.isStart()) {
initBoard();
}

if (command.isMove()) {
findMoveLocation(input);
}

OutputView.printBoardStatus(BoardStatusResponse.from(board));
}

private void initBoard() {
board = new Board(new InitBoard().getInitBoard());
}

private void findMoveLocation(final String input) {
String[] inputInfo = input.split(SEPARATOR);
String preLocation = inputInfo[PRE_INDEX];
String moveLocation = inputInfo[MOVE_INDEX];

Choose a reason for hiding this comment

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

이 부분도 객체로 관리하면 어떤 점이 좋을까?

Copy link
Owner Author

Choose a reason for hiding this comment

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

이 부분도 객체로 관리하면 어떤 점이 좋을까?

복잡한 로직을 간단하게 표현할수 있고, 추가로 예외처리를 할 수 있습니다. 저의 경우에는 이미 입력값이 command에서 진행되었기에 따로 새로 만든 객체에서 예외를 하지는 않았습니다.

movePieces(preLocation, moveLocation);
}

private void movePieces(final String preLocation, final String moveLocation) {
try {
Location pre = Location.from(preLocation.charAt(RANK_INDEX), preLocation.charAt(FILE_INDEX));
Location move = Location.from(moveLocation.charAt(RANK_INDEX), moveLocation.charAt(FILE_INDEX));
board.movePieces(pre, move);
} catch (IllegalStateException e) {
OutputView.printErrorMessage(e.getMessage());
}
}
}
26 changes: 26 additions & 0 deletions src/main/java/domain/Bishop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package domain;

public class Bishop extends Pieces {

public Bishop(final State state) {
super(state);
}

@Override
boolean canMove(final char preRank, final char preFile, final Location moveLocation) {
char moveRank = moveLocation.getRank();
char moveFile = moveLocation.getFile();

return Math.abs(preRank - moveRank) == Math.abs(preFile - moveFile);
}

@Override
boolean isKnight() {
return false;
}

@Override
boolean isPawn() {
return false;
}
}
138 changes: 138 additions & 0 deletions src/main/java/domain/Board.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package domain;

import java.util.Map;

import static domain.Error.*;

Choose a reason for hiding this comment

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

import에 와일드카드는 좋지 않아서 없애기 (이전 리뷰 참고)

Copy link
Owner Author

Choose a reason for hiding this comment

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

import에 와일드카드는 좋지 않아서 없애기 (이전 리뷰 참고)

인텔리제이에서 설정해줘서 의식하지 않고있었는데, static import도 따로 설정해줘야하는 것을 놓치고 있었습니다. 수정하겠습니다!


public class Board {

Choose a reason for hiding this comment

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

현재 구조에서는 한 팀에서 계속 움직일 수 있어
왜 그럴까?

ex. move a2 a4 -> move a4 a5 ...


private static final int FILE_MAX = 8;
private static final int FILE_INIT = 1;
private static final int RANK_MAX = 7;
private static final int RANK_INIT = 0;
private static final String EMPTY = ".";
private static final char FIRST_RANK = 'a';
private static final char FIRST_FILE = '0';

private final Map<Location, Pieces> board;

public Board(final Map<Location, Pieces> board) {
this.board = board;
}

public String getBoardStatus() {

Choose a reason for hiding this comment

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

뷰에서 사용되는 것 같은데, 이걸 도메인에서 해야할까?

Copy link
Owner Author

Choose a reason for hiding this comment

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

뷰에서 사용되는 것 같은데, 이걸 도메인에서 해야할까?

사실 이부분에서 고민이 많았습니다. 결국 상태를 알아야한다는 것은 Board에있는 Map의 값들을 돌려서 확인해야하는데, 여기서 처리를 하지않는다면 Map자체를 다른곳으로 넘겨야하기에 불변으로 넘기더라도 적절하지 않다고 생각하기에 도메인에서 처리했습니다. 이럴경우에는 Map 자체를 넘기더라도 다른곳에서 처리하는것 맞는것인가요?

Choose a reason for hiding this comment

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

Map을 넘기는 것도 방법이겠지만, 키와 밸류를 알 수 있을까요? 추상적일 것 같습니다.

리스트로 나눠서 보내거나, DTO를 쓰는 것도 방법이 되겠네요

StringBuilder stringBuilder = new StringBuilder();
for (int file = FILE_MAX; file >= FILE_INIT; file--) {
findBoardStatus(file, stringBuilder);
}
return stringBuilder.toString();
}

private void findBoardStatus(final int file, final StringBuilder stringBuilder) {
for (int rank = RANK_INIT; rank <= RANK_MAX; rank++) {
Location location = Location.from((char) (FIRST_RANK + rank), (char) (FIRST_FILE + file));
addBoardStatus(location, stringBuilder);
}
}

private void addBoardStatus(final Location location, final StringBuilder stringBuilder) {
if (board.containsKey(location)) {
stringBuilder.append(board.get(location).getName());
return;
}
stringBuilder.append(EMPTY);
}

public void movePieces(final Location preLocation, final Location moveLocation) {
validationMovePieces(preLocation, moveLocation);
move(preLocation, moveLocation);
}

private void validationMovePieces(final Location preLocation, final Location moveLocation) {
checkExistPiecesInLocation(preLocation, moveLocation);
checkCanMovePiecesInLocation(preLocation, moveLocation);
if (board.get(preLocation).isPawn()) {
checkPawnMove(preLocation, moveLocation);
return;
}
checkExistPiecesInRoute(preLocation, moveLocation);
}

private void checkExistPiecesInLocation(final Location preLocation, final Location moveLocation) {
if (!board.containsKey(preLocation)) {
throw new IllegalStateException(HAS_NOT_PIECES.getMessage());
}

if (board.containsKey(moveLocation) && isSameColor(preLocation, moveLocation)) {
throw new IllegalStateException(MOVE_LOCATION_IS_SAME_COLOR.getMessage());
}
}

private boolean isSameColor(final Location preLocation, final Location moveLocation) {
Pieces piecesInPreIndex = board.get(preLocation);
Pieces piecesInMoveIndex = board.get(moveLocation);
return piecesInPreIndex.isBlack() == piecesInMoveIndex.isBlack();

Choose a reason for hiding this comment

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

  1. 이 부분을 여기서 해줘야할까?
  2. 현재 구조라면 같은 기물을 한번 더 움직일 수 있는데 개선해보는 것은 어떨까?

}

private void checkCanMovePiecesInLocation(final Location preLocation, final Location moveLocation) {
if (!board.get(preLocation).canMove(preLocation.getRank(), preLocation.getFile(), moveLocation)) {
throw new IllegalStateException(CAN_NOT_MOVE_RANGE.getMessage());
}

if (preLocation.equals(moveLocation)) {
throw new IllegalStateException(DO_NOT_STAY.getMessage());
}
}

private void checkPawnMove(final Location preLocation, final Location moveLocation) {
Direction direction = findDirection(preLocation, moveLocation);
if (direction.isUpOrDown()) {
checkRoute(direction, preLocation, moveLocation);
return;
}

if (!board.containsKey(moveLocation) || isSameColor(preLocation, moveLocation)) {
throw new IllegalStateException(PAWN_MOVE_DIAGONALLY_WITH_ENEMY.getMessage());
}
}

private void checkExistPiecesInRoute(final Location preLocation, final Location moveLocation) {
if (board.get(preLocation).isKnight()) {
return;
}
Direction direction = findDirection(preLocation, moveLocation);
checkRoute(direction, preLocation, moveLocation);
}

private Direction findDirection(final Location preLocation, final Location moveLocation) {
int rowDirection = Direction.getSubtractDirection(preLocation.getRank(), moveLocation.getRank());
int columnDirection = Direction.getSubtractDirection(preLocation.getFile(), moveLocation.getFile());

return Direction.from(rowDirection, columnDirection);
}

private void checkRoute(final Direction direction, final Location preLocation, final Location moveLocation) {
char nowRank = (char) (preLocation.getRank() + direction.getRow());
char nowFile = (char) (preLocation.getFile() + direction.getColumn());

while (!moveLocation.isSameLocation(nowRank, nowFile)) {
checkExistPiecesInLocation(nowRank, nowFile);
nowRank = (char) (nowRank + direction.getRow());
nowFile = (char) (nowFile + direction.getColumn());
}
}

private void checkExistPiecesInLocation(final char nowRank, final char nowFile) {
Location nowLocation = Location.from(nowRank, nowFile);
if (board.containsKey(nowLocation)) {
throw new IllegalStateException(CAN_NOT_MOVE_BECAUSE_OBSTACLE.getMessage());
}
}

private void move(final Location preLocation, final Location moveLocation) {

Choose a reason for hiding this comment

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

이 부분에서 몇 가지 예외가 필요할 것 같아
조금만 더 고민해보는 것은 어떨까?

Copy link
Owner Author

Choose a reason for hiding this comment

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

이 부분에서 몇 가지 예외가 필요할 것 같아 조금만 더 고민해보는 것은 어떨까?

현재 로직은 이동전 기물의 위치에서 기물을 제거하고 이동하는 곳으로 기물을 덮어씌우는 로직입니다. 이때 발생할 수 있는 예외는 이동전 기물이 존재하는지, 이동동선에 기물이 있는지, 이동할 곳에 같은 기물이 있는지, 기물의 움직일 수 있는 위치인지와 같은 예외는 이미 처리했기습니다. 혹시 어떠한 예외처리가 필요한지 궁금합니다.

Pieces pieces = board.get(preLocation);
board.remove(preLocation);
board.put(moveLocation, pieces);
}
}

45 changes: 45 additions & 0 deletions src/main/java/domain/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package domain;

import java.util.Arrays;

import static domain.Error.WRONG_INPUT;

public enum Command {

START("start"),
END("end"),
MOVE("move");

private final String select;

Command(final String select) {
this.select = select;
}

public static Command from(final String command) {
return Arrays.stream(Command.values())
.filter(status -> command.startsWith(status.select))
.findAny().orElseThrow(() -> new IllegalStateException(WRONG_INPUT.getMessage()));

Choose a reason for hiding this comment

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

객체지향 생활체조 4번 참고

}

public static boolean isCorrectCommand(final String command) {
return Arrays.stream(Command.values())
.anyMatch(status -> command.startsWith(status.select));
}

public static boolean isStartCommand(final String command) {
return Command.START.select.equals(command);
}

public boolean isEnd() {
return this.select.equals("end");
}

public boolean isStart() {
return this.select.equals("start");
}

public boolean isMove() {
return this.select.equals("move");
}
}