-
Notifications
You must be signed in to change notification settings - Fork 0
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
Feat/level1 #1
Changes from 28 commits
33c122b
ac07697
a45f16c
bd7c2d4
20a79ca
5aa94a5
afa3851
3a92fc8
61bb2ef
5c82e6f
3d06229
d03e694
6967868
1806ee9
177a4cb
5741b32
96fdbe2
e25119a
f65812f
b1bf8be
ef911de
55ad847
1461e0c
3245948
2ba5b9a
bd5fb4d
b9c73b3
fa6c6b8
cbd5ad0
5087472
eff0320
f3f146a
2a6aa29
d750454
0690c52
bed7daf
490ff2e
c6626e6
3c0859b
20f2584
6177d2c
932097c
94a3649
cc24b10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,8 @@ | ||
import controller.Controller; | ||
|
||
public class Main { | ||
public static void main(String[] args) { | ||
Controller controller = new Controller(); | ||
controller.start(); | ||
} | ||
} |
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을 반환 |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 커밋로그를 보니깐 전반적으로 TDD가 이뤄지지 않은 것 같은데 이유가 있었을까? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
처음 시작시 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]; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 객체로 관리하면 어떤 점이 좋을까? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
} | ||
} | ||
} |
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package domain; | ||
|
||
import java.util.Map; | ||
|
||
import static domain.Error.*; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import에 와일드카드는 좋지 않아서 없애기 (이전 리뷰 참고) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
인텔리제이에서 설정해줘서 의식하지 않고있었는데, static import도 따로 설정해줘야하는 것을 놓치고 있었습니다. 수정하겠습니다! |
||
|
||
public class Board { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 구조에서는 한 팀에서 계속 움직일 수 있어 ex. |
||
|
||
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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 뷰에서 사용되는 것 같은데, 이걸 도메인에서 해야할까? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
사실 이부분에서 고민이 많았습니다. 결국 상태를 알아야한다는 것은 Board에있는 Map의 값들을 돌려서 확인해야하는데, 여기서 처리를 하지않는다면 Map자체를 다른곳으로 넘겨야하기에 불변으로 넘기더라도 적절하지 않다고 생각하기에 도메인에서 처리했습니다. 이럴경우에는 Map 자체를 넘기더라도 다른곳에서 처리하는것 맞는것인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분에서 몇 가지 예외가 필요할 것 같아 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 로직은 이동전 기물의 위치에서 기물을 제거하고 이동하는 곳으로 기물을 덮어씌우는 로직입니다. 이때 발생할 수 있는 예외는 이동전 기물이 존재하는지, 이동동선에 기물이 있는지, 이동할 곳에 같은 기물이 있는지, 기물의 움직일 수 있는 위치인지와 같은 예외는 이미 처리했기습니다. 혹시 어떠한 예외처리가 필요한지 궁금합니다. |
||
Pieces pieces = board.get(preLocation); | ||
board.remove(preLocation); | ||
board.put(moveLocation, pieces); | ||
} | ||
} | ||
|
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())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
도메인의 패키지 구조를 역할별로 나눠보면 어떨까?