Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
8ed7716
docs(README): 1.1단계 리드미 작성
miniminjae92 Mar 25, 2026
d1fa894
test(PositionTest): 테스트 작성
miniminjae92 Mar 25, 2026
a956456
feat(Position): 위치 기능 구현
miniminjae92 Mar 25, 2026
c6fffa2
test(SideTest): 테스트 작성
miniminjae92 Mar 25, 2026
2306d36
feat(Side): 진영 기능 구현
miniminjae92 Mar 25, 2026
98b7af5
test(PieceTest): 테스트 작성
miniminjae92 Mar 25, 2026
8c0e6b9
feat(Piece): 기물 기능 구현
miniminjae92 Mar 25, 2026
5830535
feat(Pieces): 기물 구현 클래스들 추가
miniminjae92 Mar 25, 2026
07a7c91
feat(Board): 장기판 생성 부분 구현
miniminjae92 Mar 25, 2026
151c12a
feat(Game): 게임 초기화 기능 부분 구현
miniminjae92 Mar 25, 2026
663a123
feat(InitialBoardFactory): 게임 초기화 기능 구현
miniminjae92 Mar 26, 2026
b4c9502
feat(Position): 위치 이동 기능 구현
miniminjae92 Mar 27, 2026
559f0f2
docs(README): 1.2단계 기능 목록 작성
miniminjae92 Mar 27, 2026
8dfc831
docs(README): 1.2단계 기능 목록 작성
miniminjae92 Mar 27, 2026
4b70abd
refactor: 패키지 생성 및 이동
miniminjae92 Mar 28, 2026
80c4f31
feat(Piece): 기물 인터페이스 구현
miniminjae92 Mar 28, 2026
7b112e0
feat(General): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
6937ac2
feat(Guard): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
303e556
feat(Soldier): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
dd4f2ac
feat(Horse): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
50f2eb9
feat(Elephant): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
21aa29e
feat(Chariot): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
da517e6
feat(Cannon): 기물 이동 규칙 구현
miniminjae92 Mar 28, 2026
5cd420c
feat(Side): 진영 확인 기능 구현
miniminjae92 Mar 28, 2026
1ed9111
refactor(Selection): 도메인 패키지 이동
miniminjae92 Mar 28, 2026
72210f2
feat(Board): 장기판 기능 구현
miniminjae92 Mar 28, 2026
7b99b94
Merge branch 'step1' of github.com:miniminjae92/java-janggi into step1
miniminjae92 Mar 28, 2026
3e6002d
docs: 리드미 수정
miniminjae92 Mar 30, 2026
d5f2ee7
refactor: 테스트 파일 경로 변경
miniminjae92 Mar 30, 2026
5371e48
test(Selection): 테스트 추가 및 코드 수정
miniminjae92 Mar 30, 2026
2cbe86c
feat(Name): 클래스 및 테스트 추가
miniminjae92 Mar 30, 2026
ac6a56b
test(Formation): 테스트 추가 및 메서드 이름 변경
miniminjae92 Mar 30, 2026
178cd12
feat(Player): 클래스 및 테스트 추가
miniminjae92 Mar 30, 2026
2767292
feat(TurnState): 턴 기능 상태패턴 도입
miniminjae92 Mar 30, 2026
92d1e8a
test(Board): 테스트 추가 및 코드 수정
miniminjae92 Mar 30, 2026
78870f0
feat(view): 입출력 부분 수정
miniminjae92 Mar 30, 2026
193056f
feat(InputParser): 입력 파서 클래스 및 테스트 추가
miniminjae92 Mar 30, 2026
c95e533
test(GameTest): 테스트 추가 및 클래스 추가, 코드 수정
miniminjae92 Mar 30, 2026
6567b11
refactor(Players): TurnState 구현체 이름 변경 및 Players 생성 변경
miniminjae92 Mar 30, 2026
e4c4558
feat(domain): 초기화 부분 수정 및 올바른 턴에 맞는 진행 기능 구현
miniminjae92 Mar 30, 2026
a6a6570
test(Piece): 각 기물들 이동 테스트 추가
miniminjae92 Mar 30, 2026
3e24b68
style: 축약한 변수명 수정
miniminjae92 Mar 30, 2026
37da877
docs: 리드미 수정
miniminjae92 Mar 30, 2026
50546c5
refactor: 테스트 및 네이밍 변경
miniminjae92 Mar 30, 2026
b4df2ef
feat: 기물, 장기판 생성 클래스 추가 및 수정, 이동 전략 클래스 추가
miniminjae92 Mar 30, 2026
96a44ff
feat: 방향 클래스 추가 및 위치, 게임, 게임매니저 수정
miniminjae92 Mar 30, 2026
fd6dfac
refactor: 기물 이동 전략 리팩터링 및 테스트 수정
miniminjae92 Mar 30, 2026
e2e879a
refactor: 도메인 세부 디렉토리 생성 및 클래스들 이동
miniminjae92 Mar 31, 2026
23862a7
refactor: 클래스 및 축약된 변수 명명 변경
miniminjae92 Mar 31, 2026
f660c3f
refactor(OutputView): 출력 포맷을 전각 유니코드 사용
miniminjae92 Mar 31, 2026
c90daf6
refactor(Side): 사용 안하는 메서드 제거
miniminjae92 Mar 31, 2026
2059839
refactor: 클래스 및 축약 변수 명명 변경
miniminjae92 Mar 31, 2026
d4dfde2
refactor: Depth 수정
miniminjae92 Mar 31, 2026
08c2567
refactor: import 순서 변경
miniminjae92 Mar 31, 2026
47bb454
refactor(OutputView): 메서드 분리
miniminjae92 Mar 31, 2026
3b8a8f9
refactor(Soldier): depth 수정
miniminjae92 Mar 31, 2026
6d7b921
refactor: 메서드 명명 변경
miniminjae92 Mar 31, 2026
5f3e627
refactor: depth 수정
miniminjae92 Mar 31, 2026
70eadf0
docs: 리드미 수정
miniminjae92 Mar 31, 2026
29c93f8
refactor(Formation): 변수명 수정
miniminjae92 Apr 2, 2026
00889f3
refactor(Players): 접근제어자 수정
miniminjae92 Apr 2, 2026
cbffaf8
refactor(strategy): Path 생성시 검증하도록 수정
miniminjae92 Apr 2, 2026
bbb9237
refactor(Game): 명령 쿼리 혼합으로 수정
miniminjae92 Apr 2, 2026
06db191
refactor: 위치 입력 형식 변경
miniminjae92 Apr 2, 2026
7120652
refactor(strategy): Path 비즈니스 로직 추가
miniminjae92 Apr 2, 2026
0fbc4d2
refactor: 기물 이동 비즈니스 로직 전략으로 전부 이동 및 메서드명 변경
miniminjae92 Apr 3, 2026
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
344 changes: 342 additions & 2 deletions README.md

Large diffs are not rendered by default.

Empty file removed src/main/java/.gitkeep
Empty file.
14 changes: 14 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import domain.GameManager;
import java.util.Scanner;
import view.InputView;
import view.OutputView;

public class Application {
public static void main(String[] args) {
GameManager gameManager = new GameManager(
new InputView(new Scanner(System.in)),
new OutputView()
);
gameManager.play();
}
}
28 changes: 28 additions & 0 deletions src/main/java/domain/Destinations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package domain;

import java.util.List;

public class Destinations {
private final List<Position> positions;

public Destinations(List<Position> positions) {
validate(positions);
this.positions = List.copyOf(positions);
}

private void validate(List<Position> positions) {
if (positions.isEmpty()) {
throw new IllegalArgumentException("이동 가능한 목적지가 없습니다.");
}
}

public List<Position> getPositions() {
return positions;
}

public void validateDestinations(Position target) {
if (!positions.contains(target)) {
throw new IllegalArgumentException("이동할 수 없는 위치입니다.");
}
}
Comment on lines +23 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이런 메서드는 절차적으로 호출하지 않으면 의도한대로 사용되지 않을 우려가 있어보이는데요.

구조적으로 안전하게 설계를 개선해보면 좋을것 같네요.

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

import domain.board.Board;
import domain.piece.Piece;
import domain.player.Players;
import java.util.Map;

public class Game {
private Board board;
private final Players players;

public Game(Board board, Players players) {
this.board = board;
this.players = players;
}

public Side getCurrentSide() {
return players.getCurrentSide();
}

public Destinations selectSource(Position position) {
Piece piece = board.getPiece(position);
players.getCurrentPlayer().validateAlly(piece);
return findDestinations(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.

players.getCurrentPlayer().validateAlly(piece);Players에게 위임할 수 없나요?


public void move(Position source, Position target) {
Destinations destinations = selectSource(source);
destinations.validateDestinations(target);
movePiece(source, target);
players.switchPlayer();
}

private Destinations findDestinations(Position position) {
return board.findDestinations(position);
}

private void movePiece(Position source, Position target) {
this.board = board.movePiece(source, target);
}

public Map<Position, Piece> getBoard() {
return board.getBoard();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

getBoard()Map<Position, Piece>를 그대로 반환하고 있는데요. 사용하는 곳은 view로 보이네요. 불변한 컬렉션을 반환해보면 어떨까요?

public boolean isOver() {
return board.isGameOver();
}

public String getWinner() {
return players.getWaitingPlayerName();
}
Comment on lines +50 to +52
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

winner를 찾기위해 대기중인 플레이어의 이름을 가져오는게 직관적으로 의도에 맞는 네이밍으로 보이지는 않는것 같아요.

다른 네이밍은 없을까요?

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

import domain.board.Board;
import domain.board.BoardFactory;
import domain.board.Formation;
import domain.board.FormationCommand;
import domain.player.Name;
import domain.player.Players;
import java.util.List;
import java.util.function.Supplier;
import view.InputParser;
import view.InputView;
import view.OutputView;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

GameManagerdomain 패키지에 위치하면서 view.InputParser, view.InputView, view.OutputView를 직접 의존하고 있는데요, 도메인 객체가 뷰 계층을 알고 있는 구조가 되어버립니다. 개선이 필요해보여요

public class GameManager {
private final InputView inputView;
private final OutputView outputView;

public GameManager(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void play() {
Game game = initializeGame();
outputView.printBoard(game.getBoard());
while (!game.isOver()) {
playTurn(game);
}
outputView.printWinner(game.getWinner());
}

private Game initializeGame() {
Name choName = getPlayerName(Side.CHO);
Players players = retry(() -> {
Name hanName = getPlayerName(Side.HAN);
return Players.createInitial(choName, hanName);
});
Board board = BoardFactory.create(getFormation(Side.CHO), getFormation(Side.HAN));
return new Game(board, players);
}

private Name getPlayerName(Side side) {
return retry(() -> InputParser.parseName(inputView.readPlayerName(side)));
}

private Formation getFormation(Side side) {
return retry(() -> Formation.from(FormationCommand.from(inputView.readFormation(side))));
}

private void playTurn(Game game) {
Position source = selectPiecePosition(game);
retry(() -> {
Position target = InputParser.parsePosition(inputView.readTargetPosition());
game.move(source, target);
});
outputView.printBoard(game.getBoard());
}

private Position selectPiecePosition(Game game) {
return retry(() -> {
Position position = InputParser.parsePosition(inputView.readSourcePosition(game.getCurrentSide()));
List<Position> destinations = game.selectSource(position).getPositions();
outputView.printDestinations(destinations);
return position;
});
}

private <T> T retry(Supplier<T> supplier) {
while (true) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}

private void retry(Runnable action) {
while (true) {
try {
action.run();
return;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}
}
}
91 changes: 91 additions & 0 deletions src/main/java/domain/Position.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package domain;

import domain.strategy.Direction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class Position {
private static final Map<Integer, Position> CACHE = new HashMap<>();

public static final int MIN = 0;
public static final int MAX_X = 8;
public static final int MAX_Y = 9;

private final int x;
private final int y;

static {
for (int x = MIN; x <= MAX_X; x++) {
for (int y = MIN; y <= MAX_Y; y++) {
CACHE.put(generateKey(x, y), new Position(x, y));
}
}
}

private Position(int x, int y) {
this.x = x;
this.y = y;
}

public static Position of(int x, int y) {
validateRange(x, y);
return CACHE.get(generateKey(x, y));
}

private static void validateRange(int x, int y) {
if (!isWithinRange(x, y)) {
throw new IllegalArgumentException(String.format("범위를 벗어난 좌표입니다: (%d, %d)", x, y));
}
}

public boolean canMove(Direction direction) {
return isWithinRange(this.x + direction.getDx(), this.y + direction.getDy());
}

public boolean canMove(List<Direction> sequence) {
if (sequence.isEmpty()) {
return true;
}

Direction direction = sequence.getFirst();
if (!canMove(direction)) {
return false;
}

return move(direction).canMove(sequence.subList(1, sequence.size()));
}

public Position move(Direction direction) {
return Position.of(this.x + direction.getDx(), this.y + direction.getDy());
}

public static boolean isWithinRange(int x, int y) {
return x >= MIN && x <= MAX_X && y >= MIN && y <= MAX_Y;
}

private static int generateKey(int x, int y) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Position이 캐싱을 통해 동일 좌표에 대해 같은 인스턴스를 반환하고 있는데, equals()hashCode()를 별도로 오버라이드하고 계시네요. 캐싱 덕분에 == 비교만으로도 동등성이 보장되는 상황에서 필요한 이유가 있나요?

return x * 31 + y;
}

public int getX() { return x; }
public int getY() { return y; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Position position)) return false;
return x == position.x && y == position.y;
}

@Override
public int hashCode() {
return Objects.hash(x, y);
}

@Override
public String toString() {
return String.format("(%d, %d)", x, y);
}
}
78 changes: 78 additions & 0 deletions src/main/java/domain/Side.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package domain;

import java.util.List;

public enum Side {
CHO("초") {
@Override
public int baseY() {
return 0;
}

@Override
public int generalY() {
return 1;
}

@Override
public int cannonY() {
return 2;
}

@Override
public int soldierY() {
return 3;
}

@Override
public List<Integer> formationX() {
return List.of(1, 2, 6, 7);
}
},
HAN("한") {
@Override
public int baseY() {
return 9;
}

@Override
public int generalY() {
return 8;
}

@Override
public int cannonY() {
return 7;
}

@Override
public int soldierY() {
return 6;
}

@Override
public List<Integer> formationX() {
return List.of(7, 6, 2, 1);
}
};

private final String name;

Side(String name) {
this.name = name;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Side enum에 baseY(), generalY(), cannonY(), soldierY(), formationX() 같은 배치 좌표 정보가 들어있는데요, 진영(Side)이라는 개념이 "초/한 구분"이라는 본래 역할 외에 "보드 배치 좌표"라는 책임까지 가진것 같네요.

SRP를 위반한 것 아닌가요?


public abstract int baseY();
public abstract int generalY();
public abstract int cannonY();
public abstract int soldierY();
public abstract List<Integer> formationX();

public boolean isAlly(Side other) {
return this.equals(other);
}

public String getName() {
return name;
}
}
Loading