From ea8a8750ff98fb96ac95e9c07f8b7f8aa99f1fee Mon Sep 17 00:00:00 2001 From: armd482 Date: Tue, 31 Mar 2026 23:23:26 +0900 Subject: [PATCH 01/29] =?UTF-8?q?refactor:=20Game=EA=B0=9D=EC=B2=B4=20null?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=B0=A9=EC=96=B4=EC=A0=81=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD(=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 11fc0dffdb..d1e90017af 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -14,33 +14,32 @@ public class Runner { private static final String SYSTEM_ERROR_MESSAGE = "시스템 오류가 발생하여 게임을 종료합니다."; private static final Logger logger = Logger.getLogger(Runner.class.getName()); - private Game game; public void run() { - initArrangeGame(); - turnGame(); + Game game = initArrangeGame(); + playGame(game); } - private void initArrangeGame() { + private Game initArrangeGame() { String hanArrangementInput = InputView.askHanArrangement(); Arrangement hanArrangement = Arrangement.from(hanArrangementInput); String choArrangementInput = InputView.askChoArrangement(); Arrangement choArrangement = Arrangement.from(choArrangementInput); - game = new Game(choArrangement, hanArrangement); + return new Game(choArrangement, hanArrangement); } - private void turnGame() { - while (playTurnGame()) { + private void playGame(Game game) { + while (playTurnGame(game)) { } } - private boolean playTurnGame() { + private boolean playTurnGame(Game game) { try { - printCurrentStatus(); - movePiece(); - return isFinishedGame(); + printCurrentStatus(game); + movePiece(game); + return isFinishedGame(game); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); return true; @@ -51,12 +50,12 @@ private boolean playTurnGame() { } } - private void printCurrentStatus() { + private void printCurrentStatus(Game game) { OutputView.printBoard(game.getCurrentBoardDto()); OutputView.printTurn(game.getCurrentSide()); } - private void movePiece() { + private void movePiece(Game game) { List startPositionInput = InputView.askStartPosition(); Position startPosition = Position.from(startPositionInput); @@ -66,7 +65,7 @@ private void movePiece() { game.move(startPosition, endPosition); } - private boolean isFinishedGame() { + private boolean isFinishedGame(Game game) { if (game.isFinished()) { OutputView.printWinner(game.getWinnerSide()); return false; From 0392115df7da518b2f91134e50a7d5059fed2ff9 Mon Sep 17 00:00:00 2001 From: armd482 Date: Tue, 31 Mar 2026 23:25:34 +0900 Subject: [PATCH 02/29] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=83=81=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index cda97ddd5a..ed1a7e689d 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -14,7 +14,6 @@ public class Position { private static final String INVALID_POSITION_SIZE = "행과 열 두 개의 값만 입력하세요."; private static final String INVALID_ROW_RANGE = "유효하지 않은 위치입니다. 행은 1부터 10까지 가능합니다."; private static final String INVALID_COL_RANGE = "유효하지 않은 위치입니다. 열은 1부터 9까지 가능합니다."; - private static final String INVALID_HORIZON = "수직 또는 수평이 아닙니다."; private final int x; private final int y; From bce6ed8f8862bc9db866bde131795a6d8ac040e5 Mon Sep 17 00:00:00 2001 From: armd482 Date: Tue, 31 Mar 2026 23:41:37 +0900 Subject: [PATCH 03/29] =?UTF-8?q?rename:=20Turn=20=EC=B6=94=EC=83=81=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B0=8F=20=EC=9E=90=EC=8B=9D=20?= =?UTF-8?q?turn=20=ED=81=B4=EB=9E=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Game.java | 4 ++-- .../janggi/domain/turn/{ActionTurn.java => BaseTurn.java} | 4 ++-- .../domain/turn/{ChoActionTurn.java => ChoTurn.java} | 8 ++++---- .../janggi/domain/turn/{Finish.java => FinishTurn.java} | 4 ++-- .../domain/turn/{HanActionTurn.java => HanTurn.java} | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) rename src/main/java/janggi/domain/turn/{ActionTurn.java => BaseTurn.java} (85%) rename src/main/java/janggi/domain/turn/{ChoActionTurn.java => ChoTurn.java} (65%) rename src/main/java/janggi/domain/turn/{Finish.java => FinishTurn.java} (86%) rename src/main/java/janggi/domain/turn/{HanActionTurn.java => HanTurn.java} (65%) diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index f4971804c2..41c0999796 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -1,7 +1,7 @@ package janggi.domain; import janggi.domain.board.Board; -import janggi.domain.turn.ChoActionTurn; +import janggi.domain.turn.ChoTurn; import janggi.domain.turn.PlayerTurn; import janggi.dto.BoardDto; import janggi.initializer.BoardInitializer; @@ -12,7 +12,7 @@ public class Game { private PlayerTurn playerTurn; public Game(Arrangement choArrangement, Arrangement hanArrangement) { - this.playerTurn = new ChoActionTurn(new Board(BoardInitializer.createBoard(choArrangement, hanArrangement))); + this.playerTurn = new ChoTurn(new Board(BoardInitializer.createBoard(choArrangement, hanArrangement))); } public void move(Position start, Position end) { diff --git a/src/main/java/janggi/domain/turn/ActionTurn.java b/src/main/java/janggi/domain/turn/BaseTurn.java similarity index 85% rename from src/main/java/janggi/domain/turn/ActionTurn.java rename to src/main/java/janggi/domain/turn/BaseTurn.java index 576b0df3eb..5ab5869a73 100644 --- a/src/main/java/janggi/domain/turn/ActionTurn.java +++ b/src/main/java/janggi/domain/turn/BaseTurn.java @@ -5,11 +5,11 @@ import janggi.domain.piece.PieceManifest; import java.util.List; -public abstract class ActionTurn implements PlayerTurn { +public abstract class BaseTurn implements PlayerTurn { protected final Board board; protected final Side side; - public ActionTurn(Board board, Side side) { + public BaseTurn(Board board, Side side) { this.board = board; this.side = side; } diff --git a/src/main/java/janggi/domain/turn/ChoActionTurn.java b/src/main/java/janggi/domain/turn/ChoTurn.java similarity index 65% rename from src/main/java/janggi/domain/turn/ChoActionTurn.java rename to src/main/java/janggi/domain/turn/ChoTurn.java index 9f1ed967d7..f856a62312 100644 --- a/src/main/java/janggi/domain/turn/ChoActionTurn.java +++ b/src/main/java/janggi/domain/turn/ChoTurn.java @@ -4,8 +4,8 @@ import janggi.domain.Side; import janggi.domain.board.Board; -public class ChoActionTurn extends ActionTurn { - public ChoActionTurn(Board board) { +public class ChoTurn extends BaseTurn { + public ChoTurn(Board board) { super(board, Side.CHO); } @@ -13,8 +13,8 @@ public ChoActionTurn(Board board) { public PlayerTurn move(Position start, Position end) { board.move(start, end, side); if (board.isEndGame()) { - return new Finish(board, side); + return new FinishTurn(board, side); } - return new HanActionTurn(board); + return new HanTurn(board); } } diff --git a/src/main/java/janggi/domain/turn/Finish.java b/src/main/java/janggi/domain/turn/FinishTurn.java similarity index 86% rename from src/main/java/janggi/domain/turn/Finish.java rename to src/main/java/janggi/domain/turn/FinishTurn.java index 6800942d8c..d3021c5303 100644 --- a/src/main/java/janggi/domain/turn/Finish.java +++ b/src/main/java/janggi/domain/turn/FinishTurn.java @@ -4,12 +4,12 @@ import janggi.domain.Side; import janggi.domain.board.Board; -public class Finish extends ActionTurn { +public class FinishTurn extends BaseTurn { private static final String INVALID_MOVE = "게임 종료 상태에서는 이동할 수 없습니다."; private final Side winnerSide; - public Finish(Board board, Side winnerSide) { + public FinishTurn(Board board, Side winnerSide) { super(board, Side.EMPTY); this.winnerSide = winnerSide; } diff --git a/src/main/java/janggi/domain/turn/HanActionTurn.java b/src/main/java/janggi/domain/turn/HanTurn.java similarity index 65% rename from src/main/java/janggi/domain/turn/HanActionTurn.java rename to src/main/java/janggi/domain/turn/HanTurn.java index 684adbd513..1023c6608c 100644 --- a/src/main/java/janggi/domain/turn/HanActionTurn.java +++ b/src/main/java/janggi/domain/turn/HanTurn.java @@ -4,8 +4,8 @@ import janggi.domain.Side; import janggi.domain.board.Board; -public class HanActionTurn extends ActionTurn { - public HanActionTurn(Board board) { +public class HanTurn extends BaseTurn { + public HanTurn(Board board) { super(board, Side.HAN); } @@ -13,8 +13,8 @@ public HanActionTurn(Board board) { public PlayerTurn move(Position start, Position end) { board.move(start, end, side); if (board.isEndGame()) { - return new Finish(board, side); + return new FinishTurn(board, side); } - return new ChoActionTurn(board); + return new ChoTurn(board); } } From bc7846a05135ed37da719e339aca1a82c9f4d44c Mon Sep 17 00:00:00 2001 From: armd482 Date: Tue, 31 Mar 2026 23:51:10 +0900 Subject: [PATCH 04/29] =?UTF-8?q?refactor:=20PieceManifest=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/board/Board.java | 4 ++-- .../janggi/domain/board/CurrentBoard.java | 12 +++++----- .../java/janggi/domain/piece/BasePiece.java | 4 ++-- src/main/java/janggi/domain/piece/Piece.java | 2 +- .../janggi/domain/piece/PieceAttribute.java | 6 +++++ .../janggi/domain/piece/PieceManifest.java | 6 ----- .../java/janggi/domain/turn/BaseTurn.java | 4 ++-- .../java/janggi/domain/turn/PlayerTurn.java | 4 ++-- src/main/java/janggi/dto/BoardDto.java | 4 ++-- src/main/java/janggi/view/OutputView.java | 24 +++++++++---------- 10 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 src/main/java/janggi/domain/piece/PieceAttribute.java delete mode 100644 src/main/java/janggi/domain/piece/PieceManifest.java diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index cf0dc88909..c3cc758b67 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -4,7 +4,7 @@ import janggi.domain.Side; import janggi.domain.piece.Empty; import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import janggi.domain.piece.PieceType; import janggi.domain.Route; import java.util.Collections; @@ -40,7 +40,7 @@ public boolean isAlly(Side side, Position position) { return board.get(position).isEqualSide(side); } - public List> getCurrentBoard() { + public List> getCurrentBoard() { CurrentBoard currentBoard = CurrentBoard.from(Collections.unmodifiableMap(board)); return currentBoard.getValues(); } diff --git a/src/main/java/janggi/domain/board/CurrentBoard.java b/src/main/java/janggi/domain/board/CurrentBoard.java index a743d9550e..8456291934 100644 --- a/src/main/java/janggi/domain/board/CurrentBoard.java +++ b/src/main/java/janggi/domain/board/CurrentBoard.java @@ -7,24 +7,24 @@ import janggi.domain.Position; import janggi.domain.piece.Piece; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; public class CurrentBoard { - private final List> currentBoard; + private final List> currentBoard; - public CurrentBoard(List> currentBoard) { + public CurrentBoard(List> currentBoard) { this.currentBoard = currentBoard; } public static CurrentBoard from(Map board) { - List> rows = new ArrayList<>(); + List> rows = new ArrayList<>(); for(int row = BOARD_START_ROWS; row <= BOARD_END_ROWS; row++) { - List currentRow = new ArrayList<>(); + List currentRow = new ArrayList<>(); for(int col = BOARD_START_COLS; col <= BOARD_END_COLS; col++) { Position position = new Position(row, col); currentRow.add(board.get(position).getPieceInfo()); @@ -34,7 +34,7 @@ public static CurrentBoard from(Map board) { return new CurrentBoard(Collections.unmodifiableList(rows)); } - public List> getValues() { + public List> getValues() { return currentBoard; } } diff --git a/src/main/java/janggi/domain/piece/BasePiece.java b/src/main/java/janggi/domain/piece/BasePiece.java index 2871c9513b..97de12de24 100644 --- a/src/main/java/janggi/domain/piece/BasePiece.java +++ b/src/main/java/janggi/domain/piece/BasePiece.java @@ -22,7 +22,7 @@ public boolean isEqualSide(Side side) { } @Override - public PieceManifest getPieceInfo() { - return new PieceManifest(side, pieceType); + public PieceAttribute getPieceInfo() { + return new PieceAttribute(side, pieceType); } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 9a97d9c3c7..9935209f26 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -14,5 +14,5 @@ public interface Piece { boolean isEqualSide(Side side); - PieceManifest getPieceInfo(); + PieceAttribute getPieceInfo(); } diff --git a/src/main/java/janggi/domain/piece/PieceAttribute.java b/src/main/java/janggi/domain/piece/PieceAttribute.java new file mode 100644 index 0000000000..70c6a176b0 --- /dev/null +++ b/src/main/java/janggi/domain/piece/PieceAttribute.java @@ -0,0 +1,6 @@ +package janggi.domain.piece; + +import janggi.domain.Side; + +public record PieceAttribute(Side side, PieceType pieceType) { +} diff --git a/src/main/java/janggi/domain/piece/PieceManifest.java b/src/main/java/janggi/domain/piece/PieceManifest.java deleted file mode 100644 index c2c7458001..0000000000 --- a/src/main/java/janggi/domain/piece/PieceManifest.java +++ /dev/null @@ -1,6 +0,0 @@ -package janggi.domain.piece; - -import janggi.domain.Side; - -public record PieceManifest(Side side, PieceType pieceType) { -} diff --git a/src/main/java/janggi/domain/turn/BaseTurn.java b/src/main/java/janggi/domain/turn/BaseTurn.java index 5ab5869a73..24359b7ce9 100644 --- a/src/main/java/janggi/domain/turn/BaseTurn.java +++ b/src/main/java/janggi/domain/turn/BaseTurn.java @@ -2,7 +2,7 @@ import janggi.domain.Side; import janggi.domain.board.Board; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import java.util.List; public abstract class BaseTurn implements PlayerTurn { @@ -20,7 +20,7 @@ public boolean isFinished() { } @Override - public List> getCurrentBoard() { + public List> getCurrentBoard() { return board.getCurrentBoard(); } diff --git a/src/main/java/janggi/domain/turn/PlayerTurn.java b/src/main/java/janggi/domain/turn/PlayerTurn.java index 4184539908..3c1fd069b2 100644 --- a/src/main/java/janggi/domain/turn/PlayerTurn.java +++ b/src/main/java/janggi/domain/turn/PlayerTurn.java @@ -2,7 +2,7 @@ import janggi.domain.Position; import janggi.domain.Side; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import java.util.List; public interface PlayerTurn { @@ -10,7 +10,7 @@ public interface PlayerTurn { boolean isFinished(); - List> getCurrentBoard(); + List> getCurrentBoard(); Side getCurrentSide(); diff --git a/src/main/java/janggi/dto/BoardDto.java b/src/main/java/janggi/dto/BoardDto.java index 82666beff0..72dbd065eb 100644 --- a/src/main/java/janggi/dto/BoardDto.java +++ b/src/main/java/janggi/dto/BoardDto.java @@ -1,7 +1,7 @@ package janggi.dto; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import java.util.List; -public record BoardDto(List> board) { +public record BoardDto(List> board) { } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 0419928c3f..3f1685148c 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,7 +1,7 @@ package janggi.view; import janggi.domain.Side; -import janggi.domain.piece.PieceManifest; +import janggi.domain.piece.PieceAttribute; import janggi.domain.piece.PieceType; import janggi.dto.BoardDto; import java.util.List; @@ -17,7 +17,7 @@ public class OutputView { private static final String ERROR_PREFIX = "[ERROR] "; public static void printBoard(BoardDto boardDto) { - List> board = boardDto.board(); + List> board = boardDto.board(); StringBuilder result = new StringBuilder(); result.append(buildColumnHeader(board.getFirst().size())); appendBoardRows(board, result); @@ -45,7 +45,7 @@ private static String buildColumnHeader(int colSize) { return header.toString(); } - private static void appendBoardRows(List> board, StringBuilder result) { + private static void appendBoardRows(List> board, StringBuilder result) { result.append(buildTopBorder(board.getFirst().size())); for (int row = 0; row < board.size(); row++) { appendBoardRow(board.get(row), row, result); @@ -53,10 +53,10 @@ private static void appendBoardRows(List> board, StringBuild } } - private static void appendBoardRow(List row, int rowIndex, StringBuilder result) { + private static void appendBoardRow(List row, int rowIndex, StringBuilder result) { result.append(String.format(" %2d ┃", rowIndex + 1)); - for (PieceManifest pieceManifest : row) { - result.append(formatCell(pieceManifest)); + for (PieceAttribute pieceAttribute : row) { + result.append(formatCell(pieceAttribute)); result.append("┃"); } result.append(System.lineSeparator()); @@ -86,11 +86,11 @@ private static String buildBorder(String start, String middle, String end, int c return border.toString(); } - private static String formatCell(PieceManifest pieceManifest) { - if (isEmpty(pieceManifest)) { + private static String formatCell(PieceAttribute pieceAttribute) { + if (isEmpty(pieceAttribute)) { return " " + EMPTY_CELL + " "; } - return " " + colorize(pieceManifest.pieceType().getName(), pieceManifest.side()) + " "; + return " " + colorize(pieceAttribute.pieceType().getName(), pieceAttribute.side()) + " "; } private static String colorize(String pieceName, Side side) { @@ -103,10 +103,10 @@ private static String colorize(String pieceName, Side side) { return pieceName; } - private static boolean isEmpty(PieceManifest pieceManifest) { - if (pieceManifest == null) { + private static boolean isEmpty(PieceAttribute pieceAttribute) { + if (pieceAttribute == null) { return true; } - return pieceManifest.pieceType() == PieceType.NONE || pieceManifest.side() == Side.EMPTY; + return pieceAttribute.pieceType() == PieceType.NONE || pieceAttribute.side() == Side.EMPTY; } } From 0bc4f566665f418bfe054b312212f31ae0e51dab Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 09:38:10 +0900 Subject: [PATCH 05/29] =?UTF-8?q?feat:=20dx,dy=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=A0=95=EC=A0=81=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Movement.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/janggi/domain/Movement.java b/src/main/java/janggi/domain/Movement.java index ddc3fc681e..3bf8c16ed5 100644 --- a/src/main/java/janggi/domain/Movement.java +++ b/src/main/java/janggi/domain/Movement.java @@ -1,5 +1,7 @@ package janggi.domain; +import java.util.Arrays; + public enum Movement { UP(-1, 0), DOWN(1, 0), @@ -10,6 +12,8 @@ public enum Movement { DOWN_RIGHT(1, 1), DOWN_LEFT(1, -1); + private static final String INVALID_DELTA_DIRECTION_MESSAGE = "해당 dx,dy에 대한 movement가 없습니다."; + private final int dx; private final int dy; @@ -25,4 +29,11 @@ public int getDx() { public int getDy() { return dy; } + + public static Movement of(int dx, int dy) { + return Arrays.stream(Movement.values()) + .filter(movement -> movement.dx == dx && movement.dy == dy) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_DELTA_DIRECTION_MESSAGE)); + } } From 20564a3dbfd236be12fc1259e0a882afa35544ab Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 11:03:04 +0900 Subject: [PATCH 06/29] =?UTF-8?q?refactor:=20Linear=20Piece=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EA=B3=84=EC=82=B0=20=EB=B0=A9=EC=8B=9D=20Movement?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Movement.java | 2 +- src/main/java/janggi/domain/Position.java | 33 ++++++++++++----- .../java/janggi/domain/piece/LinearPiece.java | 36 ++++--------------- src/main/java/janggi/domain/piece/Pawn.java | 26 ++++++++++---- .../domain/piece/SingleLinearPiece.java | 16 +++++++-- .../java/janggi/domain/piece/ChaTest.java | 2 +- .../java/janggi/domain/piece/RouteTest.java | 22 ++++++++---- 7 files changed, 81 insertions(+), 56 deletions(-) diff --git a/src/main/java/janggi/domain/Movement.java b/src/main/java/janggi/domain/Movement.java index 3bf8c16ed5..b8bf62b005 100644 --- a/src/main/java/janggi/domain/Movement.java +++ b/src/main/java/janggi/domain/Movement.java @@ -34,6 +34,6 @@ public static Movement of(int dx, int dy) { return Arrays.stream(Movement.values()) .filter(movement -> movement.dx == dx && movement.dy == dy) .findFirst() - .orElseThrow(() -> new IllegalArgumentException(INVALID_DELTA_DIRECTION_MESSAGE)); + .orElseThrow(() -> new IllegalStateException(INVALID_DELTA_DIRECTION_MESSAGE)); } } diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index ed1a7e689d..ce5f26f3a4 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -15,6 +15,8 @@ public class Position { private static final String INVALID_ROW_RANGE = "유효하지 않은 위치입니다. 행은 1부터 10까지 가능합니다."; private static final String INVALID_COL_RANGE = "유효하지 않은 위치입니다. 열은 1부터 9까지 가능합니다."; + private static final String INVALID_LINEAR_POSITION = "직선이 아닙니다."; + private final int x; private final int y; @@ -76,19 +78,32 @@ public Position move(Movement movement) { return new Position(x + movement.getDx(), y + movement.getDy()); } - public int calculateRowDistance(Position position) { - return position.x - x; - } + public Movement getLinearDirection(Position position) { + if(!isLinear(position)) { + throw new IllegalStateException(INVALID_LINEAR_POSITION); + } + + int dx = Integer.compare(position.x, x); + int dy = Integer.compare(position.y, y); - public int calculateColumnDistance(Position position) { - return position.y - y; + return Movement.of(dx, dy); } - public boolean isHorizontal(Position position) { - return position.x == this.x; + public int calculateLinearDistance(Position position) { + if(!isLinear(position)) { + throw new IllegalStateException(INVALID_LINEAR_POSITION); + } + + int dx = Math.abs(position.x - x); + int dy = Math.abs(position.y - y); + + return Math.max(dx, dy); } - public boolean isVertical(Position position) { - return position.y == this.y; + private boolean isLinear(Position position) { + int dx = Math.abs(position.x - x); + int dy = Math.abs(position.y - y); + + return (dx == 0 && dy != 0) || (dy == 0 && dx != 0); } } diff --git a/src/main/java/janggi/domain/piece/LinearPiece.java b/src/main/java/janggi/domain/piece/LinearPiece.java index e5a2a59d96..7c9228e42d 100644 --- a/src/main/java/janggi/domain/piece/LinearPiece.java +++ b/src/main/java/janggi/domain/piece/LinearPiece.java @@ -15,15 +15,15 @@ public LinearPiece(RoutePolicy routePolicy, Side side, PieceType pieceType) { @Override public Route findRoute(Position start, Position end) { - boolean isVertical = start.isVertical(end); - boolean isHorizontal = start.isHorizontal(end); + try { + Movement direction = start.getLinearDirection(end); + int distance = start.calculateLinearDistance(end); - if (!isVertical && !isHorizontal) { + return calculatePath(start, direction, distance); + } catch (IllegalStateException e) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } - int dist = start.calculateColumnDistance(end) + start.calculateRowDistance(end); - return calculatePath(start, isVertical, dist); } @Override @@ -33,32 +33,10 @@ public void validateRoute(Route route, BaseBoard boardInfo) { } } - private Route calculatePath(Position start, boolean isVertical, int dist) { - Movement movement = resolveMovement(isVertical, dist); + protected Route calculatePath(Position start, Movement direction, int dist) { return new Route(Stream - .iterate(start, current -> current.move(movement)) + .iterate(start, current -> current.move(direction)) .limit(Math.abs(dist) + 1) .toList()); } - - private Movement resolveMovement(boolean isVertical, int dist) { - if (isVertical) { - return resolveVerticalMovement(dist); - } - return resolveHorizontalMovement(dist); - } - - private Movement resolveVerticalMovement(int dist) { - if (dist < 0) { - return Movement.UP; - } - return Movement.DOWN; - } - - private Movement resolveHorizontalMovement(int dist) { - if (dist < 0) { - return Movement.LEFT; - } - return Movement.RIGHT; - } } diff --git a/src/main/java/janggi/domain/piece/Pawn.java b/src/main/java/janggi/domain/piece/Pawn.java index d8830ec1cb..741bf7284e 100644 --- a/src/main/java/janggi/domain/piece/Pawn.java +++ b/src/main/java/janggi/domain/piece/Pawn.java @@ -1,10 +1,18 @@ package janggi.domain.piece; +import janggi.domain.Movement; import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; +import java.util.Map; +import java.util.Set; public class Pawn extends SingleLinearPiece { + private Map> isBackward = Map.of( + Side.HAN, Set.of(Movement.UP, Movement.UP_LEFT, Movement.UP_RIGHT), + Side.CHO, Set.of(Movement.DOWN, Movement.DOWN_LEFT, Movement.DOWN_RIGHT) + ); + public Pawn(Side side) { super(side, PieceType.PAWN); } @@ -12,17 +20,21 @@ public Pawn(Side side) { @Override public Route findRoute(Position start, Position end) { - if(isBackward(start, end, side)) { + try { + Movement direction = start.getLinearDirection(end); + validateDirection(direction); + + int distance = start.calculateLinearDistance(end); + + return calculatePath(start, direction, distance); + } catch (IllegalStateException e) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } - return super.findRoute(start, end); - } - private boolean isBackward(Position start, Position end, Side side) { - if (side.equals(Side.CHO)) { - return start.calculateRowDistance(end) > 0; + private void validateDirection(Movement direction) { + if(isBackward.get(side).contains(direction)) { + throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } - return start.calculateRowDistance(end) < 0; } } diff --git a/src/main/java/janggi/domain/piece/SingleLinearPiece.java b/src/main/java/janggi/domain/piece/SingleLinearPiece.java index b7db6a4090..354fdb0907 100644 --- a/src/main/java/janggi/domain/piece/SingleLinearPiece.java +++ b/src/main/java/janggi/domain/piece/SingleLinearPiece.java @@ -1,5 +1,6 @@ package janggi.domain.piece; +import janggi.domain.Movement; import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; @@ -14,9 +15,20 @@ public SingleLinearPiece(Side side, PieceType pieceType) { @Override public Route findRoute(Position start, Position end) { - if(Math.abs(start.calculateRowDistance(end) + start.calculateColumnDistance(end)) != RESTRICTED_DISTANCE) { + try { + Movement direction = start.getLinearDirection(end); + int distance = start.calculateLinearDistance(end); + validateDistance(distance); + + return calculatePath(start, direction, distance); + } catch (IllegalStateException e) { + throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); + } + } + + private void validateDistance(int distance) { + if(distance != RESTRICTED_DISTANCE) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } - return super.findRoute(start, end); } } diff --git a/src/test/java/janggi/domain/piece/ChaTest.java b/src/test/java/janggi/domain/piece/ChaTest.java index 44b4119949..50f6542991 100644 --- a/src/test/java/janggi/domain/piece/ChaTest.java +++ b/src/test/java/janggi/domain/piece/ChaTest.java @@ -15,7 +15,7 @@ class ChaTest { void 시작_좌표와_끝_좌표가_같은_선_상에_존재하지_않으면_에러가_발생한다() { Cha cha = new Cha(Side.CHO); Position start = new Position(2, 3); - Position end = new Position(3, 4); + Position end = new Position(5, 6); assertThatThrownBy(() -> cha.findRoute(start, end)) .isInstanceOf(IllegalArgumentException.class) diff --git a/src/test/java/janggi/domain/piece/RouteTest.java b/src/test/java/janggi/domain/piece/RouteTest.java index 888ac185df..b5aa6807ba 100644 --- a/src/test/java/janggi/domain/piece/RouteTest.java +++ b/src/test/java/janggi/domain/piece/RouteTest.java @@ -8,6 +8,14 @@ import org.junit.jupiter.api.Test; public class RouteTest { + private boolean isLinear(Position point, Position position) { + try { + point.getLinearDirection(position); + return true; + } catch (Exception e) { + return false; + } + } @Test void 시작과_종료_사이_칸이_없는_경우_모든_조건은_항상_참으로_리턴한다() { Route route = new Route(List.of(new Position(1,1), new Position(1,2))); @@ -24,7 +32,7 @@ public class RouteTest { new Position(1, 5) )); - assertThat(route.isEveryBetween(position -> position.isHorizontal(new Position(1, 6)))).isTrue(); + assertThat(route.isEveryBetween(position -> isLinear(new Position(1, 1), position))).isTrue(); } @Test @@ -37,7 +45,7 @@ public class RouteTest { new Position(1, 5) )); - assertThat(route.isEveryBetween(position -> position.isHorizontal(new Position(1, 6)))).isFalse(); + assertThat(route.isEveryBetween(position -> isLinear(new Position(1, 1), position))).isFalse(); } @Test @@ -50,7 +58,7 @@ public class RouteTest { new Position(5, 5) )); - assertThat(route.isAnyBetween(position -> position.isHorizontal(new Position(1, 6)))).isTrue(); + assertThat(route.isAnyBetween(position -> isLinear(new Position(1, 1), position))).isTrue(); } @Test @@ -63,7 +71,7 @@ public class RouteTest { new Position(5, 5) )); - assertThat(route.isAnyBetween(position -> position.isHorizontal(new Position(1, 6)))).isFalse(); + assertThat(route.isAnyBetween(position -> isLinear(new Position(1, 1), position))).isFalse(); } @Test @@ -76,7 +84,7 @@ public class RouteTest { new Position(3, 5) )); - assertThat(route.countBetween(position -> position.isHorizontal(new Position(1, 6)))).isEqualTo(2); + assertThat(route.countBetween(position -> isLinear(new Position(1, 1), position))).isEqualTo(2); } @Test @@ -103,7 +111,7 @@ public class RouteTest { new Position(5, 5) )); - assertThat(route.isDestinationSatisfied(position -> position.isVertical(new Position(2, 5)))).isTrue(); - assertThat(route.isDestinationSatisfied(position -> position.isHorizontal(new Position(2, 5)))).isFalse(); + assertThat(route.isDestinationSatisfied(position -> isLinear(new Position(2, 5), position))).isTrue(); + assertThat(route.isDestinationSatisfied(position -> isLinear(new Position(6, 7), position))).isFalse(); } } From 0314ed8bc91e87832ac04f29aa72a5bee727da32 Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 11:08:30 +0900 Subject: [PATCH 07/29] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=83=81=EC=9C=84=20=EB=8B=A8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/piece/ActivePiece.java | 9 +++++++++ src/main/java/janggi/domain/piece/LinearPiece.java | 7 ------- src/main/java/janggi/domain/piece/StepPiece.java | 7 ------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/main/java/janggi/domain/piece/ActivePiece.java b/src/main/java/janggi/domain/piece/ActivePiece.java index cde36d3b50..59fabb3499 100644 --- a/src/main/java/janggi/domain/piece/ActivePiece.java +++ b/src/main/java/janggi/domain/piece/ActivePiece.java @@ -1,6 +1,8 @@ package janggi.domain.piece; +import janggi.domain.Route; import janggi.domain.Side; +import janggi.domain.board.BaseBoard; import janggi.domain.policy.RoutePolicy; public abstract class ActivePiece extends BasePiece { @@ -13,4 +15,11 @@ public ActivePiece(RoutePolicy routePolicy, Side side, PieceType pieceType) { super(side, pieceType); this.routePolicy = routePolicy; } + + @Override + public void validateRoute(Route route, BaseBoard boardInfo) { + if (!routePolicy.isMovable(route, side, boardInfo)) { + throw new IllegalArgumentException(UNMOVABLE_ROUTE_MESSAGE); + } + } } diff --git a/src/main/java/janggi/domain/piece/LinearPiece.java b/src/main/java/janggi/domain/piece/LinearPiece.java index 7c9228e42d..aa1db57f3d 100644 --- a/src/main/java/janggi/domain/piece/LinearPiece.java +++ b/src/main/java/janggi/domain/piece/LinearPiece.java @@ -26,13 +26,6 @@ public Route findRoute(Position start, Position end) { } - @Override - public void validateRoute(Route route, BaseBoard boardInfo) { - if (!routePolicy.isMovable(route, side, boardInfo)) { - throw new IllegalArgumentException(UNMOVABLE_ROUTE_MESSAGE); - } - } - protected Route calculatePath(Position start, Movement direction, int dist) { return new Route(Stream .iterate(start, current -> current.move(direction)) diff --git a/src/main/java/janggi/domain/piece/StepPiece.java b/src/main/java/janggi/domain/piece/StepPiece.java index 17d2c735e1..c391d9d841 100644 --- a/src/main/java/janggi/domain/piece/StepPiece.java +++ b/src/main/java/janggi/domain/piece/StepPiece.java @@ -26,11 +26,4 @@ public Route findRoute(Position start, Position end) { .findFirst() .orElseThrow(() -> new IllegalArgumentException(INVALID_DESTINATION_MESSAGE)); } - - @Override - public void validateRoute(Route route, BaseBoard boardInfo) { - if (!routePolicy.isMovable(route, side, boardInfo)) { - throw new IllegalArgumentException(UNMOVABLE_ROUTE_MESSAGE); - } - } } From 89b3f7abd0eb77f8ffc7bdfdc6f07d4ffc24e7b4 Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 13:35:41 +0900 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=EA=B6=81=EC=84=B1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 36 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index ce5f26f3a4..105fc6ad48 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -2,15 +2,28 @@ import java.util.List; import java.util.Objects; +import java.util.Set; public class Position { - public static final int POSITION_COMPONENTS_SIZE = 2; - public static final int BOARD_START_ROWS = 1; public static final int BOARD_START_COLS = 1; public static final int BOARD_END_ROWS = 10; public static final int BOARD_END_COLS = 9; + public static final int POSITION_COMPONENTS_SIZE = 2; + + private static final Integer GUNG_SUNG_COL_START = 4; + private static final Integer GUNG_SUNG_COL_END = 6; + private static final Integer HAN_GUNG_SUNG_ROW_START = 1; + private static final Integer HAN_GUNG_SUNG_ROW_END = 3; + private static final Integer CHO_GUNG_SUNG_ROW_START = 8; + private static final Integer CHO_GUNG_SUNG_ROW_END = 10; + + private static final Set GungSungM = Set.of( + new Position(1, 5), new Position(2, 4), new Position(2, 6), new Position(3,5), + new Position(8, 5), new Position(9, 4), new Position(9, 6), new Position(10,5) + ); + private static final String INVALID_POSITION_SIZE = "행과 열 두 개의 값만 입력하세요."; private static final String INVALID_ROW_RANGE = "유효하지 않은 위치입니다. 행은 1부터 10까지 가능합니다."; private static final String INVALID_COL_RANGE = "유효하지 않은 위치입니다. 열은 1부터 9까지 가능합니다."; @@ -101,9 +114,26 @@ public int calculateLinearDistance(Position position) { } private boolean isLinear(Position position) { + if(position.x == x && position.y == y) { + return false; + } + int dx = Math.abs(position.x - x); int dy = Math.abs(position.y - y); - return (dx == 0 && dy != 0) || (dy == 0 && dx != 0); + return dx == 0 || dy == 0 || isGungSungDiagonal(position); + } + + private boolean isGungSungDiagonal(Position position) { + return isGungSung(this) && isGungSung(position) && (!GungSungM.contains(this) && !GungSungM.contains(position)); + } + + private boolean isGungSung(Position position) { + if(position.y < GUNG_SUNG_COL_START || position.y > GUNG_SUNG_COL_END) { + return false; + } + + return (position.x >= HAN_GUNG_SUNG_ROW_START && position.x <= HAN_GUNG_SUNG_ROW_END) + || (position.x >= CHO_GUNG_SUNG_ROW_START && position.x <= CHO_GUNG_SUNG_ROW_END); } } From 5bebc61038111a34c5c76db394f60a189e313955 Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 21:35:38 +0900 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20=EC=A0=90=EC=88=98=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 7 ++++ src/main/java/janggi/domain/Game.java | 18 +++++++- src/main/java/janggi/domain/SideScore.java | 4 ++ src/main/java/janggi/domain/board/Board.java | 28 ++++++++----- .../janggi/domain/board/MaterialScore.java | 41 +++++++++++++++++++ .../java/janggi/domain/piece/BasePiece.java | 5 +++ src/main/java/janggi/domain/piece/Piece.java | 2 + .../java/janggi/domain/piece/PieceType.java | 24 +++++++---- .../java/janggi/domain/turn/BaseTurn.java | 6 +++ .../java/janggi/domain/turn/PlayerTurn.java | 3 ++ src/main/java/janggi/view/OutputView.java | 17 ++++++++ 11 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 src/main/java/janggi/domain/SideScore.java create mode 100644 src/main/java/janggi/domain/board/MaterialScore.java diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index d1e90017af..2cba79e132 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -39,6 +39,7 @@ private boolean playTurnGame(Game game) { try { printCurrentStatus(game); movePiece(game); + printCurrentScore(game); return isFinishedGame(game); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); @@ -51,6 +52,7 @@ private boolean playTurnGame(Game game) { } private void printCurrentStatus(Game game) { + OutputView.printLine(); OutputView.printBoard(game.getCurrentBoardDto()); OutputView.printTurn(game.getCurrentSide()); } @@ -65,6 +67,11 @@ private void movePiece(Game game) { game.move(startPosition, endPosition); } + private void printCurrentScore(Game game) { + OutputView.printLine(); + OutputView.printScore(game.getCurrentSideScore()); + } + private boolean isFinishedGame(Game game) { if (game.isFinished()) { OutputView.printWinner(game.getWinnerSide()); diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index 41c0999796..cc31a98991 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -1,10 +1,12 @@ package janggi.domain; import janggi.domain.board.Board; +import janggi.domain.piece.Piece; import janggi.domain.turn.ChoTurn; import janggi.domain.turn.PlayerTurn; import janggi.dto.BoardDto; import janggi.initializer.BoardInitializer; +import java.util.Map; public class Game { private static final String INVALID_WINNER_SIDE = "잘못된 승자 진영입니다."; @@ -12,7 +14,17 @@ public class Game { private PlayerTurn playerTurn; public Game(Arrangement choArrangement, Arrangement hanArrangement) { - this.playerTurn = new ChoTurn(new Board(BoardInitializer.createBoard(choArrangement, hanArrangement))); + Map initBoard = BoardInitializer.createBoard(choArrangement, hanArrangement); + int hanScore = initBoard.values().stream() + .filter(piece -> piece.isEqualSide(Side.HAN)) + .mapToInt(Piece::getPieceScore) + .sum(); + int choScore = initBoard.values().stream() + .filter(piece -> piece.isEqualSide(Side.CHO)) + .mapToInt(Piece::getPieceScore) + .sum(); + + this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore)); } public void move(Position start, Position end) { @@ -31,6 +43,10 @@ public Side getCurrentSide() { return playerTurn.getCurrentSide(); } + public SideScore getCurrentSideScore() { + return playerTurn.getCurrentScore(); + } + public Side getWinnerSide() { Side winnerSide = playerTurn.getWinnerSide(); if (winnerSide.equals(Side.EMPTY)) { diff --git a/src/main/java/janggi/domain/SideScore.java b/src/main/java/janggi/domain/SideScore.java new file mode 100644 index 0000000000..de9f96de68 --- /dev/null +++ b/src/main/java/janggi/domain/SideScore.java @@ -0,0 +1,4 @@ +package janggi.domain; + +public record SideScore(int han, int cho) { +} diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index c3cc758b67..0e293423a3 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -2,6 +2,7 @@ import janggi.domain.Position; import janggi.domain.Side; +import janggi.domain.SideScore; import janggi.domain.piece.Empty; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceAttribute; @@ -16,13 +17,11 @@ public class Board implements BaseBoard { private static final String INVALID_PIECE_SIDE_MESSAGE = "자기 진영의 기물만 움직일 수 있습니다."; private final Map board; - private final Map isGungAlive = new HashMap<>(); + private final MaterialScore materialScore; - public Board(Map board) { + public Board(Map board, int hanScore, int choScore) { this.board = board; - - isGungAlive.put(Side.CHO, true); - isGungAlive.put(Side.HAN, true); + this.materialScore = new MaterialScore(hanScore, choScore); } @Override @@ -45,28 +44,37 @@ public List> getCurrentBoard() { return currentBoard.getValues(); } - public void move(Position start, Position end, Side side) { + public void move(Position start, Position end, Side movableSide) { Piece piece = board.get(start); - if (!piece.isEqualSide(side)) { + if (!piece.isEqualSide(movableSide)) { throw new IllegalArgumentException(INVALID_PIECE_SIDE_MESSAGE); } Route route = piece.findRoute(start, end); piece.validateRoute(route, this); - movePiece(start, end, piece, side); + movePiece(start, end, piece, movableSide); } public boolean isEndGame() { - return isGungAlive.values().stream().anyMatch(isCaptured -> !isCaptured); + return materialScore.isAnyGungDead(); + } + + public SideScore getScore() { + return materialScore.getCurrentScore(); } private void movePiece(Position start, Position end, Piece piece, Side side) { Piece destinationPiece = board.get(end); if(destinationPiece.isEqualPieceType(PieceType.GUNG)) { - isGungAlive.put(side.getOppositeSide(), false); + materialScore.updateGungDead(side.getOppositeSide()); } + + if(!destinationPiece.isEqualPieceType(PieceType.NONE)) { + materialScore.decreaseScore(side.getOppositeSide(), destinationPiece.getPieceScore()); + } + board.put(end, piece); board.put(start, new Empty()); } diff --git a/src/main/java/janggi/domain/board/MaterialScore.java b/src/main/java/janggi/domain/board/MaterialScore.java new file mode 100644 index 0000000000..4cbed8e8c8 --- /dev/null +++ b/src/main/java/janggi/domain/board/MaterialScore.java @@ -0,0 +1,41 @@ +package janggi.domain.board; + +import janggi.domain.Side; +import janggi.domain.SideScore; +import java.util.HashMap; +import java.util.Map; + +public class MaterialScore { + private final Map isGungAlive; + private final Map score; + + public MaterialScore(int hanScore, int choScore) { + this.isGungAlive = new HashMap<>(Map.of(Side.HAN, true, Side.CHO, true)); + this.score = new HashMap<>(Map.of(Side.HAN, hanScore, Side.CHO, choScore)); + } + + public SideScore getCurrentScore() { + return new SideScore(score.get(Side.HAN), score.get(Side.CHO)); + } + + public boolean isAnyGungDead() { + return isGungAlive.values().stream().anyMatch(isCaptured -> !isCaptured); + } + + public void updateGungDead(Side side) { + isGungAlive.put(side, false); + } + + public void decreaseScore(Side side, int pieceScore) { + if(pieceScore < 0) { + throw new IllegalStateException(""); + } + + Integer currentScore = score.get(side); + if(currentScore == null) { + throw new IllegalArgumentException(""); + } + + score.put(side, currentScore - pieceScore); + } +} diff --git a/src/main/java/janggi/domain/piece/BasePiece.java b/src/main/java/janggi/domain/piece/BasePiece.java index 97de12de24..efaa5dba7f 100644 --- a/src/main/java/janggi/domain/piece/BasePiece.java +++ b/src/main/java/janggi/domain/piece/BasePiece.java @@ -25,4 +25,9 @@ public boolean isEqualSide(Side side) { public PieceAttribute getPieceInfo() { return new PieceAttribute(side, pieceType); } + + @Override + public int getPieceScore() { + return this.pieceType.getScore(); + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index 9935209f26..fcfb2cf5c5 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -14,5 +14,7 @@ public interface Piece { boolean isEqualSide(Side side); + int getPieceScore(); + PieceAttribute getPieceInfo(); } diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java index 59710b76b2..65260c1fb8 100644 --- a/src/main/java/janggi/domain/piece/PieceType.java +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -1,22 +1,28 @@ package janggi.domain.piece; public enum PieceType { - CHA("CH"), - GUNG("GU"), - MA("MA"), - NONE("."), - PAWN("JO"), - PO("PO"), - SA("SA"), - SANG("SG"); + CHA("CH", 13), + GUNG("GU", 0), + MA("MA", 5), + NONE(".", 0), + PAWN("JO", 2), + PO("PO", 7), + SA("SA", 3), + SANG("SG", 3); private final String name; + private final int score; - PieceType(String name) { + PieceType(String name, int score) { this.name = name; + this.score = score; } public String getName() { return name; } + + public int getScore() { + return score; + } } diff --git a/src/main/java/janggi/domain/turn/BaseTurn.java b/src/main/java/janggi/domain/turn/BaseTurn.java index 24359b7ce9..c561026856 100644 --- a/src/main/java/janggi/domain/turn/BaseTurn.java +++ b/src/main/java/janggi/domain/turn/BaseTurn.java @@ -1,6 +1,7 @@ package janggi.domain.turn; import janggi.domain.Side; +import janggi.domain.SideScore; import janggi.domain.board.Board; import janggi.domain.piece.PieceAttribute; import java.util.List; @@ -33,4 +34,9 @@ public Side getCurrentSide() { public Side getWinnerSide() { return Side.EMPTY; } + + @Override + public SideScore getCurrentScore() { + return board.getScore(); + } } diff --git a/src/main/java/janggi/domain/turn/PlayerTurn.java b/src/main/java/janggi/domain/turn/PlayerTurn.java index 3c1fd069b2..6efdde627c 100644 --- a/src/main/java/janggi/domain/turn/PlayerTurn.java +++ b/src/main/java/janggi/domain/turn/PlayerTurn.java @@ -2,6 +2,7 @@ import janggi.domain.Position; import janggi.domain.Side; +import janggi.domain.SideScore; import janggi.domain.piece.PieceAttribute; import java.util.List; @@ -15,4 +16,6 @@ public interface PlayerTurn { Side getCurrentSide(); Side getWinnerSide(); + + SideScore getCurrentScore(); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index 3f1685148c..aef3243322 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,6 +1,7 @@ package janggi.view; import janggi.domain.Side; +import janggi.domain.SideScore; import janggi.domain.piece.PieceAttribute; import janggi.domain.piece.PieceType; import janggi.dto.BoardDto; @@ -14,8 +15,19 @@ public class OutputView { private static final String ANSI_GREEN = "\u001B[32m"; private static final String TURN_PREFIX = "현재 턴: "; + + private static final String SCORE_PREFIX = "[ 현재 점수 현황 ]"; + private static final String HAN_SCORE_PREFIX = "한: "; + private static final String CHO_SCORE_PREFIX = "초: "; + private static final String SCORE_SUFFIX = "점"; + private static final String SCORE_DELIMITER = ", "; + private static final String ERROR_PREFIX = "[ERROR] "; + public static void printLine() { + System.out.println(); + } + public static void printBoard(BoardDto boardDto) { List> board = boardDto.board(); StringBuilder result = new StringBuilder(); @@ -28,6 +40,11 @@ public static void printTurn(Side side) { System.out.println(TURN_PREFIX + side.getName()); } + public static void printScore(SideScore score) { + System.out.println(SCORE_PREFIX); + System.out.println(CHO_SCORE_PREFIX + score.cho() + SCORE_SUFFIX + SCORE_DELIMITER + HAN_SCORE_PREFIX + score.han() + SCORE_SUFFIX); + } + public static void printWinner(Side winnerSide) { System.out.printf("%s 승리!%n", winnerSide.getName()); } From 8e63e176fbd245b4efb0488ceaaf4c09b132bce8 Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 21:56:35 +0900 Subject: [PATCH 10/29] =?UTF-8?q?test:=20board,=20materialScore=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../janggi/domain/board/MaterialScore.java | 7 +++- .../java/janggi/domain/board/BoardTest.java | 27 ++++++++++++-- .../domain/board/MaterialScoreTest.java | 37 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/test/java/janggi/domain/board/MaterialScoreTest.java diff --git a/src/main/java/janggi/domain/board/MaterialScore.java b/src/main/java/janggi/domain/board/MaterialScore.java index 4cbed8e8c8..9ecd51ab16 100644 --- a/src/main/java/janggi/domain/board/MaterialScore.java +++ b/src/main/java/janggi/domain/board/MaterialScore.java @@ -6,6 +6,9 @@ import java.util.Map; public class MaterialScore { + private static final String INVALID_NEGATIVE_DECREASE_SCORE = "차감할 점수는 0보다 커야 합니다."; + private static final String INVALID_SCORE_SIDE = "해당 진영의 점수 정보가 존재하지 않습니다"; + private final Map isGungAlive; private final Map score; @@ -28,12 +31,12 @@ public void updateGungDead(Side side) { public void decreaseScore(Side side, int pieceScore) { if(pieceScore < 0) { - throw new IllegalStateException(""); + throw new IllegalArgumentException(INVALID_NEGATIVE_DECREASE_SCORE); } Integer currentScore = score.get(side); if(currentScore == null) { - throw new IllegalArgumentException(""); + throw new IllegalStateException(INVALID_SCORE_SIDE); } score.put(side, currentScore - pieceScore); diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 12d6812947..3d379df0be 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -6,6 +6,9 @@ import janggi.domain.Arrangement; import janggi.domain.Position; import janggi.domain.Side; +import janggi.domain.SideScore; +import janggi.domain.piece.Gung; +import janggi.domain.piece.Pawn; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceType; import janggi.domain.piece.Po; @@ -17,7 +20,7 @@ import org.junit.jupiter.params.provider.CsvSource; class BoardTest { - private Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG)); + private Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG), 72, 72); @Test void 빈_칸인지_여부를_제대로_반영한다() { @@ -42,7 +45,7 @@ class BoardTest { @Test void 자기_진영의_기물을_움직이면_정상_작동한다() { - Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG)); + Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG), 72, 72); board.move(new Position(1, 1), new Position(2, 1), Side.HAN); assertThat(board.getCurrentBoard().get(1).getFirst().pieceType()).isEqualTo(PieceType.CHA); @@ -51,8 +54,26 @@ class BoardTest { @Test void 다른_진영의_기물을_움직이면_예외_처리한다() { - Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG)); + Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG), 72, 72); assertThatThrownBy(() -> board.move(new Position(1, 1), new Position(2, 1), Side.CHO)).isInstanceOf(IllegalArgumentException.class).hasMessage("자기 진영의 기물만 움직일 수 있습니다."); } + + @Test + void 기물을_잡으면_상대편_진영의_점수가_깎인다() { + Map customBoard = new HashMap<>(Map.of(new Position(1, 1), new Pawn(Side.HAN), new Position(1, 2), new Pawn(Side.CHO))); + Board board = new Board(customBoard, PieceType.PAWN.getScore(), PieceType.PAWN.getScore()); + + board.move(new Position(1, 2), new Position(1, 1), Side.CHO); + assertThat(board.getScore()).isEqualTo(new SideScore(0, PieceType.PAWN.getScore())); + } + + @Test + void 궁을_잡으면_게임_끝나는지_확인하는_메서드에서_참으로_리턴한다() { + Map customBoard = new HashMap<>(Map.of(new Position(1, 1), new Pawn(Side.HAN), new Position(1, 2), new Gung(Side.CHO))); + Board board = new Board(customBoard,0, 0); + + board.move(new Position(1, 1), new Position(1, 2), Side.HAN); + assertThat(board.isEndGame()).isTrue(); + } } diff --git a/src/test/java/janggi/domain/board/MaterialScoreTest.java b/src/test/java/janggi/domain/board/MaterialScoreTest.java new file mode 100644 index 0000000000..3c82bbc674 --- /dev/null +++ b/src/test/java/janggi/domain/board/MaterialScoreTest.java @@ -0,0 +1,37 @@ +package janggi.domain.board; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import janggi.domain.Side; +import janggi.domain.SideScore; +import org.junit.jupiter.api.Test; + +public class MaterialScoreTest { + @Test + void 초기_점수를_정확하게_반환한다() { + MaterialScore materialScore = new MaterialScore(54, 72); + SideScore currentScore = materialScore.getCurrentScore(); + + assertThat(currentScore.cho()).isEqualTo(72); + assertThat(currentScore.han()).isEqualTo(54); + } + + @Test + void 점수_감소_메서드에서_음수가_들어오면_예외가_발생한다() { + MaterialScore materialScore = new MaterialScore(72, 72); + + assertThatThrownBy(() -> materialScore.decreaseScore(Side.HAN, -1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("차감할 점수는 0보다 커야 합니다."); + } + + @Test + void 궁이_죽기_전까지는_isAnyGungDead가_거짓이다() { + MaterialScore materialScore = new MaterialScore(72, 72); + assertThat(materialScore.isAnyGungDead()).isFalse(); + + materialScore.updateGungDead(Side.CHO); + assertThat(materialScore.isAnyGungDead()).isTrue(); + } +} From 23514d63aadf327c504f7e3e76d16be5bc332dba Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 22:21:24 +0900 Subject: [PATCH 11/29] =?UTF-8?q?feat:=20=EC=A2=85=EB=A3=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 32 ++++++++++++++++---- src/main/java/janggi/domain/board/Board.java | 1 - src/main/java/janggi/view/InputView.java | 22 +++++++++++--- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 2cba79e132..3c573dd935 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -6,10 +6,13 @@ import janggi.view.InputView; import janggi.view.OutputView; import java.util.List; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; public class Runner { + public static final String END_TEXT = "종료"; + private static final String UNEXPECTED_SERVER_ERROR_LOG_MESSAGE = "예측하지 못한 시스템 오류 발생"; private static final String SYSTEM_ERROR_MESSAGE = "시스템 오류가 발생하여 게임을 종료합니다."; @@ -38,9 +41,7 @@ private void playGame(Game game) { private boolean playTurnGame(Game game) { try { printCurrentStatus(game); - movePiece(game); - printCurrentScore(game); - return isFinishedGame(game); + return executeTurn(game); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); return true; @@ -57,16 +58,35 @@ private void printCurrentStatus(Game game) { OutputView.printTurn(game.getCurrentSide()); } - private void movePiece(Game game) { - List startPositionInput = InputView.askStartPosition(); - Position startPosition = Position.from(startPositionInput); + private boolean executeTurn(Game game) { + Optional> startPositionInput = InputView.askStartPosition(); + + if(startPositionInput.isEmpty()) { + return consentEndGame(game); + } + + Position startPosition = Position.from(startPositionInput.get()); List endPositionInput = InputView.askEndPosition(); Position endPosition = Position.from(endPositionInput); game.move(startPosition, endPosition); + + printCurrentScore(game); + return isFinishedGame(game); + } + + private boolean consentEndGame(Game game) { + String consentInput = InputView.consentEnd(); + + if(consentInput.equals(END_TEXT)) { + return false; + } + + return executeTurn(game); } + private void printCurrentScore(Game game) { OutputView.printLine(); OutputView.printScore(game.getCurrentSideScore()); diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index 0e293423a3..fdf0ebf4f9 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -9,7 +9,6 @@ import janggi.domain.piece.PieceType; import janggi.domain.Route; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index fb7328f322..956953d8b5 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -1,15 +1,19 @@ package janggi.view; +import static janggi.Runner.END_TEXT; + import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Scanner; public class InputView { private static final String HAN_ARRANGEMENT_MESSAGE = "한 진영의 배치를 입력해주세요. (예: 마상마상, 마상상마, 상마상마, 상마마상)"; private static final String CHO_ARRANGEMENT_MESSAGE = "초 진영의 배치를 입력해주세요. (예: 마상마상, 마상상마, 상마상마, 상마마상)"; - private static final String START_POSITION_MESSAGE = "움직일 기물의 시작 좌표를 입력해주세요. (예: 3,4)"; + private static final String START_POSITION_MESSAGE = "움직일 기물의 시작 좌표를 입력하거나, 종료하려면 '종료'를 입력해주세요. (예: 3,4 또는 종료)"; private static final String END_POSITION_MESSAGE = "움직일 기물의 도착 좌표를 입력해주세요. (예: 4,4)"; private static final String BASE_DELIMITER = ","; + private static final String CONSENT_END_MESSAGE = "종료하는 데 동의하시면 '종료'를, 계속하시려면 아무 키나 입력해주세요."; private static final String INVALID_POSITION_TYPE = "숫자만 입력 가능합니다."; @@ -25,12 +29,22 @@ public static String askChoArrangement() { return scanner.nextLine(); } - public static List askStartPosition() { + public static Optional> askStartPosition() { System.out.println(START_POSITION_MESSAGE); String input = scanner.nextLine(); - return Arrays.stream(input.split(BASE_DELIMITER)) + + if(input.equals(END_TEXT)) { + return Optional.empty(); + } + + return Optional.of(Arrays.stream(input.split(BASE_DELIMITER)) .map(InputView::parseInt) - .toList(); + .toList()); + } + + public static String consentEnd() { + System.out.println(CONSENT_END_MESSAGE); + return scanner.nextLine(); } public static List askEndPosition() { From 200784c377d8f4e87338643e796ed35d7b9f088d Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 22:35:30 +0900 Subject: [PATCH 12/29] =?UTF-8?q?docs:=20readme=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 694745386c..a1f3141ab1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ - 시작 시 초/한 진영의 기본 기물 배치와 마상 배치를 반영해 초기화한다. - 장기판은 현재 위치의 기물 조회, 빈 칸 여부, 아군/적군 여부를 판단한다. - 장기판은 기물 이동 요청을 받아 실제 상태를 변경한다. +- 장기판은 상대 기물을 잡았을 때 해당 진영의 점수를 실시간으로 차감한다. +- 장기판은 어느 한 진영의 궁이 잡히면 즉시 게임 종료 상태로 변경한다. ### 기물 - 기물은 종류와 진영 정보를 가진다. @@ -23,17 +25,23 @@ - 좌표는 입력값을 검증하여 생성한다. - 좌표는 보드 범위(행 1~10, 열 1~9)를 벗어날 수 없다. - 좌표는 이동값(`Movement`)을 적용해 다음 위치를 계산할 수 있다. +- 좌표는 다른 위치에 대해서 선형(직선, 대각선)여부를 판단할 수 있다. +- 좌표는 특정 좌표 범위(한: 1~3행/4~6열, 초: 8~10행/4~6열)를 '궁성'으로 정의한다. ### 이동 전략 - 각 기물은 목적지까지의 경로를 계산한다. - 경로 검증은 `RoutePolicy`를 통해 수행한다. - 일반 기물은 경로가 비어 있고 도착 지점에 아군이 없을 때 이동할 수 있다. - 포는 이동 경로 중 정확히 하나의 기물을 넘어야 하며, 포를 넘을 수 없다. +- 궁성 내부에 위치한 기물(궁, 사, 차, 포, 졸)은 궁성 라인을 따른 대각선 이동이 가능하다. - 목적지 자체가 이동 규칙에 맞지 않으면 예외를 발생시킨다. ### 게임 진행 - 게임은 초 진영부터 시작한다. - 플레이어는 턴마다 시작 좌표와 도착 좌표를 입력한다. +- 사용자가 시작 좌표 입력 시 '종료'를 입력하면 게임 중단 여부를 재확인한다. + - 상대편이 동의(종료 입력)하면 게임을 즉시 종료한다. + - 동의하지 않으면(종료가 아닌 다른 값을 입력) 현재 턴을 유지하며 다시 입력을 받는다. - 현재 턴의 진영만 자신의 기물을 이동할 수 있다. - 이동이 완료되면 턴이 상대 진영으로 넘어간다. - 게임 상태는 `ChoTurn`, `HanTurn`, `Finish`로 구분된다. @@ -46,7 +54,8 @@ ### 입력 - 한 진영의 마상 배치를 입력받는다. - 초 진영의 마상 배치를 입력받는다. -- 이동할 기물의 시작 좌표를 입력받는다. (예: `3,4`) +- 이동할 기물의 시작 좌표 또는 종료를 입력받는다. (예: `3,4` 또는 `종료` ) + - 종료 재확인 시 종료 동의 여부를 입력받는다. (예: `종료`) - 이동할 기물의 도착 좌표를 입력받는다. (예: `4,4`) - 잘못된 입력이 들어오면 예외 메시지를 출력하고 다시 입력받는다. @@ -54,6 +63,8 @@ - 현재 장기판 상태를 행/열 좌표와 함께 출력한다. - 초와 한 기물은 ANSI 색상으로 구분해 출력한다. - 현재 턴의 진영을 출력한다. +- 매 턴 이동이 성공하면 현재 양 진영의 점수 합계를 출력한다. +- 게임 종료 시 최종 점수와 함께 승리한 진영을 출력한다. - 예외가 발생하면 `[ERROR]` 형식의 메시지를 출력한다. ### 예외 처리 @@ -65,3 +76,4 @@ - 현재 턴의 진영이 아닌 기물을 움직이려는 경우 - 기물의 이동 규칙에 맞지 않는 목적지를 입력한 경우 - 이동 경로가 막혀 있거나 도착 지점에 아군 기물이 있는 경우 +- 점수 차감 시 유효하지 않은 점수값(음수 등)이 계산되는 경우 From b99a5f294996cfee7e5940415a0045db400f3992 Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 22:52:48 +0900 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20=EC=A0=90=EC=88=98=20=EB=86=92?= =?UTF-8?q?=EC=9D=80=20=EC=A7=84=EC=98=81=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=8A=B9=EB=A6=AC=20=EC=B6=9C=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 15 ++++++++++++++- src/main/java/janggi/domain/SideScore.java | 2 +- .../java/janggi/domain/board/MaterialScore.java | 8 +++++--- src/main/java/janggi/view/OutputView.java | 3 +-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 3c573dd935..7fb7886fb6 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -3,6 +3,8 @@ import janggi.domain.Arrangement; import janggi.domain.Game; import janggi.domain.Position; +import janggi.domain.Side; +import janggi.domain.SideScore; import janggi.view.InputView; import janggi.view.OutputView; import java.util.List; @@ -69,6 +71,7 @@ private boolean executeTurn(Game game) { List endPositionInput = InputView.askEndPosition(); Position endPosition = Position.from(endPositionInput); + OutputView.printLine(); game.move(startPosition, endPosition); @@ -80,15 +83,25 @@ private boolean consentEndGame(Game game) { String consentInput = InputView.consentEnd(); if(consentInput.equals(END_TEXT)) { + printCurrentScore(game); + printScoreWinner(game); return false; } return executeTurn(game); } + private void printScoreWinner(Game game) { + SideScore score = game.getCurrentSideScore(); + if(score.cho() > score.han()) { + OutputView.printWinner(Side.CHO); + return; + } + OutputView.printWinner(Side.HAN); + } + private void printCurrentScore(Game game) { - OutputView.printLine(); OutputView.printScore(game.getCurrentSideScore()); } diff --git a/src/main/java/janggi/domain/SideScore.java b/src/main/java/janggi/domain/SideScore.java index de9f96de68..69caa1caac 100644 --- a/src/main/java/janggi/domain/SideScore.java +++ b/src/main/java/janggi/domain/SideScore.java @@ -1,4 +1,4 @@ package janggi.domain; -public record SideScore(int han, int cho) { +public record SideScore(double han, double cho) { } diff --git a/src/main/java/janggi/domain/board/MaterialScore.java b/src/main/java/janggi/domain/board/MaterialScore.java index 9ecd51ab16..2ccd06f7d0 100644 --- a/src/main/java/janggi/domain/board/MaterialScore.java +++ b/src/main/java/janggi/domain/board/MaterialScore.java @@ -9,12 +9,14 @@ public class MaterialScore { private static final String INVALID_NEGATIVE_DECREASE_SCORE = "차감할 점수는 0보다 커야 합니다."; private static final String INVALID_SCORE_SIDE = "해당 진영의 점수 정보가 존재하지 않습니다"; + private static final double DUM = 1.5; + private final Map isGungAlive; - private final Map score; + private final Map score; public MaterialScore(int hanScore, int choScore) { this.isGungAlive = new HashMap<>(Map.of(Side.HAN, true, Side.CHO, true)); - this.score = new HashMap<>(Map.of(Side.HAN, hanScore, Side.CHO, choScore)); + this.score = new HashMap<>(Map.of(Side.HAN, hanScore + DUM, Side.CHO, (double) choScore)); } public SideScore getCurrentScore() { @@ -34,7 +36,7 @@ public void decreaseScore(Side side, int pieceScore) { throw new IllegalArgumentException(INVALID_NEGATIVE_DECREASE_SCORE); } - Integer currentScore = score.get(side); + Double currentScore = score.get(side); if(currentScore == null) { throw new IllegalStateException(INVALID_SCORE_SIDE); } diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index aef3243322..ef0e1e8f2a 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -16,7 +16,6 @@ public class OutputView { private static final String TURN_PREFIX = "현재 턴: "; - private static final String SCORE_PREFIX = "[ 현재 점수 현황 ]"; private static final String HAN_SCORE_PREFIX = "한: "; private static final String CHO_SCORE_PREFIX = "초: "; private static final String SCORE_SUFFIX = "점"; @@ -41,11 +40,11 @@ public static void printTurn(Side side) { } public static void printScore(SideScore score) { - System.out.println(SCORE_PREFIX); System.out.println(CHO_SCORE_PREFIX + score.cho() + SCORE_SUFFIX + SCORE_DELIMITER + HAN_SCORE_PREFIX + score.han() + SCORE_SUFFIX); } public static void printWinner(Side winnerSide) { + printLine(); System.out.printf("%s 승리!%n", winnerSide.getName()); } From aa8337e9cf2d3cf95594b0b9db98e524748d819c Mon Sep 17 00:00:00 2001 From: armd482 Date: Wed, 1 Apr 2026 23:16:00 +0900 Subject: [PATCH 14/29] =?UTF-8?q?test:=20=EC=A7=81=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B6=81=EC=84=B1=20=EB=8C=80=EA=B0=81=EC=84=A0=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EB=B0=A9=ED=96=A5,=20=EA=B8=B8=EC=9D=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/janggi/domain/PositionTest.java | 103 +++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index d6204bc5e3..c5be66aade 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -1,16 +1,17 @@ package janggi.domain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; class PositionTest { - - @ParameterizedTest @ValueSource(strings = {"0,7", "100,2", "-2,7"}) void 입력한_행이_유효_범위를_벗어나면_예외가_발생한다(String input) { @@ -37,4 +38,102 @@ class PositionTest { assertThatThrownBy(() -> Position.from(inputValue1)).isInstanceOf(IllegalArgumentException.class).hasMessage("행과 열 두 개의 값만 입력하세요."); assertThatThrownBy(() -> Position.from(inputValue2)).isInstanceOf(IllegalArgumentException.class).hasMessage("행과 열 두 개의 값만 입력하세요."); } + + @ParameterizedTest + @CsvSource({ + "5, 5, 1, 5, -1, 0", + "5, 5, 9, 5, 1, 0", + "5, 5, 5, 1, 0, -1", + "5, 5, 5, 9, 0, 1" + }) + void 상하좌우_직선에_대한_방향을_제대로_가져온다(int x1, int y1, int x2, int y2, int dx, int dy) { + Position start = new Position(x1, y1); + Position end = new Position(x2, y2); + + Movement movement = start.getLinearDirection(end); + + assertThat(movement.getDx()).isEqualTo(dx); + assertThat(movement.getDy()).isEqualTo(dy); + } + + @ParameterizedTest + @CsvSource({ + "1, 4, 3, 6, 1, 1", + "3, 6, 1, 4, -1, -1", + "10, 4, 8, 6, -1, 1", + "8, 6, 10, 4, 1, -1" + }) + void 궁성_내부_대각선_직선에_대한_방향을_제대로_가져온다(int startX, int startY, int endX, int endY, int dx, int dy) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + Movement movement = start.getLinearDirection(end); + + assertThat(movement.getDx()).isEqualTo(dx); + assertThat(movement.getDy()).isEqualTo(dy); + } + + @Test + void 직선이_아닌_경우_방향을_가져오면_예외가_발생한다() { + Position start = new Position(1, 1); + Position end = new Position(2, 3); + + assertThatThrownBy(() -> start.getLinearDirection(end)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("직선이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "1,5,2,6", + "2,4,3,5", + "9,4,10,5", + "8,5,9,6" + }) + void 궁성_내부_이동_가능한_대각선이_아닌_경우_예외가_발생한다(int startX, int startY, int endX, int endY) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + assertThatThrownBy(() -> start.getLinearDirection(end)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("직선이 아닙니다."); + } + + @Test + void 직선이_아닌_경우_길이를_가져오면_예외가_발생한다() { + Position start = new Position(1, 1); + Position end = new Position(2, 3); + + assertThatThrownBy(() -> start.calculateLinearDistance(end)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("직선이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "1,4,1,2,2", + "5,8,2,8,3", + "3,2,3,7,5", + "1,5,2,5,1" + }) + void 상하좌우_직선에_대한_길이를_제대로_가져온다(int startX, int startY, int endX, int endY, int distance) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + assertThat(start.calculateLinearDistance(end)).isEqualTo(distance); + } + + @ParameterizedTest + @CsvSource({ + "8,4,10,6,2", + "10,4,9,5,1", + "3,6,2,5,1", + "3,4,1,6,2" + }) + void 궁성_내부_대각선에_대한_길이를_제대로_가져온다(int startX, int startY, int endX, int endY, int distance) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + assertThat(start.calculateLinearDistance(end)).isEqualTo(distance); + } } From b0b67d90b5390a72832adcee5203bf1fc7c5d7ed Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 11:17:16 +0900 Subject: [PATCH 15/29] =?UTF-8?q?feat:=20max=20turn=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 21 +++++++++---------- src/main/java/janggi/domain/Game.java | 8 ++----- src/main/java/janggi/domain/Position.java | 2 +- .../java/janggi/domain/piece/ActivePiece.java | 1 + src/main/java/janggi/domain/piece/Gung.java | 10 +++++++++ .../java/janggi/domain/turn/BaseTurn.java | 13 +++++------- src/main/java/janggi/domain/turn/ChoTurn.java | 21 ++++++++++++++----- .../java/janggi/domain/turn/FinishTurn.java | 9 ++++++-- src/main/java/janggi/domain/turn/HanTurn.java | 20 +++++++++++++----- .../java/janggi/domain/board/BoardTest.java | 2 +- .../domain/board/MaterialScoreTest.java | 2 +- 11 files changed, 69 insertions(+), 40 deletions(-) diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 7fb7886fb6..950c6444cd 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -38,6 +38,15 @@ private Game initArrangeGame() { private void playGame(Game game) { while (playTurnGame(game)) { } + + Side winnerSide = game.getWinnerSide(); + + if(winnerSide == null) { + printCurrentScore(game); + printScoreWinner(game); + return; + } + OutputView.printWinner(winnerSide); } private boolean playTurnGame(Game game) { @@ -76,15 +85,13 @@ private boolean executeTurn(Game game) { game.move(startPosition, endPosition); printCurrentScore(game); - return isFinishedGame(game); + return game.isFinished(); } private boolean consentEndGame(Game game) { String consentInput = InputView.consentEnd(); if(consentInput.equals(END_TEXT)) { - printCurrentScore(game); - printScoreWinner(game); return false; } @@ -104,12 +111,4 @@ private void printScoreWinner(Game game) { private void printCurrentScore(Game game) { OutputView.printScore(game.getCurrentSideScore()); } - - private boolean isFinishedGame(Game game) { - if (game.isFinished()) { - OutputView.printWinner(game.getWinnerSide()); - return false; - } - return true; - } } diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index cc31a98991..6caae60b08 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -24,7 +24,7 @@ public Game(Arrangement choArrangement, Arrangement hanArrangement) { .mapToInt(Piece::getPieceScore) .sum(); - this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore)); + this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 1); } public void move(Position start, Position end) { @@ -48,10 +48,6 @@ public SideScore getCurrentSideScore() { } public Side getWinnerSide() { - Side winnerSide = playerTurn.getWinnerSide(); - if (winnerSide.equals(Side.EMPTY)) { - throw new IllegalStateException(INVALID_WINNER_SIDE); - } - return winnerSide; + return playerTurn.getWinnerSide(); } } diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index 105fc6ad48..dd74bc7dce 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -128,7 +128,7 @@ private boolean isGungSungDiagonal(Position position) { return isGungSung(this) && isGungSung(position) && (!GungSungM.contains(this) && !GungSungM.contains(position)); } - private boolean isGungSung(Position position) { + public static boolean isGungSung(Position position) { if(position.y < GUNG_SUNG_COL_START || position.y > GUNG_SUNG_COL_END) { return false; } diff --git a/src/main/java/janggi/domain/piece/ActivePiece.java b/src/main/java/janggi/domain/piece/ActivePiece.java index 59fabb3499..19b35624ee 100644 --- a/src/main/java/janggi/domain/piece/ActivePiece.java +++ b/src/main/java/janggi/domain/piece/ActivePiece.java @@ -10,6 +10,7 @@ public abstract class ActivePiece extends BasePiece { protected static final String UNMOVABLE_ROUTE_MESSAGE = "이동할 수 없는 경로입니다."; protected final RoutePolicy routePolicy; + // public ActivePiece(RoutePolicy routePolicy, Side side, PieceType pieceType) { super(side, pieceType); diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 9cd23314fb..355289d57f 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -1,9 +1,19 @@ package janggi.domain.piece; +import janggi.domain.Position; +import janggi.domain.Route; import janggi.domain.Side; public class Gung extends SingleLinearPiece { public Gung(Side side) { super(side, PieceType.GUNG); } + + @Override + public Route findRoute(Position start, Position end) { + if(!Position.isGungSung(end)) { + throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); + } + return super.findRoute(start, end); + } } diff --git a/src/main/java/janggi/domain/turn/BaseTurn.java b/src/main/java/janggi/domain/turn/BaseTurn.java index c561026856..75fc72dab5 100644 --- a/src/main/java/janggi/domain/turn/BaseTurn.java +++ b/src/main/java/janggi/domain/turn/BaseTurn.java @@ -7,12 +7,14 @@ import java.util.List; public abstract class BaseTurn implements PlayerTurn { + protected static final int MAX_TURN = 200; + protected final Board board; - protected final Side side; + protected final int turn; - public BaseTurn(Board board, Side side) { + public BaseTurn(Board board, int turn) { this.board = board; - this.side = side; + this.turn = turn; } @Override @@ -25,11 +27,6 @@ public List> getCurrentBoard() { return board.getCurrentBoard(); } - @Override - public Side getCurrentSide() { - return side; - } - @Override public Side getWinnerSide() { return Side.EMPTY; diff --git a/src/main/java/janggi/domain/turn/ChoTurn.java b/src/main/java/janggi/domain/turn/ChoTurn.java index f856a62312..ff009603a8 100644 --- a/src/main/java/janggi/domain/turn/ChoTurn.java +++ b/src/main/java/janggi/domain/turn/ChoTurn.java @@ -5,16 +5,27 @@ import janggi.domain.board.Board; public class ChoTurn extends BaseTurn { - public ChoTurn(Board board) { - super(board, Side.CHO); + public ChoTurn(Board board, int turn) { + super(board, turn); } @Override public PlayerTurn move(Position start, Position end) { - board.move(start, end, side); + board.move(start, end, Side.CHO); + + if(turn == MAX_TURN) { + return new FinishTurn(board, Side.EMPTY, turn); + } + if (board.isEndGame()) { - return new FinishTurn(board, side); + return new FinishTurn(board, Side.CHO, turn); } - return new HanTurn(board); + + return new HanTurn(board, turn + 1); + } + + @Override + public Side getCurrentSide() { + return Side.CHO; } } diff --git a/src/main/java/janggi/domain/turn/FinishTurn.java b/src/main/java/janggi/domain/turn/FinishTurn.java index d3021c5303..6f24947367 100644 --- a/src/main/java/janggi/domain/turn/FinishTurn.java +++ b/src/main/java/janggi/domain/turn/FinishTurn.java @@ -9,8 +9,8 @@ public class FinishTurn extends BaseTurn { private final Side winnerSide; - public FinishTurn(Board board, Side winnerSide) { - super(board, Side.EMPTY); + public FinishTurn(Board board, Side winnerSide, int turn) { + super(board, turn); this.winnerSide = winnerSide; } @@ -28,4 +28,9 @@ public boolean isFinished() { public Side getWinnerSide() { return winnerSide; } + + @Override + public Side getCurrentSide() { + return Side.EMPTY; + } } diff --git a/src/main/java/janggi/domain/turn/HanTurn.java b/src/main/java/janggi/domain/turn/HanTurn.java index 1023c6608c..b45883f0b5 100644 --- a/src/main/java/janggi/domain/turn/HanTurn.java +++ b/src/main/java/janggi/domain/turn/HanTurn.java @@ -5,16 +5,26 @@ import janggi.domain.board.Board; public class HanTurn extends BaseTurn { - public HanTurn(Board board) { - super(board, Side.HAN); + public HanTurn(Board board, int turn) { + super(board, turn); } @Override public PlayerTurn move(Position start, Position end) { - board.move(start, end, side); + board.move(start, end, Side.HAN); + + if(turn == MAX_TURN) { + return new FinishTurn(board, Side.EMPTY, turn); + } + if (board.isEndGame()) { - return new FinishTurn(board, side); + return new FinishTurn(board, Side.HAN, turn); } - return new ChoTurn(board); + return new ChoTurn(board, turn + 1); + } + + @Override + public Side getCurrentSide() { + return Side.HAN; } } diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 3d379df0be..95cf92b174 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -65,7 +65,7 @@ class BoardTest { Board board = new Board(customBoard, PieceType.PAWN.getScore(), PieceType.PAWN.getScore()); board.move(new Position(1, 2), new Position(1, 1), Side.CHO); - assertThat(board.getScore()).isEqualTo(new SideScore(0, PieceType.PAWN.getScore())); + assertThat(board.getScore()).isEqualTo(new SideScore(1.5, PieceType.PAWN.getScore())); } @Test diff --git a/src/test/java/janggi/domain/board/MaterialScoreTest.java b/src/test/java/janggi/domain/board/MaterialScoreTest.java index 3c82bbc674..68a832243c 100644 --- a/src/test/java/janggi/domain/board/MaterialScoreTest.java +++ b/src/test/java/janggi/domain/board/MaterialScoreTest.java @@ -14,7 +14,7 @@ public class MaterialScoreTest { SideScore currentScore = materialScore.getCurrentScore(); assertThat(currentScore.cho()).isEqualTo(72); - assertThat(currentScore.han()).isEqualTo(54); + assertThat(currentScore.han()).isEqualTo(55.5); } @Test From 54d5e64104d8ee1c47a2cfa22e43f4ed6c15bc5c Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 13:08:59 +0900 Subject: [PATCH 16/29] =?UTF-8?q?refactor:=20=EC=A7=81=EC=84=A0=20?= =?UTF-8?q?=ED=8C=90=EB=B3=84=20Linear=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 60 ++-------- src/main/java/janggi/domain/piece/Gung.java | 3 +- .../java/janggi/domain/piece/LinearPiece.java | 59 +++++++++- src/main/java/janggi/domain/piece/Pawn.java | 6 +- src/main/java/janggi/domain/piece/Sa.java | 11 ++ .../domain/piece/SingleLinearPiece.java | 5 +- src/test/java/janggi/domain/PositionTest.java | 98 ---------------- .../java/janggi/domain/piece/ChaTest.java | 109 ++++++++++++++++++ .../java/janggi/domain/piece/GungTest.java | 58 ++++++++-- .../java/janggi/domain/piece/PawnTest.java | 60 +++++++++- src/test/java/janggi/domain/piece/PoTest.java | 88 ++++++++++++++ .../java/janggi/domain/piece/RouteTest.java | 22 ++-- src/test/java/janggi/domain/piece/SaTest.java | 60 ++++++++-- 13 files changed, 434 insertions(+), 205 deletions(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index dd74bc7dce..f053a17367 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -12,24 +12,10 @@ public class Position { public static final int POSITION_COMPONENTS_SIZE = 2; - private static final Integer GUNG_SUNG_COL_START = 4; - private static final Integer GUNG_SUNG_COL_END = 6; - private static final Integer HAN_GUNG_SUNG_ROW_START = 1; - private static final Integer HAN_GUNG_SUNG_ROW_END = 3; - private static final Integer CHO_GUNG_SUNG_ROW_START = 8; - private static final Integer CHO_GUNG_SUNG_ROW_END = 10; - - private static final Set GungSungM = Set.of( - new Position(1, 5), new Position(2, 4), new Position(2, 6), new Position(3,5), - new Position(8, 5), new Position(9, 4), new Position(9, 6), new Position(10,5) - ); - private static final String INVALID_POSITION_SIZE = "행과 열 두 개의 값만 입력하세요."; private static final String INVALID_ROW_RANGE = "유효하지 않은 위치입니다. 행은 1부터 10까지 가능합니다."; private static final String INVALID_COL_RANGE = "유효하지 않은 위치입니다. 열은 1부터 9까지 가능합니다."; - private static final String INVALID_LINEAR_POSITION = "직선이 아닙니다."; - private final int x; private final int y; @@ -91,49 +77,23 @@ public Position move(Movement movement) { return new Position(x + movement.getDx(), y + movement.getDy()); } - public Movement getLinearDirection(Position position) { - if(!isLinear(position)) { - throw new IllegalStateException(INVALID_LINEAR_POSITION); - } - - int dx = Integer.compare(position.x, x); - int dy = Integer.compare(position.y, y); - - return Movement.of(dx, dy); + public int getDeltaX(Position position) { + return Math.abs(position.x - x); } - public int calculateLinearDistance(Position position) { - if(!isLinear(position)) { - throw new IllegalStateException(INVALID_LINEAR_POSITION); - } - - int dx = Math.abs(position.x - x); - int dy = Math.abs(position.y - y); - - return Math.max(dx, dy); + public int getDeltaY(Position position) { + return Math.abs(position.y - y); } - private boolean isLinear(Position position) { - if(position.x == x && position.y == y) { - return false; - } - - int dx = Math.abs(position.x - x); - int dy = Math.abs(position.y - y); - - return dx == 0 || dy == 0 || isGungSungDiagonal(position); + public int compareX(Position position) { + return Integer.compare(position.x, x); } - private boolean isGungSungDiagonal(Position position) { - return isGungSung(this) && isGungSung(position) && (!GungSungM.contains(this) && !GungSungM.contains(position)); + public int compareY(Position position) { + return Integer.compare(position.y, y); } - public static boolean isGungSung(Position position) { - if(position.y < GUNG_SUNG_COL_START || position.y > GUNG_SUNG_COL_END) { - return false; - } - - return (position.x >= HAN_GUNG_SUNG_ROW_START && position.x <= HAN_GUNG_SUNG_ROW_END) - || (position.x >= CHO_GUNG_SUNG_ROW_START && position.x <= CHO_GUNG_SUNG_ROW_END); + public boolean isRange(int startX, int endX, int startY, int endY) { + return x >= startX && x <= endX && y >= startY && y <= endY; } } diff --git a/src/main/java/janggi/domain/piece/Gung.java b/src/main/java/janggi/domain/piece/Gung.java index 355289d57f..e6882ca307 100644 --- a/src/main/java/janggi/domain/piece/Gung.java +++ b/src/main/java/janggi/domain/piece/Gung.java @@ -11,9 +11,10 @@ public Gung(Side side) { @Override public Route findRoute(Position start, Position end) { - if(!Position.isGungSung(end)) { + if(!isGungSung(end)) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } + return super.findRoute(start, end); } } diff --git a/src/main/java/janggi/domain/piece/LinearPiece.java b/src/main/java/janggi/domain/piece/LinearPiece.java index aa1db57f3d..c4c295e5d7 100644 --- a/src/main/java/janggi/domain/piece/LinearPiece.java +++ b/src/main/java/janggi/domain/piece/LinearPiece.java @@ -6,24 +6,57 @@ import janggi.domain.Side; import janggi.domain.board.BaseBoard; import janggi.domain.policy.RoutePolicy; +import java.util.List; +import java.util.Set; import java.util.stream.Stream; public abstract class LinearPiece extends ActivePiece { + private static final Integer GUNG_SUNG_COL_START = 4; + private static final Integer GUNG_SUNG_COL_END = 6; + private static final Integer HAN_GUNG_SUNG_ROW_START = 1; + private static final Integer HAN_GUNG_SUNG_ROW_END = 3; + private static final Integer CHO_GUNG_SUNG_ROW_START = 8; + private static final Integer CHO_GUNG_SUNG_ROW_END = 10; + + private static final Set GungSungOrthogonalDirections = Set.of( + new Position(1, 5), new Position(2, 4), new Position(2, 6), new Position(3,5), + new Position(8, 5), new Position(9, 4), new Position(9, 6), new Position(10,5) + ); + public LinearPiece(RoutePolicy routePolicy, Side side, PieceType pieceType) { super(routePolicy, side, pieceType); } @Override public Route findRoute(Position start, Position end) { - try { - Movement direction = start.getLinearDirection(end); - int distance = start.calculateLinearDistance(end); - - return calculatePath(start, direction, distance); - } catch (IllegalStateException e) { + if(!isLinear(start, end)) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } + Movement direction = getLinearDirection(start, end); + int distance = calculateLinearDistance(start, end); + + return calculatePath(start, direction, distance); + } + + protected boolean isLinear(Position start, Position end) { + if(start.compareX(end) == 0 && start.compareY(end) == 0) { + return false; + } + + int dx = start.getDeltaX(end); + int dy = start.getDeltaY(end); + + return dx == 0 || dy == 0 || isGungSungDiagonal(start, end); + } + + private boolean isGungSungDiagonal(Position start, Position end ) { + return isGungSung(start) && isGungSung(end) && (!GungSungOrthogonalDirections.contains(start) && !GungSungOrthogonalDirections.contains(end)); + } + + protected static boolean isGungSung(Position position) { + return position.isRange(HAN_GUNG_SUNG_ROW_START, HAN_GUNG_SUNG_ROW_END, GUNG_SUNG_COL_START, GUNG_SUNG_COL_END) + || position.isRange(CHO_GUNG_SUNG_ROW_START, CHO_GUNG_SUNG_ROW_END, GUNG_SUNG_COL_START, GUNG_SUNG_COL_END); } protected Route calculatePath(Position start, Movement direction, int dist) { @@ -32,4 +65,18 @@ protected Route calculatePath(Position start, Movement direction, int dist) { .limit(Math.abs(dist) + 1) .toList()); } + + protected Movement getLinearDirection(Position start, Position end) { + int dx = start.compareX(end); + int dy = start.compareY(end); + + return Movement.of(dx, dy); + } + + protected int calculateLinearDistance(Position start, Position end) { + int dx = start.getDeltaX(end); + int dy = start.getDeltaY(end); + + return Math.max(dx, dy); + } } diff --git a/src/main/java/janggi/domain/piece/Pawn.java b/src/main/java/janggi/domain/piece/Pawn.java index 741bf7284e..b83b37361d 100644 --- a/src/main/java/janggi/domain/piece/Pawn.java +++ b/src/main/java/janggi/domain/piece/Pawn.java @@ -21,12 +21,10 @@ public Pawn(Side side) { @Override public Route findRoute(Position start, Position end) { try { - Movement direction = start.getLinearDirection(end); + Movement direction = getLinearDirection(start, end); validateDirection(direction); - int distance = start.calculateLinearDistance(end); - - return calculatePath(start, direction, distance); + return super.findRoute(start, end); } catch (IllegalStateException e) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } diff --git a/src/main/java/janggi/domain/piece/Sa.java b/src/main/java/janggi/domain/piece/Sa.java index 293060c37e..c45eb95b3c 100644 --- a/src/main/java/janggi/domain/piece/Sa.java +++ b/src/main/java/janggi/domain/piece/Sa.java @@ -1,9 +1,20 @@ package janggi.domain.piece; +import janggi.domain.Position; +import janggi.domain.Route; import janggi.domain.Side; public class Sa extends SingleLinearPiece { public Sa(Side side) { super( side, PieceType.SA); } + + @Override + public Route findRoute(Position start, Position end) { + if(!isGungSung(end)) { + throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); + } + + return super.findRoute(start, end); + } } diff --git a/src/main/java/janggi/domain/piece/SingleLinearPiece.java b/src/main/java/janggi/domain/piece/SingleLinearPiece.java index 354fdb0907..56a80b823c 100644 --- a/src/main/java/janggi/domain/piece/SingleLinearPiece.java +++ b/src/main/java/janggi/domain/piece/SingleLinearPiece.java @@ -16,11 +16,10 @@ public SingleLinearPiece(Side side, PieceType pieceType) { @Override public Route findRoute(Position start, Position end) { try { - Movement direction = start.getLinearDirection(end); - int distance = start.calculateLinearDistance(end); + int distance = calculateLinearDistance(start, end); validateDistance(distance); - return calculatePath(start, direction, distance); + return super.findRoute(start, end); } catch (IllegalStateException e) { throw new IllegalArgumentException(INVALID_DESTINATION_MESSAGE); } diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index c5be66aade..13ac1ce8fe 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -38,102 +38,4 @@ class PositionTest { assertThatThrownBy(() -> Position.from(inputValue1)).isInstanceOf(IllegalArgumentException.class).hasMessage("행과 열 두 개의 값만 입력하세요."); assertThatThrownBy(() -> Position.from(inputValue2)).isInstanceOf(IllegalArgumentException.class).hasMessage("행과 열 두 개의 값만 입력하세요."); } - - @ParameterizedTest - @CsvSource({ - "5, 5, 1, 5, -1, 0", - "5, 5, 9, 5, 1, 0", - "5, 5, 5, 1, 0, -1", - "5, 5, 5, 9, 0, 1" - }) - void 상하좌우_직선에_대한_방향을_제대로_가져온다(int x1, int y1, int x2, int y2, int dx, int dy) { - Position start = new Position(x1, y1); - Position end = new Position(x2, y2); - - Movement movement = start.getLinearDirection(end); - - assertThat(movement.getDx()).isEqualTo(dx); - assertThat(movement.getDy()).isEqualTo(dy); - } - - @ParameterizedTest - @CsvSource({ - "1, 4, 3, 6, 1, 1", - "3, 6, 1, 4, -1, -1", - "10, 4, 8, 6, -1, 1", - "8, 6, 10, 4, 1, -1" - }) - void 궁성_내부_대각선_직선에_대한_방향을_제대로_가져온다(int startX, int startY, int endX, int endY, int dx, int dy) { - Position start = new Position(startX, startY); - Position end = new Position(endX, endY); - - Movement movement = start.getLinearDirection(end); - - assertThat(movement.getDx()).isEqualTo(dx); - assertThat(movement.getDy()).isEqualTo(dy); - } - - @Test - void 직선이_아닌_경우_방향을_가져오면_예외가_발생한다() { - Position start = new Position(1, 1); - Position end = new Position(2, 3); - - assertThatThrownBy(() -> start.getLinearDirection(end)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("직선이 아닙니다."); - } - - @ParameterizedTest - @CsvSource({ - "1,5,2,6", - "2,4,3,5", - "9,4,10,5", - "8,5,9,6" - }) - void 궁성_내부_이동_가능한_대각선이_아닌_경우_예외가_발생한다(int startX, int startY, int endX, int endY) { - Position start = new Position(startX, startY); - Position end = new Position(endX, endY); - - assertThatThrownBy(() -> start.getLinearDirection(end)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("직선이 아닙니다."); - } - - @Test - void 직선이_아닌_경우_길이를_가져오면_예외가_발생한다() { - Position start = new Position(1, 1); - Position end = new Position(2, 3); - - assertThatThrownBy(() -> start.calculateLinearDistance(end)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("직선이 아닙니다."); - } - - @ParameterizedTest - @CsvSource({ - "1,4,1,2,2", - "5,8,2,8,3", - "3,2,3,7,5", - "1,5,2,5,1" - }) - void 상하좌우_직선에_대한_길이를_제대로_가져온다(int startX, int startY, int endX, int endY, int distance) { - Position start = new Position(startX, startY); - Position end = new Position(endX, endY); - - assertThat(start.calculateLinearDistance(end)).isEqualTo(distance); - } - - @ParameterizedTest - @CsvSource({ - "8,4,10,6,2", - "10,4,9,5,1", - "3,6,2,5,1", - "3,4,1,6,2" - }) - void 궁성_내부_대각선에_대한_길이를_제대로_가져온다(int startX, int startY, int endX, int endY, int distance) { - Position start = new Position(startX, startY); - Position end = new Position(endX, endY); - - assertThat(start.calculateLinearDistance(end)).isEqualTo(distance); - } } diff --git a/src/test/java/janggi/domain/piece/ChaTest.java b/src/test/java/janggi/domain/piece/ChaTest.java index 50f6542991..d9380f776e 100644 --- a/src/test/java/janggi/domain/piece/ChaTest.java +++ b/src/test/java/janggi/domain/piece/ChaTest.java @@ -8,6 +8,8 @@ import janggi.domain.Side; import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class ChaTest { @@ -81,4 +83,111 @@ class ChaTest { end ))); } + + @Test + void 궁성_위_오른쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(3, 4); + Position end = new Position(1, 6); + + Route routes = cha.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_위_왼쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(3, 6); + Position end = new Position(1, 4); + + Route routes = cha.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_아래_오른쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(3, 6); + + Route routes = cha.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_아래_왼쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(3, 4); + Position end = new Position(1, 6); + + Route routes = cha.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 대각선_이동이_궁성_밖을_포함할_때_에러가_발생한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(4, 7); + + assertThatThrownBy(() -> cha.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @Test + void 궁성_내의_대각선_이동과_궁성_밖_직선_이동이_함께_있을_때_에러가_발생한다() { + Cha cha = new Cha(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(4, 6); + + assertThatThrownBy(() -> cha.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "1, 5, 2, 4", + "1, 5, 2, 6", + "2, 4, 1, 5", + "2, 6, 3, 5", + "9, 4, 8, 5", + "9, 6, 10, 5" + }) + void 궁성_내_십자_위치에서의_대각선_이동이_있을_때_에러가_발생한다(int startX, int startY, int endX, int endY) { + Cha cha = new Cha(Side.CHO); + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + assertThatThrownBy(() -> cha.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } } diff --git a/src/test/java/janggi/domain/piece/GungTest.java b/src/test/java/janggi/domain/piece/GungTest.java index 7b329f859d..d1d90b696d 100644 --- a/src/test/java/janggi/domain/piece/GungTest.java +++ b/src/test/java/janggi/domain/piece/GungTest.java @@ -6,18 +6,19 @@ import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; +import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; public class GungTest { @ParameterizedTest @CsvSource({ - "2,3,2,4", - "2,3,2,2", - "2,3,1,3", - "2,3,3,3" + "2,5,1,5", + "2,5,2,4", + "2,5,2,6", + "2,5,3,5" }) - void 궁은_상하좌우로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { + void 궁은_궁성_안에서_상하좌우로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); Position endPosition = new Position(endX, endY); @@ -30,18 +31,53 @@ public class GungTest { @ParameterizedTest @CsvSource({ - "2,3,2,5", - "2,3,2,1", - "2,3,1,6", - "2,3,3,1" + "2,5,1,4", + "2,5,1,6", + "2,5,3,4", + "2,5,3,6" }) - void 궁은_상하좌우가_아닌_좌표로는_이동할_수_없다(int startX, int startY, int endX, int endY) { + void 궁은_궁성_안에서_십자_위치를_제외한_곳에서_대각선으로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); Position endPosition = new Position(endX, endY); Gung gung = new Gung(Side.CHO); - assertThatThrownBy(() -> gung.findRoute(startPosition, endPosition)) + Route actual = gung.findRoute(startPosition, endPosition); + + assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); + } + + @ParameterizedTest + @CsvSource({ + "1, 5, 2, 4", + "1, 5, 2, 6", + "2, 4, 1, 5", + "2, 6, 3, 5", + "9, 4, 8, 5", + "9, 6, 10, 5" + }) + void 궁은_궁성_내_십자_위치에서의_대각선_이동이_있을_때_에러가_발생한다(int startX, int startY, int endX, int endY) { + Gung gung = new Gung(Side.CHO); + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + Assertions.assertThatThrownBy(() -> gung.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "8,4,8,3", + "8,6,8,7", + "8,5,7,5" + }) + void 궁은_궁성_밖으로_이동이_있을_때_에러가_발생한다(int startX, int startY, int endX, int endY) { + Gung gung = new Gung(Side.CHO); + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + Assertions.assertThatThrownBy(() -> gung.findRoute(start, end)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("올바른 도착 지점이 아닙니다."); } diff --git a/src/test/java/janggi/domain/piece/PawnTest.java b/src/test/java/janggi/domain/piece/PawnTest.java index 5b4751b27c..f4f2914e82 100644 --- a/src/test/java/janggi/domain/piece/PawnTest.java +++ b/src/test/java/janggi/domain/piece/PawnTest.java @@ -14,7 +14,7 @@ class PawnTest { @CsvSource({ "2,3,3,3", "2,3,2,2", - "2,3,2,4" + "2,3,2,4", }) void 폰은_한_진영일때_하좌우로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); @@ -27,6 +27,24 @@ class PawnTest { assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); } + @ParameterizedTest + @CsvSource({ + "9,5,10,4", + "9,5,10,6" + }) + void 폰은_한_진영일때_궁성에서_아래_대각선으로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { + Position startPosition = new Position(startX, startY); + Position endPosition = new Position(endX, endY); + + Pawn pawn = new Pawn(Side.HAN); + + Route actual = pawn.findRoute(startPosition, endPosition); + + assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); + } + + + @ParameterizedTest @CsvSource({ "2,3,1,3", @@ -44,6 +62,22 @@ class PawnTest { assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); } + @ParameterizedTest + @CsvSource({ + "2,5,1,4", + "2,5,1,6", + }) + void 폰은_초_진영일때_궁성에서_위_대각선으로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { + Position startPosition = new Position(startX, startY); + Position endPosition = new Position(endX, endY); + + Pawn pawn = new Pawn(Side.CHO); + + Route actual = pawn.findRoute(startPosition, endPosition); + + assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); + } + @ParameterizedTest @CsvSource({ "2,3,1,3", @@ -63,12 +97,26 @@ class PawnTest { @ParameterizedTest @CsvSource({ - "2,3,3,3", - "2,3,1,6", - "2,3,3,1", - "2,3,2,3" + "9,5,8,4", + "9,5,8,6", + }) + void 폰은_한_진영일_때_궁성에서_위_대각선으로_이동할_수_없다(int startX, int startY, int endX, int endY) { + Position startPosition = new Position(startX, startY); + Position endPosition = new Position(endX, endY); + + Pawn pawn = new Pawn(Side.HAN); + + assertThatThrownBy(() -> pawn.findRoute(startPosition, endPosition)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "2,5,3,4", + "2,5,3,6", }) - void 폰은_초_진영일_때_상좌우가_아닌_좌표로는_이동할_수_없다(int startX, int startY, int endX, int endY) { + void 폰은_초_진영일_때_궁성에서_아래_대각선으로_이동할_수_없다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); Position endPosition = new Position(endX, endY); diff --git a/src/test/java/janggi/domain/piece/PoTest.java b/src/test/java/janggi/domain/piece/PoTest.java index a6c5952bd9..5ca6979597 100644 --- a/src/test/java/janggi/domain/piece/PoTest.java +++ b/src/test/java/janggi/domain/piece/PoTest.java @@ -80,4 +80,92 @@ class PoTest { end ))); } + + @Test + void 궁성_위_오른쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(3, 4); + Position end = new Position(1, 6); + + Route routes = po.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_위_왼쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(3, 6); + Position end = new Position(1, 4); + + Route routes = po.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_아래_오른쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(3, 6); + + Route routes = po.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 궁성_아래_왼쪽_대각선에_해당되는_시작과_끝좌표에_올바른_경로를_생성한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(3, 4); + Position end = new Position(1, 6); + + Route routes = po.findRoute(start, end); + + assertThat(routes).isEqualTo(new Route(List.of( + start, + new Position(2, 5), + end + ))); + } + + @Test + void 대각선_이동이_궁성_밖을_포함할_때_에러가_발생한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(4, 7); + + assertThatThrownBy(() -> po.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @Test + void 궁성_내의_대각선_이동과_궁성_밖_직선_이동이_함께_있을_때_에러가_발생한다() { + Po po = new Po(Side.CHO); + + Position start = new Position(1, 4); + Position end = new Position(4, 6); + + assertThatThrownBy(() -> po.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } } diff --git a/src/test/java/janggi/domain/piece/RouteTest.java b/src/test/java/janggi/domain/piece/RouteTest.java index b5aa6807ba..ecc5d0b2d6 100644 --- a/src/test/java/janggi/domain/piece/RouteTest.java +++ b/src/test/java/janggi/domain/piece/RouteTest.java @@ -8,14 +8,6 @@ import org.junit.jupiter.api.Test; public class RouteTest { - private boolean isLinear(Position point, Position position) { - try { - point.getLinearDirection(position); - return true; - } catch (Exception e) { - return false; - } - } @Test void 시작과_종료_사이_칸이_없는_경우_모든_조건은_항상_참으로_리턴한다() { Route route = new Route(List.of(new Position(1,1), new Position(1,2))); @@ -32,7 +24,7 @@ private boolean isLinear(Position point, Position position) { new Position(1, 5) )); - assertThat(route.isEveryBetween(position -> isLinear(new Position(1, 1), position))).isTrue(); + assertThat(route.isEveryBetween(position -> position.compareX(new Position(1, 6)) == 0)).isTrue(); } @Test @@ -45,7 +37,7 @@ private boolean isLinear(Position point, Position position) { new Position(1, 5) )); - assertThat(route.isEveryBetween(position -> isLinear(new Position(1, 1), position))).isFalse(); + assertThat(route.isEveryBetween(position -> position.compareX(new Position(1, 1)) == 0)).isFalse(); } @Test @@ -58,7 +50,7 @@ private boolean isLinear(Position point, Position position) { new Position(5, 5) )); - assertThat(route.isAnyBetween(position -> isLinear(new Position(1, 1), position))).isTrue(); + assertThat(route.isAnyBetween(position -> position.compareX(new Position(1, 1)) == 0)).isTrue(); } @Test @@ -71,7 +63,7 @@ private boolean isLinear(Position point, Position position) { new Position(5, 5) )); - assertThat(route.isAnyBetween(position -> isLinear(new Position(1, 1), position))).isFalse(); + assertThat(route.isAnyBetween(position -> position.compareX(new Position(1, 1)) == 0)).isFalse(); } @Test @@ -84,7 +76,7 @@ private boolean isLinear(Position point, Position position) { new Position(3, 5) )); - assertThat(route.countBetween(position -> isLinear(new Position(1, 1), position))).isEqualTo(2); + assertThat(route.countBetween(position -> position.compareX(new Position(1, 1)) == 0)).isEqualTo(2); } @Test @@ -111,7 +103,7 @@ private boolean isLinear(Position point, Position position) { new Position(5, 5) )); - assertThat(route.isDestinationSatisfied(position -> isLinear(new Position(2, 5), position))).isTrue(); - assertThat(route.isDestinationSatisfied(position -> isLinear(new Position(6, 7), position))).isFalse(); + assertThat(route.isDestinationSatisfied(position -> position.compareY(new Position(2, 5)) == 0)).isTrue(); + assertThat(route.isDestinationSatisfied(position -> position.compareX(new Position(6, 7)) == 0)).isFalse(); } } diff --git a/src/test/java/janggi/domain/piece/SaTest.java b/src/test/java/janggi/domain/piece/SaTest.java index 6145863eb7..5970d7b993 100644 --- a/src/test/java/janggi/domain/piece/SaTest.java +++ b/src/test/java/janggi/domain/piece/SaTest.java @@ -6,18 +6,19 @@ import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; +import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; class SaTest { @ParameterizedTest @CsvSource({ - "2,3,2,4", - "2,3,2,2", - "2,3,1,3", - "2,3,3,3" + "2,5,1,5", + "2,5,2,4", + "2,5,2,6", + "2,5,3,5" }) - void 사가_상하좌우로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { + void 사는_궁성_안에서_상하좌우로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); Position endPosition = new Position(endX, endY); @@ -30,18 +31,55 @@ class SaTest { @ParameterizedTest @CsvSource({ - "2,3,2,5", - "2,3,2,1", - "2,3,1,6", - "2,3,3,1" + "2,5,1,4", + "2,5,1,6", + "2,5,3,4", + "2,5,3,6" }) - void 사가_상하좌우가_아닌_좌표로는_이동할_수_없다(int startX, int startY, int endX, int endY) { + void 사는_궁성_안에서_십자_위치를_제외한_곳에서_대각선으로_한_칸_이동할_수_있다(int startX, int startY, int endX, int endY) { Position startPosition = new Position(startX, startY); Position endPosition = new Position(endX, endY); Sa sa = new Sa(Side.CHO); - assertThatThrownBy(() -> sa.findRoute(startPosition, endPosition)) + Route actual = sa.findRoute(startPosition, endPosition); + + assertThat(actual.isDestinationSatisfied(position -> position.equals(endPosition))).isTrue(); + } + + @ParameterizedTest + @CsvSource({ + "1, 5, 2, 4", + "1, 5, 2, 6", + "2, 4, 1, 5", + "2, 6, 3, 5", + "9, 4, 8, 5", + "9, 6, 10, 5" + }) + void 사는_궁성_내_십자_위치에서의_대각선_이동이_있을_때_에러가_발생한다(int startX, int startY, int endX, int endY) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + Sa sa = new Sa(Side.CHO); + + Assertions.assertThatThrownBy(() -> sa.findRoute(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("올바른 도착 지점이 아닙니다."); + } + + @ParameterizedTest + @CsvSource({ + "8,4,8,3", + "8,6,8,7", + "8,5,7,5" + }) + void 사는_궁성_밖으로_이동이_있을_때_에러가_발생한다(int startX, int startY, int endX, int endY) { + Position start = new Position(startX, startY); + Position end = new Position(endX, endY); + + Sa sa = new Sa(Side.CHO); + + Assertions.assertThatThrownBy(() -> sa.findRoute(start, end)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("올바른 도착 지점이 아닙니다."); } From e38c5ece3330364cf1c003ad33483bde69645493 Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 13:12:26 +0900 Subject: [PATCH 17/29] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20im?= =?UTF-8?q?port=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Position.java | 1 - src/main/java/janggi/domain/Route.java | 1 - src/main/java/janggi/domain/piece/LinearPiece.java | 2 -- src/main/java/janggi/domain/piece/SingleLinearPiece.java | 1 - src/main/java/janggi/domain/piece/StepPiece.java | 1 - src/test/java/janggi/domain/PositionTest.java | 3 --- src/test/java/janggi/domain/board/BoardTest.java | 3 --- src/test/java/janggi/domain/piece/GungTest.java | 1 - src/test/java/janggi/domain/piece/SaTest.java | 1 - 9 files changed, 14 deletions(-) diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index f053a17367..b1ee033875 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Objects; -import java.util.Set; public class Position { public static final int BOARD_START_ROWS = 1; diff --git a/src/main/java/janggi/domain/Route.java b/src/main/java/janggi/domain/Route.java index b114ceeb15..196d21b3a7 100644 --- a/src/main/java/janggi/domain/Route.java +++ b/src/main/java/janggi/domain/Route.java @@ -1,7 +1,6 @@ package janggi.domain; import java.util.List; -import java.util.Objects; import java.util.function.Predicate; public record Route(List route) { diff --git a/src/main/java/janggi/domain/piece/LinearPiece.java b/src/main/java/janggi/domain/piece/LinearPiece.java index c4c295e5d7..6266b4e3b4 100644 --- a/src/main/java/janggi/domain/piece/LinearPiece.java +++ b/src/main/java/janggi/domain/piece/LinearPiece.java @@ -4,9 +4,7 @@ import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; -import janggi.domain.board.BaseBoard; import janggi.domain.policy.RoutePolicy; -import java.util.List; import java.util.Set; import java.util.stream.Stream; diff --git a/src/main/java/janggi/domain/piece/SingleLinearPiece.java b/src/main/java/janggi/domain/piece/SingleLinearPiece.java index 56a80b823c..8c4336293c 100644 --- a/src/main/java/janggi/domain/piece/SingleLinearPiece.java +++ b/src/main/java/janggi/domain/piece/SingleLinearPiece.java @@ -1,6 +1,5 @@ package janggi.domain.piece; -import janggi.domain.Movement; import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; diff --git a/src/main/java/janggi/domain/piece/StepPiece.java b/src/main/java/janggi/domain/piece/StepPiece.java index c391d9d841..cd539bcd96 100644 --- a/src/main/java/janggi/domain/piece/StepPiece.java +++ b/src/main/java/janggi/domain/piece/StepPiece.java @@ -4,7 +4,6 @@ import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; -import janggi.domain.board.BaseBoard; import janggi.domain.policy.RoutePolicy; import java.util.List; import java.util.Optional; diff --git a/src/test/java/janggi/domain/PositionTest.java b/src/test/java/janggi/domain/PositionTest.java index 13ac1ce8fe..e241305efa 100644 --- a/src/test/java/janggi/domain/PositionTest.java +++ b/src/test/java/janggi/domain/PositionTest.java @@ -1,14 +1,11 @@ package janggi.domain; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; class PositionTest { diff --git a/src/test/java/janggi/domain/board/BoardTest.java b/src/test/java/janggi/domain/board/BoardTest.java index 95cf92b174..eb92f4ef10 100644 --- a/src/test/java/janggi/domain/board/BoardTest.java +++ b/src/test/java/janggi/domain/board/BoardTest.java @@ -11,13 +11,10 @@ import janggi.domain.piece.Pawn; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceType; -import janggi.domain.piece.Po; import janggi.initializer.BoardInitializer; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; class BoardTest { private Board board = new Board(BoardInitializer.createBoard(Arrangement.MA_SANG_MA_SANG, Arrangement.MA_SANG_MA_SANG), 72, 72); diff --git a/src/test/java/janggi/domain/piece/GungTest.java b/src/test/java/janggi/domain/piece/GungTest.java index d1d90b696d..22f2565f4d 100644 --- a/src/test/java/janggi/domain/piece/GungTest.java +++ b/src/test/java/janggi/domain/piece/GungTest.java @@ -1,7 +1,6 @@ package janggi.domain.piece; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import janggi.domain.Position; import janggi.domain.Route; diff --git a/src/test/java/janggi/domain/piece/SaTest.java b/src/test/java/janggi/domain/piece/SaTest.java index 5970d7b993..331e831060 100644 --- a/src/test/java/janggi/domain/piece/SaTest.java +++ b/src/test/java/janggi/domain/piece/SaTest.java @@ -1,7 +1,6 @@ package janggi.domain.piece; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import janggi.domain.Position; import janggi.domain.Route; From fc60b03f5f58a340e52bcc3747bfe0117addae27 Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 15:54:59 +0900 Subject: [PATCH 18/29] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=B0=A9=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20dao=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + src/main/java/janggi/dao/GameRoom.java | 90 +++++++++++++++++++ src/main/java/janggi/db/SQLManager.java | 60 +++++++++++++ src/main/java/janggi/domain/GameInfo.java | 4 + .../java/janggi/domain/piece/ActivePiece.java | 1 - 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/main/java/janggi/dao/GameRoom.java create mode 100644 src/main/java/janggi/db/SQLManager.java create mode 100644 src/main/java/janggi/domain/GameInfo.java diff --git a/build.gradle b/build.gradle index ce846f70cc..56d87204a1 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,9 @@ repositories { } dependencies { + implementation 'org.xerial:sqlite-jdbc:3.46.0.0' + implementation 'org.slf4j:slf4j-simple:2.0.9' + testImplementation platform('org.junit:junit-bom:5.11.4') testImplementation platform('org.assertj:assertj-bom:3.27.3') testImplementation('org.junit.jupiter:junit-jupiter') diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java new file mode 100644 index 0000000000..a2ea3e5458 --- /dev/null +++ b/src/main/java/janggi/dao/GameRoom.java @@ -0,0 +1,90 @@ +package janggi.dao; + +import janggi.domain.GameInfo; +import janggi.db.SQLManager; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + + +public class GameRoom { + private final SQLManager sqlManager = new SQLManager("jdbc:sqlite:src/main/resources/game_room.db"); + + public void initTable() { + String sql = "CREATE TABLE IF NOT EXISTS GameRoom (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + + "name TEXT NOT NULL, " + + "created_date TEXT NOT NULL, " + + "recently_date TEXT NOT NULL)"; + + try (Connection conn = sqlManager.ensureConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(sql); + if (!conn.getAutoCommit()) conn.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public List findAllGames() { + List gameInfos = new ArrayList<>(); + String sql = "SELECT * FROM GameRoom"; + + try (Connection conn = sqlManager.ensureConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + + while (rs.next()) { + GameInfo game = new GameInfo( + rs.getString("id"), + rs.getString("name"), + rs.getString("created_date"), + rs.getString("recently_date") + ); + gameInfos.add(game); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return gameInfos; + } + + public void insertGame(String name, String createdDate, String recentlyDate) { + String sql = "INSERT INTO GameRoom (name, created_date, recently_date) VALUES (?, ?, ?)"; + + try (Connection conn = sqlManager.ensureConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setString(1, name); + pstmt.setString(2, createdDate); + pstmt.setString(3, recentlyDate); + + pstmt.executeUpdate(); + + if (!conn.getAutoCommit()) conn.commit(); + + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void removeGame(int id) { + String sql = "DELETE FROM Game WHERE id = ?"; + + try (Connection conn = sqlManager.ensureConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setInt(1, id); + int affectedRows = pstmt.executeUpdate(); + + if (affectedRows > 0) { + if (!conn.getAutoCommit()) conn.commit(); + } else { + throw new IllegalStateException("해당 id의 게임이 없습니다."); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/janggi/db/SQLManager.java b/src/main/java/janggi/db/SQLManager.java new file mode 100644 index 0000000000..c295aa7257 --- /dev/null +++ b/src/main/java/janggi/db/SQLManager.java @@ -0,0 +1,60 @@ +package janggi.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class SQLManager { + private static final String SQLITE_JDBC_DRIVER = "org.sqlite.JDBC"; + + private static final boolean OPT_AUTO_COMMIT = false; + private static final int OPT_VALID_TIMEOUT = 500; + + private Connection conn = null; + private String driver; + private String url; + + public SQLManager(String url) { + this.driver = SQLITE_JDBC_DRIVER; + this.url = url; + } + + public Connection createConnection() { + try { + Class.forName(this.driver); + this.conn = DriverManager.getConnection(this.url); + this.conn.setAutoCommit(OPT_AUTO_COMMIT); + } catch(ClassNotFoundException | SQLException e) { + e.printStackTrace(); + } + + return this.conn; + } + + public void closeConnection() { + try { + if(this.conn == null) { + return; + } + this.conn.close(); + } catch(SQLException e) { + e.printStackTrace(); + } + } + + public Connection ensureConnection() { + try { + if (this.conn == null || this.conn.isClosed() || !this.conn.isValid(OPT_VALID_TIMEOUT)) { + closeConnection(); + createConnection(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return this.conn; + } + + public Connection getConnection() { + return this.conn; + } +} diff --git a/src/main/java/janggi/domain/GameInfo.java b/src/main/java/janggi/domain/GameInfo.java new file mode 100644 index 0000000000..1018f88850 --- /dev/null +++ b/src/main/java/janggi/domain/GameInfo.java @@ -0,0 +1,4 @@ +package janggi.domain; + +public record GameInfo(String id, String name, String created, String recent) { +} diff --git a/src/main/java/janggi/domain/piece/ActivePiece.java b/src/main/java/janggi/domain/piece/ActivePiece.java index 19b35624ee..59fabb3499 100644 --- a/src/main/java/janggi/domain/piece/ActivePiece.java +++ b/src/main/java/janggi/domain/piece/ActivePiece.java @@ -10,7 +10,6 @@ public abstract class ActivePiece extends BasePiece { protected static final String UNMOVABLE_ROUTE_MESSAGE = "이동할 수 없는 경로입니다."; protected final RoutePolicy routePolicy; - // public ActivePiece(RoutePolicy routePolicy, Side side, PieceType pieceType) { super(side, pieceType); From cb946b8444d471a3c68bce03255254fee480a624 Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 15:55:14 +0900 Subject: [PATCH 19/29] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=B0=A9=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EB=B0=8F=20=EC=84=A0=ED=83=9D=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Application.java | 9 ++++- src/main/java/janggi/JanggiService.java | 26 ++++++++++++ src/main/java/janggi/Runner.java | 49 +++++++++++++++++++++++ src/main/java/janggi/domain/GameName.java | 19 +++++++++ src/main/java/janggi/view/InputView.java | 16 ++++++++ src/main/java/janggi/view/OutputView.java | 11 +++++ 6 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/main/java/janggi/JanggiService.java create mode 100644 src/main/java/janggi/domain/GameName.java diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index c17454283c..cb69f0efc1 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -1,8 +1,15 @@ package janggi; +import janggi.dao.GameRoom; + public class Application { public static void main(String[] args) { - Runner runner = new Runner(); + GameRoom gameRoom = new GameRoom(); + gameRoom.initTable(); + + JanggiService janggiService = new JanggiService(gameRoom); + + Runner runner = new Runner(janggiService); runner.run(); } } diff --git a/src/main/java/janggi/JanggiService.java b/src/main/java/janggi/JanggiService.java new file mode 100644 index 0000000000..f39a1ecbc1 --- /dev/null +++ b/src/main/java/janggi/JanggiService.java @@ -0,0 +1,26 @@ +package janggi; + +import janggi.dao.GameRoom; +import janggi.domain.GameInfo; +import java.util.List; + +public class JanggiService { + private GameRoom gameRoom; + + public JanggiService(GameRoom gameRoom) { + this.gameRoom = gameRoom; + } + + public List getEntireGame() { + gameRoom.initTable(); + return gameRoom.findAllGames(); + } + + public void addGameData(String name, String createdDate, String lastUpdatedDate) { + gameRoom.insertGame(name, createdDate, lastUpdatedDate); + } + + public void removeGame(int id) { + gameRoom.removeGame(id); + } +} diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 950c6444cd..bb8b449284 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -2,11 +2,15 @@ import janggi.domain.Arrangement; import janggi.domain.Game; +import janggi.domain.GameInfo; +import janggi.domain.GameName; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.SideScore; import janggi.view.InputView; import janggi.view.OutputView; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; import java.util.logging.Level; @@ -20,11 +24,56 @@ public class Runner { private static final Logger logger = Logger.getLogger(Runner.class.getName()); + private final JanggiService janggiService; + + public Runner(JanggiService janggiService) { + this.janggiService = janggiService; + } + public void run() { + manageGameRoom(); Game game = initArrangeGame(); playGame(game); } + public void manageGameRoom() { + List gameInfos = janggiService.getEntireGame(); + if(gameInfos.isEmpty()) { + addGame(); + /* initialize new */ + return; + } + OutputView.printGameRoom(gameInfos); + + Optional input = InputView.askLoadGame(); + + if(input.isEmpty()) { + addGame(); + /* initialize new */ + return; + } + + GameInfo selectedGame = gameInfos.get(input.get() - 1); + + if(selectedGame == null) { + throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); + } + + /* initialize info */ + + } + + private void addGame() { + GameName gameName = new GameName(InputView.askGameName()); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String formattedNow = LocalDateTime.now().format(formatter); + + System.out.println(formattedNow); + + janggiService.addGameData(gameName.name(), formattedNow, formattedNow); + } + private Game initArrangeGame() { String hanArrangementInput = InputView.askHanArrangement(); Arrangement hanArrangement = Arrangement.from(hanArrangementInput); diff --git a/src/main/java/janggi/domain/GameName.java b/src/main/java/janggi/domain/GameName.java new file mode 100644 index 0000000000..f280f5d8c5 --- /dev/null +++ b/src/main/java/janggi/domain/GameName.java @@ -0,0 +1,19 @@ +package janggi.domain; + +public record GameName(String name) { + private static final int MIN_NAME_SIZE = 2; + private static final int MAX_NAME_SIZE = 20; + + private static final String INVALID_NAME_SIZE_MESSAGE = "이름은 2글자 이상, 20글자 이하로만 가능합니다."; + + public GameName(String name) { + this.name = validate(name); + } + + private static String validate(String name) { + if(name.length() < MIN_NAME_SIZE || name.length() > MAX_NAME_SIZE) { + throw new IllegalArgumentException(INVALID_NAME_SIZE_MESSAGE); + } + return name; + } +} diff --git a/src/main/java/janggi/view/InputView.java b/src/main/java/janggi/view/InputView.java index 956953d8b5..5ce74a983c 100644 --- a/src/main/java/janggi/view/InputView.java +++ b/src/main/java/janggi/view/InputView.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import java.util.Scanner; +import javax.swing.text.html.Option; public class InputView { private static final String HAN_ARRANGEMENT_MESSAGE = "한 진영의 배치를 입력해주세요. (예: 마상마상, 마상상마, 상마상마, 상마마상)"; @@ -19,6 +20,21 @@ public class InputView { private static final Scanner scanner = new Scanner(System.in); + public static Optional askLoadGame() { + System.out.println("불러오려는 게임의 번호를, 또는 새로 생성하려면 '*'를 입력해주세요."); + String input = scanner.nextLine(); + System.out.println(); + if(input.trim().equals("*")) { + return Optional.empty(); + } + return Optional.of(parseInt(input)); + } + + public static String askGameName() { + System.out.println("생성하려는 게임의 이름을 입력해주세요."); + return scanner.nextLine(); + } + public static String askHanArrangement() { System.out.println(HAN_ARRANGEMENT_MESSAGE); return scanner.nextLine(); diff --git a/src/main/java/janggi/view/OutputView.java b/src/main/java/janggi/view/OutputView.java index ef0e1e8f2a..b32028d24a 100644 --- a/src/main/java/janggi/view/OutputView.java +++ b/src/main/java/janggi/view/OutputView.java @@ -1,10 +1,12 @@ package janggi.view; +import janggi.domain.GameInfo; import janggi.domain.Side; import janggi.domain.SideScore; import janggi.domain.piece.PieceAttribute; import janggi.domain.piece.PieceType; import janggi.dto.BoardDto; +import java.time.format.DateTimeFormatter; import java.util.List; public class OutputView { @@ -27,6 +29,15 @@ public static void printLine() { System.out.println(); } + public static void printGameRoom(List gameInfos) { + System.out.println("진행 중인 장기 게임"); + for(int i = 0; i < gameInfos.size(); i++) { + GameInfo gameInfo = gameInfos.get(i); + System.out.println(i + 1 + ". 이름: " + gameInfo.name() + ", 생성 시간: " + gameInfo.created() + ", 가장 마지막 플레이 시간: " + gameInfo.recent()); + } + printLine(); + } + public static void printBoard(BoardDto boardDto) { List> board = boardDto.board(); StringBuilder result = new StringBuilder(); From e47ab051db91b26729c3553c0103dcbfdd2471c0 Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 18:24:11 +0900 Subject: [PATCH 20/29] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EA=B8=B0?= =?UTF-8?q?=EB=AC=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=80=EC=9E=A5,?= =?UTF-8?q?=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Application.java | 16 ++- src/main/java/janggi/JanggiService.java | 43 ++++++- src/main/java/janggi/Runner.java | 77 ++++++------- src/main/java/janggi/dao/GameRoom.java | 33 ++++-- src/main/java/janggi/dao/Piece.java | 108 ++++++++++++++++++ .../java/janggi/domain/BoardInitInfo.java | 4 + src/main/java/janggi/domain/Game.java | 38 +++++- src/main/java/janggi/domain/GameInfo.java | 2 +- .../java/janggi/domain/PieceInitInfo.java | 6 + src/main/java/janggi/domain/Position.java | 8 ++ src/main/java/janggi/domain/Side.java | 9 ++ src/main/java/janggi/domain/board/Board.java | 4 +- .../java/janggi/domain/piece/BasePiece.java | 7 ++ src/main/java/janggi/domain/piece/Piece.java | 3 + .../java/janggi/domain/piece/PieceType.java | 11 ++ src/main/java/janggi/domain/turn/ChoTurn.java | 11 +- .../java/janggi/domain/turn/FinishTurn.java | 2 +- src/main/java/janggi/domain/turn/HanTurn.java | 11 +- .../java/janggi/domain/turn/PlayerTurn.java | 2 +- .../java/janggi/domain/turn/TurnState.java | 6 + src/main/java/janggi/dto/PieceDto.java | 4 + .../janggi/initializer/BoardInitializer.java | 7 ++ src/main/resources/game_room.db | Bin 0 -> 12288 bytes src/main/resources/janggi.db | Bin 0 -> 20480 bytes 24 files changed, 338 insertions(+), 74 deletions(-) create mode 100644 src/main/java/janggi/dao/Piece.java create mode 100644 src/main/java/janggi/domain/BoardInitInfo.java create mode 100644 src/main/java/janggi/domain/PieceInitInfo.java create mode 100644 src/main/java/janggi/domain/turn/TurnState.java create mode 100644 src/main/java/janggi/dto/PieceDto.java create mode 100644 src/main/resources/game_room.db create mode 100644 src/main/resources/janggi.db diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index cb69f0efc1..2f52e6b627 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -1,15 +1,25 @@ package janggi; import janggi.dao.GameRoom; +import janggi.dao.Piece; +import janggi.db.SQLManager; +import janggi.domain.Game; public class Application { public static void main(String[] args) { - GameRoom gameRoom = new GameRoom(); + SQLManager sqlManager = new SQLManager("jdbc:sqlite:src/main/resources/janggi.db"); + + GameRoom gameRoom = new GameRoom(sqlManager); gameRoom.initTable(); - JanggiService janggiService = new JanggiService(gameRoom); + Piece piece = new Piece(sqlManager); + piece.initTable(); + + JanggiService janggiService = new JanggiService(sqlManager, gameRoom, piece); + + Game game = new Game(); - Runner runner = new Runner(janggiService); + Runner runner = new Runner(janggiService, game); runner.run(); } } diff --git a/src/main/java/janggi/JanggiService.java b/src/main/java/janggi/JanggiService.java index f39a1ecbc1..75e6e73df0 100644 --- a/src/main/java/janggi/JanggiService.java +++ b/src/main/java/janggi/JanggiService.java @@ -1,14 +1,26 @@ package janggi; import janggi.dao.GameRoom; +import janggi.dao.Piece; +import janggi.db.SQLManager; import janggi.domain.GameInfo; +import janggi.domain.PieceInitInfo; +import janggi.domain.Position; +import janggi.domain.Side; +import janggi.domain.piece.PieceType; +import java.sql.Connection; +import java.sql.SQLException; import java.util.List; public class JanggiService { - private GameRoom gameRoom; + private final SQLManager sqlManager; + private final GameRoom gameRoom; + private final Piece piece; - public JanggiService(GameRoom gameRoom) { + public JanggiService(SQLManager sqlManager, GameRoom gameRoom, Piece piece) { + this.sqlManager = sqlManager; this.gameRoom = gameRoom; + this.piece = piece; } public List getEntireGame() { @@ -16,11 +28,34 @@ public List getEntireGame() { return gameRoom.findAllGames(); } - public void addGameData(String name, String createdDate, String lastUpdatedDate) { - gameRoom.insertGame(name, createdDate, lastUpdatedDate); + public int addGameData(String name, String createdDate, String lastUpdatedDate, List pieceInitInfos) { + return gameRoom.insertGame(name, createdDate, lastUpdatedDate); } public void removeGame(int id) { gameRoom.removeGame(id); } + + public List getPieceInitInfos(int gameId) { + return piece.getAllPieces(gameId).stream() + .map(pieceDto -> new PieceInitInfo(new Position(pieceDto.x(), pieceDto.y()), Side.from(pieceDto.side()), PieceType.from(pieceDto.pieceType()))) + .toList(); + } + + public void movePiece(int gameId, Position start, Position end, Side side, PieceType pieceType) { + Connection connection = sqlManager.ensureConnection(); + try { + piece.deletePiece(connection, gameId, start.getX(), start.getY()); + piece.updatePiece(connection, gameId, end.getX(), end.getY(), side.name(), pieceType.name()); + + connection.commit(); + } catch (SQLException e) { + try { + connection.rollback(); + } catch (SQLException rollbackEx) { + rollbackEx.printStackTrace(); + } + throw new RuntimeException("기물 이동 중 오류가 발생하여 롤백되었습니다.", e); + } + } } diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index bb8b449284..222506f1fc 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -4,9 +4,11 @@ import janggi.domain.Game; import janggi.domain.GameInfo; import janggi.domain.GameName; +import janggi.domain.PieceInitInfo; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.SideScore; +import janggi.domain.piece.PieceAttribute; import janggi.view.InputView; import janggi.view.OutputView; import java.time.LocalDateTime; @@ -25,32 +27,29 @@ public class Runner { private static final Logger logger = Logger.getLogger(Runner.class.getName()); private final JanggiService janggiService; + private final Game game; - public Runner(JanggiService janggiService) { + public Runner(JanggiService janggiService, Game game) { this.janggiService = janggiService; + this.game = game; } public void run() { - manageGameRoom(); - Game game = initArrangeGame(); - playGame(game); + int gameId = manageGameRoom(); + playGame(gameId); } - public void manageGameRoom() { + public int manageGameRoom() { List gameInfos = janggiService.getEntireGame(); if(gameInfos.isEmpty()) { - addGame(); - /* initialize new */ - return; + return initArrangeGame(); } OutputView.printGameRoom(gameInfos); Optional input = InputView.askLoadGame(); if(input.isEmpty()) { - addGame(); - /* initialize new */ - return; + return initArrangeGame(); } GameInfo selectedGame = gameInfos.get(input.get() - 1); @@ -59,49 +58,46 @@ public void manageGameRoom() { throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); } - /* initialize info */ - + List pieceInitInfos = janggiService.getPieceInitInfos(selectedGame.id()); + game.init(pieceInitInfos); + return selectedGame.id(); } - private void addGame() { + private int initArrangeGame() { GameName gameName = new GameName(InputView.askGameName()); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String formattedNow = LocalDateTime.now().format(formatter); - - System.out.println(formattedNow); - - janggiService.addGameData(gameName.name(), formattedNow, formattedNow); - } - - private Game initArrangeGame() { String hanArrangementInput = InputView.askHanArrangement(); Arrangement hanArrangement = Arrangement.from(hanArrangementInput); String choArrangementInput = InputView.askChoArrangement(); Arrangement choArrangement = Arrangement.from(choArrangementInput); - return new Game(choArrangement, hanArrangement); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String formattedNow = LocalDateTime.now().format(formatter); + + List pieceInitInfos = game.init(hanArrangement, choArrangement); + + return janggiService.addGameData(gameName.name(), formattedNow, formattedNow, pieceInitInfos); } - private void playGame(Game game) { - while (playTurnGame(game)) { + private void playGame(int gameId) { + while (playTurnGame(gameId)) { } Side winnerSide = game.getWinnerSide(); if(winnerSide == null) { - printCurrentScore(game); - printScoreWinner(game); + printCurrentScore(); + printScoreWinner(); return; } OutputView.printWinner(winnerSide); } - private boolean playTurnGame(Game game) { + private boolean playTurnGame(int gameId) { try { - printCurrentStatus(game); - return executeTurn(game); + printCurrentStatus(); + return executeTurn(gameId); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); return true; @@ -112,17 +108,17 @@ private boolean playTurnGame(Game game) { } } - private void printCurrentStatus(Game game) { + private void printCurrentStatus() { OutputView.printLine(); OutputView.printBoard(game.getCurrentBoardDto()); OutputView.printTurn(game.getCurrentSide()); } - private boolean executeTurn(Game game) { + private boolean executeTurn(int gameId) { Optional> startPositionInput = InputView.askStartPosition(); if(startPositionInput.isEmpty()) { - return consentEndGame(game); + return consentEndGame(gameId); } Position startPosition = Position.from(startPositionInput.get()); @@ -131,23 +127,24 @@ private boolean executeTurn(Game game) { Position endPosition = Position.from(endPositionInput); OutputView.printLine(); - game.move(startPosition, endPosition); + PieceAttribute pieceAttribute = game.move(startPosition, endPosition); + janggiService.movePiece(gameId, startPosition, endPosition, pieceAttribute.side(), pieceAttribute.pieceType()); - printCurrentScore(game); + printCurrentScore(); return game.isFinished(); } - private boolean consentEndGame(Game game) { + private boolean consentEndGame(int gameId) { String consentInput = InputView.consentEnd(); if(consentInput.equals(END_TEXT)) { return false; } - return executeTurn(game); + return executeTurn(gameId); } - private void printScoreWinner(Game game) { + private void printScoreWinner() { SideScore score = game.getCurrentSideScore(); if(score.cho() > score.han()) { OutputView.printWinner(Side.CHO); @@ -157,7 +154,7 @@ private void printScoreWinner(Game game) { } - private void printCurrentScore(Game game) { + private void printCurrentScore() { OutputView.printScore(game.getCurrentSideScore()); } } diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java index a2ea3e5458..bfe4f0ac18 100644 --- a/src/main/java/janggi/dao/GameRoom.java +++ b/src/main/java/janggi/dao/GameRoom.java @@ -8,14 +8,22 @@ public class GameRoom { - private final SQLManager sqlManager = new SQLManager("jdbc:sqlite:src/main/resources/game_room.db"); + private final SQLManager sqlManager; + + public GameRoom(SQLManager sqlManager) { + this.sqlManager = sqlManager; + } public void initTable() { - String sql = "CREATE TABLE IF NOT EXISTS GameRoom (" + - "id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "name TEXT NOT NULL, " + - "created_date TEXT NOT NULL, " + - "recently_date TEXT NOT NULL)"; + String sql = + """ + CREATE TABLE IF NOT EXISTS GameRoom ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + """; try (Connection conn = sqlManager.ensureConnection(); Statement stmt = conn.createStatement()) { @@ -36,7 +44,7 @@ public List findAllGames() { while (rs.next()) { GameInfo game = new GameInfo( - rs.getString("id"), + rs.getInt("id"), rs.getString("name"), rs.getString("created_date"), rs.getString("recently_date") @@ -49,7 +57,8 @@ public List findAllGames() { return gameInfos; } - public void insertGame(String name, String createdDate, String recentlyDate) { + public int insertGame(String name, String createdDate, String recentlyDate) { + int generatedId = -1; String sql = "INSERT INTO GameRoom (name, created_date, recently_date) VALUES (?, ?, ?)"; try (Connection conn = sqlManager.ensureConnection(); @@ -63,9 +72,17 @@ public void insertGame(String name, String createdDate, String recentlyDate) { if (!conn.getAutoCommit()) conn.commit(); + try (ResultSet rs = pstmt.getGeneratedKeys()) { + if (rs.next()) { + generatedId = rs.getInt(1); // 첫 번째 컬럼이 생성된 ID + } + } + } catch (SQLException e) { e.printStackTrace(); } + + return generatedId; } public void removeGame(int id) { diff --git a/src/main/java/janggi/dao/Piece.java b/src/main/java/janggi/dao/Piece.java new file mode 100644 index 0000000000..3ccecbfe26 --- /dev/null +++ b/src/main/java/janggi/dao/Piece.java @@ -0,0 +1,108 @@ +package janggi.dao; + +import janggi.db.SQLManager; +import janggi.dto.PieceDto; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + + +public class Piece { + private final SQLManager sqlManager; + + public Piece(SQLManager sqlManager) { + this.sqlManager = sqlManager; + } + + public void initTable() { + String sql = + """ + CREATE TABLE IF NOT EXISTS Piece ( + game_id INTEGER NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL, + piece_type TEXT NOT NULL, + side TEXT NOT NULL, + PRIMARY KEY (game_id, x, y), + FOREIGN KEY (game_id) REFERENCES GameRoom(id) ON DELETE CASCADE + ) + """; + + try (Connection conn = sqlManager.ensureConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(sql); + if (!conn.getAutoCommit()) conn.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public List getAllPieces(int gameId) { + List pieces = new ArrayList<>(); + String sql = "SELECT * FROM Piece WHERE game_id = ?"; + + try (Connection conn = sqlManager.ensureConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setInt(1, gameId); + + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + PieceDto pieceDto = new PieceDto( + rs.getInt("x"), + rs.getInt("y"), + rs.getString("piece_type"), + rs.getString("side") + ); + pieces.add(pieceDto); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return pieces; + } + + public void updatePiece(Connection conn, int gameId, int x, int y, String type, String side) { + String sql = """ + INSERT INTO Piece (game_id, x, y, piece_type, side) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(game_id, x, y) + DO UPDATE SET + piece_type = excluded.piece_type, + side = excluded.side + """; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setInt(1, gameId); + pstmt.setInt(2, x); + pstmt.setInt(3, y); + pstmt.setString(4, type); + pstmt.setString(5, side); + + pstmt.executeUpdate(); + + if (!conn.getAutoCommit()) conn.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void deletePiece(Connection conn, int gameId, int x, int y) { + String sql = "DELETE FROM Piece WHERE game_id = ? AND x = ? AND y = ?"; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setInt(1, gameId); + pstmt.setInt(2, x); + pstmt.setInt(3, y); + + pstmt.executeUpdate(); + if (!conn.getAutoCommit()) conn.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } + } +} + diff --git a/src/main/java/janggi/domain/BoardInitInfo.java b/src/main/java/janggi/domain/BoardInitInfo.java new file mode 100644 index 0000000000..628836cce5 --- /dev/null +++ b/src/main/java/janggi/domain/BoardInitInfo.java @@ -0,0 +1,4 @@ +package janggi.domain; + +public record BoardInitInfo(String name, String createdAt, String updatedAt) { +} diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index 6caae60b08..5751ae7d93 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -2,18 +2,20 @@ import janggi.domain.board.Board; import janggi.domain.piece.Piece; +import janggi.domain.piece.PieceAttribute; import janggi.domain.turn.ChoTurn; import janggi.domain.turn.PlayerTurn; +import janggi.domain.turn.TurnState; import janggi.dto.BoardDto; import janggi.initializer.BoardInitializer; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class Game { - private static final String INVALID_WINNER_SIDE = "잘못된 승자 진영입니다."; - private PlayerTurn playerTurn; - public Game(Arrangement choArrangement, Arrangement hanArrangement) { + public List init(Arrangement choArrangement, Arrangement hanArrangement) { Map initBoard = BoardInitializer.createBoard(choArrangement, hanArrangement); int hanScore = initBoard.values().stream() .filter(piece -> piece.isEqualSide(Side.HAN)) @@ -25,10 +27,30 @@ public Game(Arrangement choArrangement, Arrangement hanArrangement) { .sum(); this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 1); + return getPieceInitInfo(initBoard); } - public void move(Position start, Position end) { - playerTurn = playerTurn.move(start, end); + public List init(List pieceInitInfos) { + Map initBoard = BoardInitializer.createBoard(pieceInitInfos); + int hanScore = initBoard.values().stream() + .filter(piece -> piece.isEqualSide(Side.HAN)) + .mapToInt(Piece::getPieceScore) + .sum(); + int choScore = initBoard.values().stream() + .filter(piece -> piece.isEqualSide(Side.CHO)) + .mapToInt(Piece::getPieceScore) + .sum(); + + this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 1); // 현재 side, turn 가져오기 + return getPieceInitInfo(initBoard); + } + + public PieceAttribute move(Position start, Position end) { + Side currentTurn = playerTurn.getCurrentSide(); + + TurnState turnState = playerTurn.move(start, end); + playerTurn = turnState.playerTurn(); + return turnState.movedPiece(); } public boolean isFinished() { @@ -50,4 +72,10 @@ public SideScore getCurrentSideScore() { public Side getWinnerSide() { return playerTurn.getWinnerSide(); } + + private List getPieceInitInfo(Map board) { + List pieceInitInfos = new ArrayList<>(); + board.forEach(((position, piece) -> pieceInitInfos.add(piece.getPieceInitInfo(position)))); + return pieceInitInfos; + } } diff --git a/src/main/java/janggi/domain/GameInfo.java b/src/main/java/janggi/domain/GameInfo.java index 1018f88850..7c07122bc5 100644 --- a/src/main/java/janggi/domain/GameInfo.java +++ b/src/main/java/janggi/domain/GameInfo.java @@ -1,4 +1,4 @@ package janggi.domain; -public record GameInfo(String id, String name, String created, String recent) { +public record GameInfo(int id, String name, String created, String recent) { } diff --git a/src/main/java/janggi/domain/PieceInitInfo.java b/src/main/java/janggi/domain/PieceInitInfo.java new file mode 100644 index 0000000000..f4c2998935 --- /dev/null +++ b/src/main/java/janggi/domain/PieceInitInfo.java @@ -0,0 +1,6 @@ +package janggi.domain; + +import janggi.domain.piece.PieceType; + +public record PieceInitInfo(Position position, Side side, PieceType pieceType) { +} diff --git a/src/main/java/janggi/domain/Position.java b/src/main/java/janggi/domain/Position.java index b1ee033875..dec7765062 100644 --- a/src/main/java/janggi/domain/Position.java +++ b/src/main/java/janggi/domain/Position.java @@ -95,4 +95,12 @@ public int compareY(Position position) { public boolean isRange(int startX, int endX, int startY, int endY) { return x >= startX && x <= endX && y >= startY && y <= endY; } + + public int getX() { + return x; + } + + public int getY() { + return y; + } } diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index e0902b564d..bbb65246cc 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -1,5 +1,6 @@ package janggi.domain; +import java.util.Arrays; import java.util.Map; public enum Side { @@ -8,6 +9,7 @@ public enum Side { EMPTY("없음"); private static final String INVALID_OPPOSITE_SIDE = "반대 진영이 없습니다."; + private static final String INVALID_SIDE_NAME = "해당 이름의 진영이 없습니다."; private static final Map oppositeSide = Map.of( Side.CHO, Side.HAN, @@ -33,4 +35,11 @@ public Side getOppositeSide() { return side; } + + public static Side from(String name) { + return Arrays.stream(Side.values()) + .filter(side -> side.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_SIDE_NAME)); + } } diff --git a/src/main/java/janggi/domain/board/Board.java b/src/main/java/janggi/domain/board/Board.java index fdf0ebf4f9..aaa5e49dc3 100644 --- a/src/main/java/janggi/domain/board/Board.java +++ b/src/main/java/janggi/domain/board/Board.java @@ -43,7 +43,7 @@ public List> getCurrentBoard() { return currentBoard.getValues(); } - public void move(Position start, Position end, Side movableSide) { + public PieceAttribute move(Position start, Position end, Side movableSide) { Piece piece = board.get(start); if (!piece.isEqualSide(movableSide)) { throw new IllegalArgumentException(INVALID_PIECE_SIDE_MESSAGE); @@ -53,6 +53,8 @@ public void move(Position start, Position end, Side movableSide) { piece.validateRoute(route, this); movePiece(start, end, piece, movableSide); + + return piece.getPieceInfo(); } public boolean isEndGame() { diff --git a/src/main/java/janggi/domain/piece/BasePiece.java b/src/main/java/janggi/domain/piece/BasePiece.java index efaa5dba7f..53f5b7fea5 100644 --- a/src/main/java/janggi/domain/piece/BasePiece.java +++ b/src/main/java/janggi/domain/piece/BasePiece.java @@ -1,5 +1,7 @@ package janggi.domain.piece; +import janggi.domain.PieceInitInfo; +import janggi.domain.Position; import janggi.domain.Side; public abstract class BasePiece implements Piece { @@ -30,4 +32,9 @@ public PieceAttribute getPieceInfo() { public int getPieceScore() { return this.pieceType.getScore(); } + + @Override + public PieceInitInfo getPieceInitInfo(Position position) { + return new PieceInitInfo(position, side, pieceType); + } } diff --git a/src/main/java/janggi/domain/piece/Piece.java b/src/main/java/janggi/domain/piece/Piece.java index fcfb2cf5c5..682cac46b9 100644 --- a/src/main/java/janggi/domain/piece/Piece.java +++ b/src/main/java/janggi/domain/piece/Piece.java @@ -1,5 +1,6 @@ package janggi.domain.piece; +import janggi.domain.PieceInitInfo; import janggi.domain.Position; import janggi.domain.Route; import janggi.domain.Side; @@ -17,4 +18,6 @@ public interface Piece { int getPieceScore(); PieceAttribute getPieceInfo(); + + PieceInitInfo getPieceInitInfo(Position position); } diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java index 65260c1fb8..70d65eee6b 100644 --- a/src/main/java/janggi/domain/piece/PieceType.java +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -1,5 +1,7 @@ package janggi.domain.piece; +import java.util.Arrays; + public enum PieceType { CHA("CH", 13), GUNG("GU", 0), @@ -10,6 +12,8 @@ public enum PieceType { SA("SA", 3), SANG("SG", 3); + private static final String INVALID_PIECE_TYPE_NAME = "해당 이름의 기물 종류가 없습니다."; + private final String name; private final int score; @@ -25,4 +29,11 @@ public String getName() { public int getScore() { return score; } + + public static PieceType from(String name) { + return Arrays.stream(PieceType.values()) + .filter(pieceType -> pieceType.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(INVALID_PIECE_TYPE_NAME)); + } } diff --git a/src/main/java/janggi/domain/turn/ChoTurn.java b/src/main/java/janggi/domain/turn/ChoTurn.java index ff009603a8..55b8383081 100644 --- a/src/main/java/janggi/domain/turn/ChoTurn.java +++ b/src/main/java/janggi/domain/turn/ChoTurn.java @@ -3,6 +3,7 @@ import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.board.Board; +import janggi.domain.piece.PieceAttribute; public class ChoTurn extends BaseTurn { public ChoTurn(Board board, int turn) { @@ -10,18 +11,18 @@ public ChoTurn(Board board, int turn) { } @Override - public PlayerTurn move(Position start, Position end) { - board.move(start, end, Side.CHO); + public TurnState move(Position start, Position end) { + PieceAttribute pieceAttribute = board.move(start, end, Side.CHO); if(turn == MAX_TURN) { - return new FinishTurn(board, Side.EMPTY, turn); + return new TurnState(new FinishTurn(board, Side.EMPTY, turn), pieceAttribute); } if (board.isEndGame()) { - return new FinishTurn(board, Side.CHO, turn); + return new TurnState(new FinishTurn(board, Side.CHO, turn), pieceAttribute); } - return new HanTurn(board, turn + 1); + return new TurnState(new HanTurn(board, turn + 1), pieceAttribute); } @Override diff --git a/src/main/java/janggi/domain/turn/FinishTurn.java b/src/main/java/janggi/domain/turn/FinishTurn.java index 6f24947367..138e6febb8 100644 --- a/src/main/java/janggi/domain/turn/FinishTurn.java +++ b/src/main/java/janggi/domain/turn/FinishTurn.java @@ -15,7 +15,7 @@ public FinishTurn(Board board, Side winnerSide, int turn) { } @Override - public PlayerTurn move(Position start, Position end) { + public TurnState move(Position start, Position end) { throw new IllegalStateException(INVALID_MOVE); } diff --git a/src/main/java/janggi/domain/turn/HanTurn.java b/src/main/java/janggi/domain/turn/HanTurn.java index b45883f0b5..98d8802a91 100644 --- a/src/main/java/janggi/domain/turn/HanTurn.java +++ b/src/main/java/janggi/domain/turn/HanTurn.java @@ -3,6 +3,7 @@ import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.board.Board; +import janggi.domain.piece.PieceAttribute; public class HanTurn extends BaseTurn { public HanTurn(Board board, int turn) { @@ -10,17 +11,17 @@ public HanTurn(Board board, int turn) { } @Override - public PlayerTurn move(Position start, Position end) { - board.move(start, end, Side.HAN); + public TurnState move(Position start, Position end) { + PieceAttribute pieceAttribute = board.move(start, end, Side.HAN); if(turn == MAX_TURN) { - return new FinishTurn(board, Side.EMPTY, turn); + return new TurnState(new FinishTurn(board, Side.EMPTY, turn), pieceAttribute); } if (board.isEndGame()) { - return new FinishTurn(board, Side.HAN, turn); + return new TurnState(new FinishTurn(board, Side.HAN, turn), pieceAttribute); } - return new ChoTurn(board, turn + 1); + return new TurnState(new ChoTurn(board, turn + 1), pieceAttribute); } @Override diff --git a/src/main/java/janggi/domain/turn/PlayerTurn.java b/src/main/java/janggi/domain/turn/PlayerTurn.java index 6efdde627c..2e0335d9bb 100644 --- a/src/main/java/janggi/domain/turn/PlayerTurn.java +++ b/src/main/java/janggi/domain/turn/PlayerTurn.java @@ -7,7 +7,7 @@ import java.util.List; public interface PlayerTurn { - PlayerTurn move(Position start, Position end); + TurnState move(Position start, Position end); boolean isFinished(); diff --git a/src/main/java/janggi/domain/turn/TurnState.java b/src/main/java/janggi/domain/turn/TurnState.java new file mode 100644 index 0000000000..6be102cc66 --- /dev/null +++ b/src/main/java/janggi/domain/turn/TurnState.java @@ -0,0 +1,6 @@ +package janggi.domain.turn; + +import janggi.domain.piece.PieceAttribute; + +public record TurnState(PlayerTurn playerTurn, PieceAttribute movedPiece) { +} diff --git a/src/main/java/janggi/dto/PieceDto.java b/src/main/java/janggi/dto/PieceDto.java new file mode 100644 index 0000000000..142193a879 --- /dev/null +++ b/src/main/java/janggi/dto/PieceDto.java @@ -0,0 +1,4 @@ +package janggi.dto; + +public record PieceDto(int x, int y, String pieceType, String side) { +} diff --git a/src/main/java/janggi/initializer/BoardInitializer.java b/src/main/java/janggi/initializer/BoardInitializer.java index f5b6d74e15..b55a9c8700 100644 --- a/src/main/java/janggi/initializer/BoardInitializer.java +++ b/src/main/java/janggi/initializer/BoardInitializer.java @@ -1,6 +1,7 @@ package janggi.initializer; import janggi.domain.Arrangement; +import janggi.domain.PieceInitInfo; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.piece.Cha; @@ -85,6 +86,12 @@ public static Map createBoard(Arrangement choArrangement, Arran return board; } + public static Map createBoard(List pieceInitInfos) { + Map board = initBoard(); + pieceInitInfos.forEach(pieceInitInfo -> initBoard().put(pieceInitInfo.position(), pieceMap.get(pieceInitInfo.pieceType()).apply(pieceInitInfo.side()))); + return board; + } + private static Map initBoard() { Map pieces = new HashMap<>(); for (int i = Position.BOARD_START_ROWS; i <= Position.BOARD_END_ROWS; i++) { diff --git a/src/main/resources/game_room.db b/src/main/resources/game_room.db new file mode 100644 index 0000000000000000000000000000000000000000..0263666ab28a9ce9a5792381803786bf0bf88a97 GIT binary patch literal 12288 zcmeI&&q~8E90%}Z-Snc=+fKuHk&SIK+jY#ItWw6X>t^lHfiUV8VX!}Ls|UR)cp2V& z1s}p^@LAl&ComNX+bD=9LB2nlr2XZW1U|Pw&rYqL&)M~8GVpw+k}X0hIb@6w5*JMp zEetANf?OEE_fHWgd-pe+qADgQhzU9Nt>Zf$DzfbUm3$(Z}uYJu4#b zj7EcSj4kaFW?8z|WmVfXE2hI5PPJw@Ep}qIn9+3WRa>m7nYNo_!70o&FI;BVMQd7C zj&&y7^Le-375(bmgm?JR@83mCW`eu9BwrHwU49V*0s;_#00bZa0SG_<0uX=z1Rwx` zUkYeag6yd3{OS2)_Bx-vsY*d9|jKW!XHc3bJ= z^fmTAJMK8yiD^=j7YJX0E$OE}mOeTO-23V#sS-cN_roX^=f<&Nn#Ly~48tht=aqhn zl-0{cMd#*Pc~`Myy!$n&*8Uph>XY&Is`jt?RQt25GjRkEKmY**5I_I{1Q0*~fu+F1 z$-$9jS?1%LH2Qw4x``SrU*)DRoj{7f`Rq!O$3?v&bov<$RhY!0?FF(Wec^Qi;oZ1y zBX6DWh%a`;C)pRnbTLsPkl*x78?#N5cx$}tx33+)FTTjWsIL~(5c7su*vn;Hc6`}x zc^e&U;mb?uORp(=qNR7?kH^D$65FEVi3{mUz1^nMYdROQV%z0I>!W2F$tYIy>D{fq z(T3419q08h_XywTS$3a&ooRMUht}Dd+0P|cL#FC(rbYwxqI$5Q`=YTv(ratzk@oL% z^T;|qH6K3Ya~5kZ^3ujEMRavcYbUg><=h0Fwxs^U;z; Date: Thu, 2 Apr 2026 22:48:07 +0900 Subject: [PATCH 21/29] =?UTF-8?q?feat:=20gameRoom=20turn,=20side=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20move=20=EC=8B=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/JanggiService.java | 47 ++++++++-- src/main/java/janggi/Runner.java | 32 ++++--- src/main/java/janggi/dao/GameRoom.java | 91 +++++++++++-------- src/main/java/janggi/dao/Piece.java | 45 +++++++-- src/main/java/janggi/db/SQLManager.java | 43 ++++----- src/main/java/janggi/domain/Game.java | 20 ++-- src/main/java/janggi/domain/GameInfo.java | 2 +- src/main/java/janggi/domain/MoveResult.java | 7 ++ src/main/java/janggi/domain/turn/ChoTurn.java | 9 +- src/main/java/janggi/domain/turn/HanTurn.java | 10 +- .../janggi/domain/turn/TurnAttribute.java | 6 ++ .../java/janggi/domain/turn/TurnState.java | 2 +- src/main/java/janggi/dto/GameDto.java | 4 + src/main/java/janggi/dto/GameResponseDto.java | 4 + .../java/janggi/dto/PiecePositionDto.java | 4 + src/main/java/janggi/dto/TurnDto.java | 4 + 16 files changed, 225 insertions(+), 105 deletions(-) create mode 100644 src/main/java/janggi/domain/MoveResult.java create mode 100644 src/main/java/janggi/domain/turn/TurnAttribute.java create mode 100644 src/main/java/janggi/dto/GameDto.java create mode 100644 src/main/java/janggi/dto/GameResponseDto.java create mode 100644 src/main/java/janggi/dto/PiecePositionDto.java create mode 100644 src/main/java/janggi/dto/TurnDto.java diff --git a/src/main/java/janggi/JanggiService.java b/src/main/java/janggi/JanggiService.java index 75e6e73df0..f1afcfcae0 100644 --- a/src/main/java/janggi/JanggiService.java +++ b/src/main/java/janggi/JanggiService.java @@ -3,11 +3,14 @@ import janggi.dao.GameRoom; import janggi.dao.Piece; import janggi.db.SQLManager; -import janggi.domain.GameInfo; import janggi.domain.PieceInitInfo; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.piece.PieceType; +import janggi.dto.GameDto; +import janggi.dto.GameResponseDto; +import janggi.dto.PieceDto; +import janggi.dto.TurnDto; import java.sql.Connection; import java.sql.SQLException; import java.util.List; @@ -23,17 +26,45 @@ public JanggiService(SQLManager sqlManager, GameRoom gameRoom, Piece piece) { this.piece = piece; } - public List getEntireGame() { + public List getEntireGame() { gameRoom.initTable(); return gameRoom.findAllGames(); } - public int addGameData(String name, String createdDate, String lastUpdatedDate, List pieceInitInfos) { - return gameRoom.insertGame(name, createdDate, lastUpdatedDate); + public int addGameData(GameDto gameDto, List pieceDtos) { + Connection connection = sqlManager.ensureConnection(); + + try { + int gameId = gameRoom.insertGame(connection, gameDto); + piece.updatePieces(connection, gameId, pieceDtos); + + connection.commit(); + return gameId; + } catch (Exception e) { + try { + if (connection != null) { + connection.rollback(); + } + } catch (SQLException rollbackEx) { + rollbackEx.printStackTrace(); + } + throw new RuntimeException("게임 데이터 생성 중 오류가 발생하여 롤백되었습니다.", e); + } } public void removeGame(int id) { - gameRoom.removeGame(id); + Connection connection = sqlManager.ensureConnection(); + try { + gameRoom.removeGame(connection, id); + connection.commit(); + } catch (SQLException e) { + try { + connection.rollback(); + } catch (SQLException rollbackEx) { + rollbackEx.printStackTrace(); + } + throw new RuntimeException("게임 데이터 삭제 중 오류가 발생하여 롤백되었습니다.", e); + } } public List getPieceInitInfos(int gameId) { @@ -42,11 +73,13 @@ public List getPieceInitInfos(int gameId) { .toList(); } - public void movePiece(int gameId, Position start, Position end, Side side, PieceType pieceType) { + public void movePiece(int gameId, Position start, Position end, Side side, PieceType pieceType, TurnDto turnDto) { Connection connection = sqlManager.ensureConnection(); + PieceDto pieceDto = new PieceDto(end.getX(), end.getY(), side.name(), pieceType.name()); try { + gameRoom.updateGameTurn(connection, gameId, turnDto); piece.deletePiece(connection, gameId, start.getX(), start.getY()); - piece.updatePiece(connection, gameId, end.getX(), end.getY(), side.name(), pieceType.name()); + piece.updatePiece(connection, gameId, pieceDto); connection.commit(); } catch (SQLException e) { diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 222506f1fc..b68f350deb 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -4,11 +4,14 @@ import janggi.domain.Game; import janggi.domain.GameInfo; import janggi.domain.GameName; +import janggi.domain.MoveResult; import janggi.domain.PieceInitInfo; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.SideScore; -import janggi.domain.piece.PieceAttribute; +import janggi.dto.GameDto; +import janggi.dto.PieceDto; +import janggi.dto.TurnDto; import janggi.view.InputView; import janggi.view.OutputView; import java.time.LocalDateTime; @@ -40,12 +43,13 @@ public void run() { } public int manageGameRoom() { - List gameInfos = janggiService.getEntireGame(); + List gameInfos = janggiService.getEntireGame().stream() + .map(gameResponseDto -> new GameInfo(gameResponseDto.id(), gameResponseDto.name(), gameResponseDto.createdAt(), gameResponseDto.updatedAt(), Side.from(gameResponseDto.side()), gameResponseDto.turn())) + .toList(); if(gameInfos.isEmpty()) { return initArrangeGame(); } OutputView.printGameRoom(gameInfos); - Optional input = InputView.askLoadGame(); if(input.isEmpty()) { @@ -59,12 +63,14 @@ public int manageGameRoom() { } List pieceInitInfos = janggiService.getPieceInitInfos(selectedGame.id()); - game.init(pieceInitInfos); + game.init(pieceInitInfos, selectedGame.side(), selectedGame.turn()); return selectedGame.id(); } private int initArrangeGame() { GameName gameName = new GameName(InputView.askGameName()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String formattedNow = LocalDateTime.now().format(formatter); String hanArrangementInput = InputView.askHanArrangement(); Arrangement hanArrangement = Arrangement.from(hanArrangementInput); @@ -72,18 +78,19 @@ private int initArrangeGame() { String choArrangementInput = InputView.askChoArrangement(); Arrangement choArrangement = Arrangement.from(choArrangementInput); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String formattedNow = LocalDateTime.now().format(formatter); + List pieceDtos = game.init(choArrangement, hanArrangement).stream() + .map(pieceInitInfo -> new PieceDto(pieceInitInfo.position().getX(), pieceInitInfo.position().getY(), pieceInitInfo.pieceType().name(), pieceInitInfo.side().name())) + .toList(); - List pieceInitInfos = game.init(hanArrangement, choArrangement); - - return janggiService.addGameData(gameName.name(), formattedNow, formattedNow, pieceInitInfos); + return janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.name(), 1), pieceDtos); } private void playGame(int gameId) { while (playTurnGame(gameId)) { } + janggiService.removeGame(gameId); + Side winnerSide = game.getWinnerSide(); if(winnerSide == null) { @@ -125,10 +132,13 @@ private boolean executeTurn(int gameId) { List endPositionInput = InputView.askEndPosition(); Position endPosition = Position.from(endPositionInput); + OutputView.printLine(); - PieceAttribute pieceAttribute = game.move(startPosition, endPosition); - janggiService.movePiece(gameId, startPosition, endPosition, pieceAttribute.side(), pieceAttribute.pieceType()); + MoveResult moveResult = game.move(startPosition, endPosition); + TurnDto turnDto = new TurnDto(moveResult.turnAttribute().side().name(), moveResult.turnAttribute().turn()); + + janggiService.movePiece(gameId, startPosition, endPosition, moveResult.pieceAttribute().side(), moveResult.pieceAttribute().pieceType(), turnDto); printCurrentScore(); return game.isFinished(); diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java index bfe4f0ac18..888d651056 100644 --- a/src/main/java/janggi/dao/GameRoom.java +++ b/src/main/java/janggi/dao/GameRoom.java @@ -1,7 +1,9 @@ package janggi.dao; -import janggi.domain.GameInfo; import janggi.db.SQLManager; +import janggi.dto.GameDto; +import janggi.dto.GameResponseDto; +import janggi.dto.TurnDto; import java.sql.*; import java.util.ArrayList; import java.util.List; @@ -21,7 +23,9 @@ CREATE TABLE IF NOT EXISTS GameRoom ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TEXT NOT NULL, - updated_at TEXT NOT NULL + updated_at TEXT NOT NULL, + turn INTEGER NOT NULL, + side TEXT NOT NULL ) """; @@ -34,8 +38,35 @@ CREATE TABLE IF NOT EXISTS GameRoom ( } } - public List findAllGames() { - List gameInfos = new ArrayList<>(); + public int insertGame(Connection connection, GameDto gameDto) { + int generatedId = -1; + String sql = "INSERT INTO GameRoom (name, created_at, updated_at, turn, side) VALUES (?, ?, ?, ?, ?)"; + + try (PreparedStatement pstmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + pstmt.setString(1, gameDto.name()); + pstmt.setString(2, gameDto.createdAt()); + pstmt.setString(3, gameDto.updatedAt()); + pstmt.setInt(4, gameDto.turn()); + pstmt.setString(5, gameDto.side()); + + pstmt.executeUpdate(); + + try (ResultSet rs = pstmt.getGeneratedKeys()) { + if (rs.next()) { + generatedId = rs.getInt(1); + } + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return generatedId; + } + + public List findAllGames() { + List gameInfos = new ArrayList<>(); String sql = "SELECT * FROM GameRoom"; try (Connection conn = sqlManager.ensureConnection(); @@ -43,13 +74,14 @@ public List findAllGames() { ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { - GameInfo game = new GameInfo( + gameInfos.add(new GameResponseDto( rs.getInt("id"), rs.getString("name"), - rs.getString("created_date"), - rs.getString("recently_date") - ); - gameInfos.add(game); + rs.getString("created_at"), + rs.getString("updated_at"), + rs.getString("side"), + rs.getInt("turn") + )); } } catch (SQLException e) { e.printStackTrace(); @@ -57,48 +89,29 @@ public List findAllGames() { return gameInfos; } - public int insertGame(String name, String createdDate, String recentlyDate) { - int generatedId = -1; - String sql = "INSERT INTO GameRoom (name, created_date, recently_date) VALUES (?, ?, ?)"; + public void updateGameTurn(Connection connection, int gameId, TurnDto turnDto) { + String sql = "UPDATE GameRoom SET turn = ?, side = ?, updated_at = ? WHERE id = ?"; - try (Connection conn = sqlManager.ensureConnection(); - PreparedStatement pstmt = conn.prepareStatement(sql)) { - - pstmt.setString(1, name); - pstmt.setString(2, createdDate); - pstmt.setString(3, recentlyDate); + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { + pstmt.setInt(1, turnDto.turn()); + pstmt.setString(2, turnDto.side()); + pstmt.setString(3, java.time.LocalDateTime.now().toString()); + pstmt.setInt(4, gameId); pstmt.executeUpdate(); - - if (!conn.getAutoCommit()) conn.commit(); - - try (ResultSet rs = pstmt.getGeneratedKeys()) { - if (rs.next()) { - generatedId = rs.getInt(1); // 첫 번째 컬럼이 생성된 ID - } - } - } catch (SQLException e) { e.printStackTrace(); } - - return generatedId; } - public void removeGame(int id) { - String sql = "DELETE FROM Game WHERE id = ?"; + public void removeGame(Connection connection, int id) { + String sql = "DELETE FROM GameRoom WHERE id = ?"; - try (Connection conn = sqlManager.ensureConnection(); - PreparedStatement pstmt = conn.prepareStatement(sql)) { + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setInt(1, id); - int affectedRows = pstmt.executeUpdate(); - if (affectedRows > 0) { - if (!conn.getAutoCommit()) conn.commit(); - } else { - throw new IllegalStateException("해당 id의 게임이 없습니다."); - } + pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); diff --git a/src/main/java/janggi/dao/Piece.java b/src/main/java/janggi/dao/Piece.java index 3ccecbfe26..3e71330bfc 100644 --- a/src/main/java/janggi/dao/Piece.java +++ b/src/main/java/janggi/dao/Piece.java @@ -63,7 +63,7 @@ public List getAllPieces(int gameId) { return pieces; } - public void updatePiece(Connection conn, int gameId, int x, int y, String type, String side) { + public void updatePiece(Connection connection, int gameId, PieceDto pieceDto) { String sql = """ INSERT INTO Piece (game_id, x, y, piece_type, side) VALUES (?, ?, ?, ?, ?) @@ -73,33 +73,58 @@ ON CONFLICT(game_id, x, y) side = excluded.side """; - try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setInt(1, gameId); - pstmt.setInt(2, x); - pstmt.setInt(3, y); - pstmt.setString(4, type); - pstmt.setString(5, side); + pstmt.setInt(2, pieceDto.x()); + pstmt.setInt(3, pieceDto.y()); + pstmt.setString(4, pieceDto.pieceType()); + pstmt.setString(5, pieceDto.side()); pstmt.executeUpdate(); - if (!conn.getAutoCommit()) conn.commit(); } catch (SQLException e) { e.printStackTrace(); } } - public void deletePiece(Connection conn, int gameId, int x, int y) { + public void updatePieces(Connection connection, int gameId, List pieceDtos) { + String sql = """ + INSERT INTO Piece (game_id, x, y, piece_type, side) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(game_id, x, y) + DO UPDATE SET + piece_type = excluded.piece_type, + side = excluded.side + """; + + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { + for (PieceDto pieceDto : pieceDtos) { + pstmt.setInt(1, gameId); + pstmt.setInt(2, pieceDto.x()); + pstmt.setInt(3, pieceDto.y()); + pstmt.setString(4, pieceDto.pieceType()); + pstmt.setString(5, pieceDto.side()); + + pstmt.addBatch(); + } + + pstmt.executeBatch(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void deletePiece(Connection connection, int gameId, int x, int y) { String sql = "DELETE FROM Piece WHERE game_id = ? AND x = ? AND y = ?"; - try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + try (PreparedStatement pstmt = connection.prepareStatement(sql)) { pstmt.setInt(1, gameId); pstmt.setInt(2, x); pstmt.setInt(3, y); pstmt.executeUpdate(); - if (!conn.getAutoCommit()) conn.commit(); } catch (SQLException e) { e.printStackTrace(); } diff --git a/src/main/java/janggi/db/SQLManager.java b/src/main/java/janggi/db/SQLManager.java index c295aa7257..3174526981 100644 --- a/src/main/java/janggi/db/SQLManager.java +++ b/src/main/java/janggi/db/SQLManager.java @@ -3,58 +3,55 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.sql.Statement; public class SQLManager { private static final String SQLITE_JDBC_DRIVER = "org.sqlite.JDBC"; - private static final boolean OPT_AUTO_COMMIT = false; - private static final int OPT_VALID_TIMEOUT = 500; + private static final int OPT_VALID_TIMEOUT = 500; - private Connection conn = null; - private String driver; - private String url; + private Connection connection = null; + private final String url; public SQLManager(String url) { - this.driver = SQLITE_JDBC_DRIVER; this.url = url; } public Connection createConnection() { try { - Class.forName(this.driver); - this.conn = DriverManager.getConnection(this.url); - this.conn.setAutoCommit(OPT_AUTO_COMMIT); - } catch(ClassNotFoundException | SQLException e) { + Class.forName(SQLITE_JDBC_DRIVER); + this.connection = DriverManager.getConnection(this.url); + this.connection.setAutoCommit(OPT_AUTO_COMMIT); + + try (Statement stmt = connection.createStatement()) { + stmt.execute("PRAGMA foreign_keys = ON;"); + } + } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } - - return this.conn; + return this.connection; } public void closeConnection() { try { - if(this.conn == null) { - return; + if (connection != null && !connection.isClosed()) { + connection.close(); } - this.conn.close(); - } catch(SQLException e) { + } catch (SQLException e) { e.printStackTrace(); + } finally { + connection = null; } } public Connection ensureConnection() { try { - if (this.conn == null || this.conn.isClosed() || !this.conn.isValid(OPT_VALID_TIMEOUT)) { - closeConnection(); + if (connection == null || connection.isClosed() || connection.isValid(OPT_VALID_TIMEOUT)) { createConnection(); } } catch (SQLException e) { e.printStackTrace(); } - return this.conn; - } - - public Connection getConnection() { - return this.conn; + return connection; } } diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index 5751ae7d93..cc00b1f593 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -4,6 +4,7 @@ import janggi.domain.piece.Piece; import janggi.domain.piece.PieceAttribute; import janggi.domain.turn.ChoTurn; +import janggi.domain.turn.HanTurn; import janggi.domain.turn.PlayerTurn; import janggi.domain.turn.TurnState; import janggi.dto.BoardDto; @@ -30,7 +31,7 @@ public List init(Arrangement choArrangement, Arrangement hanArran return getPieceInitInfo(initBoard); } - public List init(List pieceInitInfos) { + public void init(List pieceInitInfos, Side side, int turn) { Map initBoard = BoardInitializer.createBoard(pieceInitInfos); int hanScore = initBoard.values().stream() .filter(piece -> piece.isEqualSide(Side.HAN)) @@ -41,16 +42,13 @@ public List init(List pieceInitInfos) { .mapToInt(Piece::getPieceScore) .sum(); - this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 1); // 현재 side, turn 가져오기 - return getPieceInitInfo(initBoard); + initTurn(new Board(initBoard, hanScore, choScore), side, turn); } - public PieceAttribute move(Position start, Position end) { - Side currentTurn = playerTurn.getCurrentSide(); - + public MoveResult move(Position start, Position end) { TurnState turnState = playerTurn.move(start, end); playerTurn = turnState.playerTurn(); - return turnState.movedPiece(); + return new MoveResult(turnState.turnAttribute(), turnState.movedPiece()); } public boolean isFinished() { @@ -78,4 +76,12 @@ private List getPieceInitInfo(Map board) { board.forEach(((position, piece) -> pieceInitInfos.add(piece.getPieceInitInfo(position)))); return pieceInitInfos; } + + private void initTurn(Board board, Side side, int turn) { + if(side.equals(Side.CHO)) { + this.playerTurn = new ChoTurn(board, turn); + return; + } + this.playerTurn = new HanTurn(board, turn); + } } diff --git a/src/main/java/janggi/domain/GameInfo.java b/src/main/java/janggi/domain/GameInfo.java index 7c07122bc5..0fc9872ab1 100644 --- a/src/main/java/janggi/domain/GameInfo.java +++ b/src/main/java/janggi/domain/GameInfo.java @@ -1,4 +1,4 @@ package janggi.domain; -public record GameInfo(int id, String name, String created, String recent) { +public record GameInfo(int id, String name, String created, String recent, Side side, int turn) { } diff --git a/src/main/java/janggi/domain/MoveResult.java b/src/main/java/janggi/domain/MoveResult.java new file mode 100644 index 0000000000..98da8f7834 --- /dev/null +++ b/src/main/java/janggi/domain/MoveResult.java @@ -0,0 +1,7 @@ +package janggi.domain; + +import janggi.domain.piece.PieceAttribute; +import janggi.domain.turn.TurnAttribute; + +public record MoveResult(TurnAttribute turnAttribute, PieceAttribute pieceAttribute) { +} diff --git a/src/main/java/janggi/domain/turn/ChoTurn.java b/src/main/java/janggi/domain/turn/ChoTurn.java index 55b8383081..79d3853387 100644 --- a/src/main/java/janggi/domain/turn/ChoTurn.java +++ b/src/main/java/janggi/domain/turn/ChoTurn.java @@ -15,14 +15,17 @@ public TurnState move(Position start, Position end) { PieceAttribute pieceAttribute = board.move(start, end, Side.CHO); if(turn == MAX_TURN) { - return new TurnState(new FinishTurn(board, Side.EMPTY, turn), pieceAttribute); + TurnAttribute turnAttribute = new TurnAttribute(Side.EMPTY, turn + 1); + return new TurnState(new FinishTurn(board, Side.EMPTY, turn + 1), turnAttribute, pieceAttribute); } if (board.isEndGame()) { - return new TurnState(new FinishTurn(board, Side.CHO, turn), pieceAttribute); + TurnAttribute turnAttribute = new TurnAttribute(Side.EMPTY, turn + 1); + return new TurnState(new FinishTurn(board, Side.CHO, turn + 1), turnAttribute, pieceAttribute); } - return new TurnState(new HanTurn(board, turn + 1), pieceAttribute); + TurnAttribute turnAttribute = new TurnAttribute(Side.HAN, turn + 1); + return new TurnState(new HanTurn(board, turn + 1), turnAttribute, pieceAttribute); } @Override diff --git a/src/main/java/janggi/domain/turn/HanTurn.java b/src/main/java/janggi/domain/turn/HanTurn.java index 98d8802a91..9de76ee8eb 100644 --- a/src/main/java/janggi/domain/turn/HanTurn.java +++ b/src/main/java/janggi/domain/turn/HanTurn.java @@ -15,13 +15,17 @@ public TurnState move(Position start, Position end) { PieceAttribute pieceAttribute = board.move(start, end, Side.HAN); if(turn == MAX_TURN) { - return new TurnState(new FinishTurn(board, Side.EMPTY, turn), pieceAttribute); + TurnAttribute turnAttribute = new TurnAttribute(Side.EMPTY, turn + 1); + return new TurnState(new FinishTurn(board, Side.EMPTY, turn), turnAttribute, pieceAttribute); } if (board.isEndGame()) { - return new TurnState(new FinishTurn(board, Side.HAN, turn), pieceAttribute); + TurnAttribute turnAttribute = new TurnAttribute(Side.EMPTY, turn + 1); + return new TurnState(new FinishTurn(board, Side.HAN, turn), turnAttribute, pieceAttribute); } - return new TurnState(new ChoTurn(board, turn + 1), pieceAttribute); + + TurnAttribute turnAttribute = new TurnAttribute(Side.HAN, turn + 1); + return new TurnState(new ChoTurn(board, turn + 1), turnAttribute, pieceAttribute); } @Override diff --git a/src/main/java/janggi/domain/turn/TurnAttribute.java b/src/main/java/janggi/domain/turn/TurnAttribute.java new file mode 100644 index 0000000000..63f7c61b26 --- /dev/null +++ b/src/main/java/janggi/domain/turn/TurnAttribute.java @@ -0,0 +1,6 @@ +package janggi.domain.turn; + +import janggi.domain.Side; + +public record TurnAttribute(Side side, int turn) { +} diff --git a/src/main/java/janggi/domain/turn/TurnState.java b/src/main/java/janggi/domain/turn/TurnState.java index 6be102cc66..0e14505494 100644 --- a/src/main/java/janggi/domain/turn/TurnState.java +++ b/src/main/java/janggi/domain/turn/TurnState.java @@ -2,5 +2,5 @@ import janggi.domain.piece.PieceAttribute; -public record TurnState(PlayerTurn playerTurn, PieceAttribute movedPiece) { +public record TurnState(PlayerTurn playerTurn, TurnAttribute turnAttribute, PieceAttribute movedPiece) { } diff --git a/src/main/java/janggi/dto/GameDto.java b/src/main/java/janggi/dto/GameDto.java new file mode 100644 index 0000000000..8b2baa2458 --- /dev/null +++ b/src/main/java/janggi/dto/GameDto.java @@ -0,0 +1,4 @@ +package janggi.dto; + +public record GameDto(String name, String createdAt, String updatedAt, String side, int turn) { +} diff --git a/src/main/java/janggi/dto/GameResponseDto.java b/src/main/java/janggi/dto/GameResponseDto.java new file mode 100644 index 0000000000..8712a57f0c --- /dev/null +++ b/src/main/java/janggi/dto/GameResponseDto.java @@ -0,0 +1,4 @@ +package janggi.dto; + +public record GameResponseDto(int id, String name, String createdAt, String updatedAt, String side, int turn) { +} diff --git a/src/main/java/janggi/dto/PiecePositionDto.java b/src/main/java/janggi/dto/PiecePositionDto.java new file mode 100644 index 0000000000..ceea459487 --- /dev/null +++ b/src/main/java/janggi/dto/PiecePositionDto.java @@ -0,0 +1,4 @@ +package janggi.dto; + +public record PiecePositionDto(int x, int y) { +} diff --git a/src/main/java/janggi/dto/TurnDto.java b/src/main/java/janggi/dto/TurnDto.java new file mode 100644 index 0000000000..01447c3e6c --- /dev/null +++ b/src/main/java/janggi/dto/TurnDto.java @@ -0,0 +1,4 @@ +package janggi.dto; + +public record TurnDto(String side, int turn) { +} From b8c0b002355a4723d969f3bbd2ecbe5ec9005998 Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 22:49:18 +0900 Subject: [PATCH 22/29] =?UTF-8?q?feat:=20main=EB=AC=B8=20=EB=A7=88?= =?UTF-8?q?=EC=A7=80=EB=A7=89=EC=97=90=20connection=20close=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Application.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 2f52e6b627..831690dd61 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -21,5 +21,7 @@ public static void main(String[] args) { Runner runner = new Runner(janggiService, game); runner.run(); + + sqlManager.closeConnection(); } } From 346c19b53a92eb7ebb54029ef4842a426725f01f Mon Sep 17 00:00:00 2001 From: armd482 Date: Thu, 2 Apr 2026 23:47:21 +0900 Subject: [PATCH 23/29] =?UTF-8?q?fix:=20=EC=A7=84=EC=98=81=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++++ janggi.db | 0 src/main/java/janggi/JanggiService.java | 9 +++---- src/main/java/janggi/Runner.java | 24 +++++++++++++----- src/main/java/janggi/dao/GameRoom.java | 8 ++---- src/main/java/janggi/domain/Side.java | 2 +- .../java/janggi/domain/piece/PieceType.java | 2 +- .../janggi/initializer/BoardInitializer.java | 2 +- src/main/resources/game_room.db | Bin 12288 -> 0 bytes src/main/resources/janggi.db | Bin 20480 -> 20480 bytes 10 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 janggi.db delete mode 100644 src/main/resources/game_room.db diff --git a/build.gradle b/build.gradle index 56d87204a1..baaafafa51 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'application' } version '1.0-SNAPSHOT' @@ -18,6 +19,10 @@ dependencies { testImplementation('org.assertj:assertj-core') } +application { + mainClass = 'janggi.Application' +} + java { toolchain { languageVersion = JavaLanguageVersion.of(21) diff --git a/janggi.db b/janggi.db new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/java/janggi/JanggiService.java b/src/main/java/janggi/JanggiService.java index f1afcfcae0..8611a9a2ae 100644 --- a/src/main/java/janggi/JanggiService.java +++ b/src/main/java/janggi/JanggiService.java @@ -27,7 +27,6 @@ public JanggiService(SQLManager sqlManager, GameRoom gameRoom, Piece piece) { } public List getEntireGame() { - gameRoom.initTable(); return gameRoom.findAllGames(); } @@ -67,15 +66,13 @@ public void removeGame(int id) { } } - public List getPieceInitInfos(int gameId) { - return piece.getAllPieces(gameId).stream() - .map(pieceDto -> new PieceInitInfo(new Position(pieceDto.x(), pieceDto.y()), Side.from(pieceDto.side()), PieceType.from(pieceDto.pieceType()))) - .toList(); + public List getPieceInitInfos(int gameId) { + return piece.getAllPieces(gameId); } public void movePiece(int gameId, Position start, Position end, Side side, PieceType pieceType, TurnDto turnDto) { Connection connection = sqlManager.ensureConnection(); - PieceDto pieceDto = new PieceDto(end.getX(), end.getY(), side.name(), pieceType.name()); + PieceDto pieceDto = new PieceDto(end.getX(), end.getY(), pieceType.getName(), side.getName()); try { gameRoom.updateGameTurn(connection, gameId, turnDto); piece.deletePiece(connection, gameId, start.getX(), start.getY()); diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index b68f350deb..b1206d0daa 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -9,6 +9,7 @@ import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.SideScore; +import janggi.domain.piece.PieceType; import janggi.dto.GameDto; import janggi.dto.PieceDto; import janggi.dto.TurnDto; @@ -38,8 +39,13 @@ public Runner(JanggiService janggiService, Game game) { } public void run() { - int gameId = manageGameRoom(); - playGame(gameId); + try { + int gameId = manageGameRoom(); + playGame(gameId); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } public int manageGameRoom() { @@ -62,7 +68,9 @@ public int manageGameRoom() { throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); } - List pieceInitInfos = janggiService.getPieceInitInfos(selectedGame.id()); + List pieceInitInfos = janggiService.getPieceInitInfos(selectedGame.id()).stream().map(pieceDto -> new PieceInitInfo(new Position(pieceDto.x(), pieceDto.y()), Side.from(pieceDto.side()), + PieceType.from(pieceDto.pieceType()))).toList(); + game.init(pieceInitInfos, selectedGame.side(), selectedGame.turn()); return selectedGame.id(); } @@ -79,10 +87,10 @@ private int initArrangeGame() { Arrangement choArrangement = Arrangement.from(choArrangementInput); List pieceDtos = game.init(choArrangement, hanArrangement).stream() - .map(pieceInitInfo -> new PieceDto(pieceInitInfo.position().getX(), pieceInitInfo.position().getY(), pieceInitInfo.pieceType().name(), pieceInitInfo.side().name())) + .map(pieceInitInfo -> new PieceDto(pieceInitInfo.position().getX(), pieceInitInfo.position().getY(), pieceInitInfo.pieceType().getName(), pieceInitInfo.side().getName())) .toList(); - return janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.name(), 1), pieceDtos); + return janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.getName(), 1), pieceDtos); } private void playGame(int gameId) { @@ -136,12 +144,14 @@ private boolean executeTurn(int gameId) { OutputView.printLine(); MoveResult moveResult = game.move(startPosition, endPosition); - TurnDto turnDto = new TurnDto(moveResult.turnAttribute().side().name(), moveResult.turnAttribute().turn()); + TurnDto turnDto = new TurnDto(moveResult.turnAttribute().side().getName(), moveResult.turnAttribute().turn()); janggiService.movePiece(gameId, startPosition, endPosition, moveResult.pieceAttribute().side(), moveResult.pieceAttribute().pieceType(), turnDto); printCurrentScore(); - return game.isFinished(); + + System.out.println(game.isFinished()); + return !game.isFinished(); } private boolean consentEndGame(int gameId) { diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java index 888d651056..7ca8cecdf3 100644 --- a/src/main/java/janggi/dao/GameRoom.java +++ b/src/main/java/janggi/dao/GameRoom.java @@ -38,7 +38,7 @@ CREATE TABLE IF NOT EXISTS GameRoom ( } } - public int insertGame(Connection connection, GameDto gameDto) { + public int insertGame(Connection connection, GameDto gameDto) throws SQLException { int generatedId = -1; String sql = "INSERT INTO GameRoom (name, created_at, updated_at, turn, side) VALUES (?, ?, ?, ?, ?)"; @@ -57,9 +57,6 @@ public int insertGame(Connection connection, GameDto gameDto) { generatedId = rs.getInt(1); } } - - } catch (SQLException e) { - e.printStackTrace(); } return generatedId; @@ -67,8 +64,7 @@ public int insertGame(Connection connection, GameDto gameDto) { public List findAllGames() { List gameInfos = new ArrayList<>(); - String sql = "SELECT * FROM GameRoom"; - + String sql = "SELECT id, name, created_at, updated_at, side, turn FROM GameRoom"; try (Connection conn = sqlManager.ensureConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { diff --git a/src/main/java/janggi/domain/Side.java b/src/main/java/janggi/domain/Side.java index bbb65246cc..0468f244fe 100644 --- a/src/main/java/janggi/domain/Side.java +++ b/src/main/java/janggi/domain/Side.java @@ -38,7 +38,7 @@ public Side getOppositeSide() { public static Side from(String name) { return Arrays.stream(Side.values()) - .filter(side -> side.name.equals(name)) + .filter(side -> side.getName().equals(name)) .findFirst() .orElseThrow(() -> new IllegalArgumentException(INVALID_SIDE_NAME)); } diff --git a/src/main/java/janggi/domain/piece/PieceType.java b/src/main/java/janggi/domain/piece/PieceType.java index 70d65eee6b..a94a2ce922 100644 --- a/src/main/java/janggi/domain/piece/PieceType.java +++ b/src/main/java/janggi/domain/piece/PieceType.java @@ -32,7 +32,7 @@ public int getScore() { public static PieceType from(String name) { return Arrays.stream(PieceType.values()) - .filter(pieceType -> pieceType.name.equals(name)) + .filter(pieceType -> pieceType.getName().equals(name)) .findFirst() .orElseThrow(() -> new IllegalArgumentException(INVALID_PIECE_TYPE_NAME)); } diff --git a/src/main/java/janggi/initializer/BoardInitializer.java b/src/main/java/janggi/initializer/BoardInitializer.java index b55a9c8700..7ecd8ee879 100644 --- a/src/main/java/janggi/initializer/BoardInitializer.java +++ b/src/main/java/janggi/initializer/BoardInitializer.java @@ -88,7 +88,7 @@ public static Map createBoard(Arrangement choArrangement, Arran public static Map createBoard(List pieceInitInfos) { Map board = initBoard(); - pieceInitInfos.forEach(pieceInitInfo -> initBoard().put(pieceInitInfo.position(), pieceMap.get(pieceInitInfo.pieceType()).apply(pieceInitInfo.side()))); + pieceInitInfos.forEach(pieceInitInfo -> board.put(pieceInitInfo.position(), pieceMap.get(pieceInitInfo.pieceType()).apply(pieceInitInfo.side()))); return board; } diff --git a/src/main/resources/game_room.db b/src/main/resources/game_room.db deleted file mode 100644 index 0263666ab28a9ce9a5792381803786bf0bf88a97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&&q~8E90%}Z-Snc=+fKuHk&SIK+jY#ItWw6X>t^lHfiUV8VX!}Ls|UR)cp2V& z1s}p^@LAl&ComNX+bD=9LB2nlr2XZW1U|Pw&rYqL&)M~8GVpw+k}X0hIb@6w5*JMp zEetANf?OEE_fHWgd-pe+qADgQhzU9Nt>Zf$DzfbUm3$(Z}uYJu4#b zj7EcSj4kaFW?8z|WmVfXE2hI5PPJw@Ep}qIn9+3WRa>m7nYNo_!70o&FI;BVMQd7C zj&&y7^Le-375(bmgm?JR@83mCW`eu9BwrHwU49V*0s;_#00bZa0SG_<0uX=z1Rwx` zUkYeag6yd3{OS2)_Bx-vsY*d9aD`2W@>poe`Zn9jmk*T01%|qoq!rR;N}6oK~l;RQuR-_xC%9mHyGcv~x1z{q1MJ z``h1s_hgrQv~f;Ndska#`Pz=vEnS&%k|wDXxgwJxgt+RDt^VMTp#lpV>NEA3_+p_; zMs8Wh#S6sZn~5wCC-`P@$Hl}6PaH@bNE}EUNE}EUNE}EUNE}EUNF4aTbYN!>(=8|{ zNbT~wT9&SAo88{FO#jQOY{*tLWiw3`(`vGr9M2T7jQUy8vbwFgy)`qlt|?ocZOGKs zH)ZPP*3^{bdN*B!Z@vg$7tPq*wRv4zrYXBXWx60+XM5`x^4SeDYbzQSX0FUG%oIUE zC7Denna#zzjOq0a*_qXK7YK?o4cY10hHPDBwlPz!R?)C_?dqcT*5XWkU8X8qlU2*D ztZ1yPsLHb9V#_JGydXu|*R-~6>Ri7{eLZb%+0eB%*KW@F(L6f$5q&xJqK9L);S>xX zo?4i55{7iPt>4hLW?9?kn&t)Wey+1tyx-T++Sw~w-S6ged`bPc*TeD$Z>woF(+R(A)B0K({ zd&-KrvM(iHAnKpwCvhNgAaNjZAaNjZAaNjZAaNjZAaNjZAaNjZ;LGShh2fHZ6j6vWtp<_Nn^%N8a@6zYSO4|{K&E86DO37H_ql5fdZQ5MMp>4=ZTyW$P;6Y;D#BJL4;#741F%oo$e zSTRI&7Z(2ue~15ypWx5%!+byA#XI>@-pHr%(R={U=SJ}7;P=66!H;}5crdsv*b%G^ zmIU>|lwd^AH}L(x`)B<({h#?S_>cPs{eAupe~o{QU+YixNBaZ)?(Ads9y`rWvgg+Z|$QTL#`&)wp#aOb&Mx6JM53g;8&ed}+| z3Fnw|$l2#?bK0GGPNh@o6gtfQ#Qvjw%06Kqvmdf=wRhO7>_v99J>DK_7uc@#2kVse zvUSWlWbL)KS}U#jR+Uw*Y)*a>2fpeKh;WRPN~QAh4dyjgpLu3aIxLTX>}5FC$TN5C zKJ&m{5tgCGt*yvuMx(}zHKnL=8ntE=Y7Ee%!;yfP4M>I~6r#~=aiS7TWaScxa5w@p zSD^@pAuw{~@K8ia)Ea2z3{@Tvi<}gV)}-3eYte8hwAB)G?IF-s%gMC|LtB-u+Uamm zG=VD0rg5DEBT0nf#SB1^2Z)FLA&;s&mnVY~H97|L!s^mtPaK3Bn+^+5g$1U=9;m{tO^4l4g*B$bOHhTI zE5iH;MW)-;bJZ@$u$uzW)?$t6Fb@-*uON-+{B#l#=2pLC-E=B+K!iLJso5+#t3Kya z0HZ8iun2wBxS(OEIlmAJ$UC3f!(n!AmOmCv* z{PYH9!nLNu6spiS9ukG3y|r|r9jJ(>{~8i>QEZQh8sZtbn&q}Ah1v{sC%qMhnDN55 zzz|a{GqQ9uDsV~ZO-OJ7Vk3-k)iy-dT#pJYKD_~m8_(Q@8Z2LQVivVgQI-x=;BwOI zF-tu2Iv8WB1*&SrTFjw#EqeUcsCKmAIK^sA5l_1cho}vTro9doxEcJ~D2Z|r>S_mp zfu5yTB8gYG0!i%JawJ%Lx($efc|>bO)I^5*<%zsshKe}vQp|$or&~}H&$I+J@uHhi z6VH7OYH*S1#i+qOlUFGcd503d$e~era7W06QB(GcZwS5s`t|)F=R-f1!RJB0z8mCR z=*RrBNrU)a5RD3`3b1P4015P+z~?|y-w1p*H1&NDjEM&4s^8R)wpsMRff z69hH7rSE~@O5M`8fSeU2mA&FSK+KG~6~H@T1_*GAf@(cppZ;<>bmQ&>S>3Hqepv%L5{V^DImwEMP3dHEJt1j3fv?)85CHN zoCFHoCO$C&%1V90iwUZ!HVG?|;~_yj=QsfI6k`EkMsaoY?5XFT!PQ+J3G(vcRF`F1 zAD`)bH1z8eU6w+BT=FOl;?taugaP_2=Oc6zPI6fS0pY+)+2Sax};~)U&5g!=kcpf31&?ZJyD3IJIRz z=*JbvO95cXyigB-BU|=`ek@q_(I7ssWp7Ym&Ab<6(8r?e3H`VPSpXU0DSPMva8~o~ zFkT`?#!3zohH z@d3?Q1k{77&u2-YKThdE2CP}Spor&iKoKv~21Pun1qxgjHzS~qUH!`;4H$qsB~l6~ z<517&56CfEMfgenG(W^|;oJEt-pUvACV5W&R{mT*FOSFra-ZC#-U-Z>Q)G$kEj{s9 zaaOz|ej}b12gP2oMZFD}Cu&8Nm@Lx#pZp9z&0pk?@dNxu-o=Y~0k?yX)EkCV@+Emx z9+I8v?Lv!OAgkqAIZ)<_e~Dj;lj22jTs$WBiyOpx(JacvFwtN16s~%=@HT&qALo^P zl6s$z;Uf4ncwfHD7O)9yAQSW)eTSZ=uhJg}zYAUpo(k>@ZVEbs_MkbK8w?Ke{D1iG z`KSCB{b&6r{KI~Szu2Gdm-@YZi=AU1u(#Qh>~6M`tzxxo1nbF6^)BIdx|O!k`r!58 zx!{rD_F#KZ6HE&x1S5k!f$M+dzop()-0R=w@AkL(EBrbBWPh+P*{|6z*h}mmmEWClA}l+@-$L|JcZOIPg0eP8iqrjKxmQ2F*M2dFf_<_c@1Go%E))rAgU^$ zzLKL-r#z$el=S@+~!MWR6YlKxmQMF*M0-7#ieO zkWz9BK#P)n7JyGRX z%d{r|u*O4Rj)TA$t0i=d1{N)cz$=5m9Swm~ssoRX0%DH@;*9`el>l*v12KmIaf*Q$ zML_JKdU9(B0BbM=<{$`+fm-4X(7>eqA@DK~xcwk-F4cic3xU{ufvEpBoO@nOt2Yq0 z7Z9^25T^i$(F2IxT~BUa0>H|L!0ZNrk*6hYS_6Yh2s{CS%OP+A9XQknVlyBf1!8$X aTo;Jx0C8*}hNS~WP3W=>^|(YQ^uGbUG7irG delta 119 zcmZozz}T>Wae_1>(?l6(Q6>hxssdiV4-724YZ&;w_l Date: Fri, 3 Apr 2026 09:35:13 +0900 Subject: [PATCH 24/29] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Application.java | 4 +- src/main/java/janggi/JanggiService.java | 58 +++++-------------- src/main/java/janggi/dao/GameRoom.java | 4 +- src/main/java/janggi/db/SQLManager.java | 2 +- .../java/janggi/db/TransactionManager.java | 45 ++++++++++++++ 5 files changed, 65 insertions(+), 48 deletions(-) create mode 100644 src/main/java/janggi/db/TransactionManager.java diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 831690dd61..5fefe951e6 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -3,11 +3,13 @@ import janggi.dao.GameRoom; import janggi.dao.Piece; import janggi.db.SQLManager; +import janggi.db.TransactionManager; import janggi.domain.Game; public class Application { public static void main(String[] args) { SQLManager sqlManager = new SQLManager("jdbc:sqlite:src/main/resources/janggi.db"); + TransactionManager transactionManager = new TransactionManager(sqlManager); GameRoom gameRoom = new GameRoom(sqlManager); gameRoom.initTable(); @@ -15,7 +17,7 @@ public static void main(String[] args) { Piece piece = new Piece(sqlManager); piece.initTable(); - JanggiService janggiService = new JanggiService(sqlManager, gameRoom, piece); + JanggiService janggiService = new JanggiService(transactionManager, gameRoom, piece); Game game = new Game(); diff --git a/src/main/java/janggi/JanggiService.java b/src/main/java/janggi/JanggiService.java index 8611a9a2ae..9ff4ae9268 100644 --- a/src/main/java/janggi/JanggiService.java +++ b/src/main/java/janggi/JanggiService.java @@ -2,8 +2,7 @@ import janggi.dao.GameRoom; import janggi.dao.Piece; -import janggi.db.SQLManager; -import janggi.domain.PieceInitInfo; +import janggi.db.TransactionManager; import janggi.domain.Position; import janggi.domain.Side; import janggi.domain.piece.PieceType; @@ -11,17 +10,15 @@ import janggi.dto.GameResponseDto; import janggi.dto.PieceDto; import janggi.dto.TurnDto; -import java.sql.Connection; -import java.sql.SQLException; import java.util.List; public class JanggiService { - private final SQLManager sqlManager; + private final TransactionManager transactionManager; private final GameRoom gameRoom; private final Piece piece; - public JanggiService(SQLManager sqlManager, GameRoom gameRoom, Piece piece) { - this.sqlManager = sqlManager; + public JanggiService(TransactionManager transactionManager, GameRoom gameRoom, Piece piece) { + this.transactionManager = transactionManager; this.gameRoom = gameRoom; this.piece = piece; } @@ -31,61 +28,32 @@ public List getEntireGame() { } public int addGameData(GameDto gameDto, List pieceDtos) { - Connection connection = sqlManager.ensureConnection(); - - try { + return transactionManager.sync((connection) -> { int gameId = gameRoom.insertGame(connection, gameDto); piece.updatePieces(connection, gameId, pieceDtos); - connection.commit(); return gameId; - } catch (Exception e) { - try { - if (connection != null) { - connection.rollback(); - } - } catch (SQLException rollbackEx) { - rollbackEx.printStackTrace(); - } - throw new RuntimeException("게임 데이터 생성 중 오류가 발생하여 롤백되었습니다.", e); - } + }); } public void removeGame(int id) { - Connection connection = sqlManager.ensureConnection(); - try { + transactionManager.sync((connection) -> { gameRoom.removeGame(connection, id); - connection.commit(); - } catch (SQLException e) { - try { - connection.rollback(); - } catch (SQLException rollbackEx) { - rollbackEx.printStackTrace(); - } - throw new RuntimeException("게임 데이터 삭제 중 오류가 발생하여 롤백되었습니다.", e); - } + }); } public List getPieceInitInfos(int gameId) { return piece.getAllPieces(gameId); } + public void movePiece(int gameId, Position start, Position end, Side side, PieceType pieceType, TurnDto turnDto) { - Connection connection = sqlManager.ensureConnection(); - PieceDto pieceDto = new PieceDto(end.getX(), end.getY(), pieceType.getName(), side.getName()); - try { + transactionManager.sync(connection -> { gameRoom.updateGameTurn(connection, gameId, turnDto); piece.deletePiece(connection, gameId, start.getX(), start.getY()); - piece.updatePiece(connection, gameId, pieceDto); - connection.commit(); - } catch (SQLException e) { - try { - connection.rollback(); - } catch (SQLException rollbackEx) { - rollbackEx.printStackTrace(); - } - throw new RuntimeException("기물 이동 중 오류가 발생하여 롤백되었습니다.", e); - } + PieceDto pieceDto = new PieceDto(end.getX(), end.getY(), pieceType.getName(), side.getName()); + piece.updatePiece(connection, gameId, pieceDto); + }); } } diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java index 7ca8cecdf3..ad9b21a2a4 100644 --- a/src/main/java/janggi/dao/GameRoom.java +++ b/src/main/java/janggi/dao/GameRoom.java @@ -38,7 +38,7 @@ CREATE TABLE IF NOT EXISTS GameRoom ( } } - public int insertGame(Connection connection, GameDto gameDto) throws SQLException { + public int insertGame(Connection connection, GameDto gameDto){ int generatedId = -1; String sql = "INSERT INTO GameRoom (name, created_at, updated_at, turn, side) VALUES (?, ?, ?, ?, ?)"; @@ -57,6 +57,8 @@ public int insertGame(Connection connection, GameDto gameDto) throws SQLExceptio generatedId = rs.getInt(1); } } + } catch (SQLException e) { + e.printStackTrace(); } return generatedId; diff --git a/src/main/java/janggi/db/SQLManager.java b/src/main/java/janggi/db/SQLManager.java index 3174526981..0b260171e5 100644 --- a/src/main/java/janggi/db/SQLManager.java +++ b/src/main/java/janggi/db/SQLManager.java @@ -46,7 +46,7 @@ public void closeConnection() { public Connection ensureConnection() { try { - if (connection == null || connection.isClosed() || connection.isValid(OPT_VALID_TIMEOUT)) { + if (connection == null || connection.isClosed() || !connection.isValid(OPT_VALID_TIMEOUT)) { createConnection(); } } catch (SQLException e) { diff --git a/src/main/java/janggi/db/TransactionManager.java b/src/main/java/janggi/db/TransactionManager.java new file mode 100644 index 0000000000..c1b0268e0d --- /dev/null +++ b/src/main/java/janggi/db/TransactionManager.java @@ -0,0 +1,45 @@ +package janggi.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.function.Consumer; +import java.util.function.Function; + +public class TransactionManager { + private final SQLManager sqlManager; + + public TransactionManager(SQLManager sqlManager) { + this.sqlManager = sqlManager; + } + + public T sync(Function function) { + Connection conn = sqlManager.ensureConnection(); + try { + T result = function.apply(conn); + conn.commit(); + return result; + } catch (Exception e) { + rollback(conn); + throw new RuntimeException("Transaction failed", e); + } + } + + public void sync(Consumer consumer) { + Connection conn = sqlManager.ensureConnection(); + try { + consumer.accept(conn); + conn.commit(); + } catch (Exception e) { + rollback(conn); + throw new RuntimeException("트랜잭션 실행 중 오류 발생", e); + } + } + + private void rollback(Connection conn) { + try { + if (conn != null) conn.rollback(); + } catch (SQLException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file From 00ed4b0fc04804b431b1fed38cc64253977d97ac Mon Sep 17 00:00:00 2001 From: armd482 Date: Fri, 3 Apr 2026 13:22:08 +0900 Subject: [PATCH 25/29] =?UTF-8?q?refactor:=20runner=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9E=98=EB=AA=BB=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B0=98=EB=B3=B5=20=EC=8B=A4=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Application.java | 4 +- src/main/java/janggi/Runner.java | 104 +++++++++++++------------- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/main/java/janggi/Application.java b/src/main/java/janggi/Application.java index 5fefe951e6..d1f73320db 100644 --- a/src/main/java/janggi/Application.java +++ b/src/main/java/janggi/Application.java @@ -22,7 +22,9 @@ public static void main(String[] args) { Game game = new Game(); Runner runner = new Runner(janggiService, game); - runner.run(); + + int gameId = runner.initBoard(); + runner.runJanggi(gameId); sqlManager.closeConnection(); } diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index b1206d0daa..53ab42a20f 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -19,6 +19,7 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,44 +39,48 @@ public Runner(JanggiService janggiService, Game game) { this.game = game; } - public void run() { - try { - int gameId = manageGameRoom(); - playGame(gameId); - } catch (Exception e) { - System.out.println(e.getMessage()); - } + public int initBoard() { + Optional previousBoard = getPreviousBoard(); + return previousBoard.orElseGet(this::createNewBoard); + } + public void runJanggi(int gameId) { + while (playTurnGame(gameId)) { + } + endGame(gameId); } - public int manageGameRoom() { + private Optional getPreviousBoard() { List gameInfos = janggiService.getEntireGame().stream() .map(gameResponseDto -> new GameInfo(gameResponseDto.id(), gameResponseDto.name(), gameResponseDto.createdAt(), gameResponseDto.updatedAt(), Side.from(gameResponseDto.side()), gameResponseDto.turn())) .toList(); - if(gameInfos.isEmpty()) { - return initArrangeGame(); - } - OutputView.printGameRoom(gameInfos); - Optional input = InputView.askLoadGame(); - if(input.isEmpty()) { - return initArrangeGame(); + if(gameInfos.isEmpty()) { + return Optional.empty(); } - GameInfo selectedGame = gameInfos.get(input.get() - 1); - - if(selectedGame == null) { - throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); - } + GameInfo selectedGame = askUntilValid(() -> getSelectedGame(gameInfos)); List pieceInitInfos = janggiService.getPieceInitInfos(selectedGame.id()).stream().map(pieceDto -> new PieceInitInfo(new Position(pieceDto.x(), pieceDto.y()), Side.from(pieceDto.side()), PieceType.from(pieceDto.pieceType()))).toList(); game.init(pieceInitInfos, selectedGame.side(), selectedGame.turn()); - return selectedGame.id(); + return Optional.of(selectedGame.id()); } - private int initArrangeGame() { + private GameInfo getSelectedGame(List gameInfos) { + OutputView.printGameRoom(gameInfos); + Optional input = InputView.askLoadGame(); + + if(input.isEmpty() || (input.get() - 1 > gameInfos.size()) && (input.get() < 0)) { + throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); + } + + GameInfo selectedGame = gameInfos.get(input.get() - 1); + return selectedGame; + } + + private int createNewBoard() { GameName gameName = new GameName(InputView.askGameName()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formattedNow = LocalDateTime.now().format(formatter); @@ -93,25 +98,12 @@ private int initArrangeGame() { return janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.getName(), 1), pieceDtos); } - private void playGame(int gameId) { - while (playTurnGame(gameId)) { - } - - janggiService.removeGame(gameId); - - Side winnerSide = game.getWinnerSide(); - - if(winnerSide == null) { - printCurrentScore(); - printScoreWinner(); - return; - } - OutputView.printWinner(winnerSide); - } - private boolean playTurnGame(int gameId) { try { - printCurrentStatus(); + OutputView.printLine(); + OutputView.printBoard(game.getCurrentBoardDto()); + OutputView.printTurn(game.getCurrentSide()); + return executeTurn(gameId); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); @@ -123,12 +115,6 @@ private boolean playTurnGame(int gameId) { } } - private void printCurrentStatus() { - OutputView.printLine(); - OutputView.printBoard(game.getCurrentBoardDto()); - OutputView.printTurn(game.getCurrentSide()); - } - private boolean executeTurn(int gameId) { Optional> startPositionInput = InputView.askStartPosition(); @@ -148,12 +134,23 @@ private boolean executeTurn(int gameId) { janggiService.movePiece(gameId, startPosition, endPosition, moveResult.pieceAttribute().side(), moveResult.pieceAttribute().pieceType(), turnDto); - printCurrentScore(); - - System.out.println(game.isFinished()); + OutputView.printScore(game.getCurrentSideScore()); return !game.isFinished(); } + private void endGame(int gameId) { + janggiService.removeGame(gameId); + + Side winnerSide = game.getWinnerSide(); + + if(winnerSide == null) { + OutputView.printScore(game.getCurrentSideScore()); + printScoreWinner(); + return; + } + OutputView.printWinner(winnerSide); + } + private boolean consentEndGame(int gameId) { String consentInput = InputView.consentEnd(); @@ -173,8 +170,13 @@ private void printScoreWinner() { OutputView.printWinner(Side.HAN); } - - private void printCurrentScore() { - OutputView.printScore(game.getCurrentSideScore()); + private static T askUntilValid(Supplier supplier) { + while (true) { + try { + return supplier.get(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } } } From 2ae0b215862c29707c6e48f68626155fc21fd646 Mon Sep 17 00:00:00 2001 From: armd482 Date: Fri, 3 Apr 2026 13:42:07 +0900 Subject: [PATCH 26/29] =?UTF-8?q?refactor:=20=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20index=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/Runner.java | 25 +++++++++---------- src/main/java/janggi/domain/GameInfos.java | 28 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 src/main/java/janggi/domain/GameInfos.java diff --git a/src/main/java/janggi/Runner.java b/src/main/java/janggi/Runner.java index 53ab42a20f..4acfade4fb 100644 --- a/src/main/java/janggi/Runner.java +++ b/src/main/java/janggi/Runner.java @@ -3,6 +3,7 @@ import janggi.domain.Arrangement; import janggi.domain.Game; import janggi.domain.GameInfo; +import janggi.domain.GameInfos; import janggi.domain.GameName; import janggi.domain.MoveResult; import janggi.domain.PieceInitInfo; @@ -41,7 +42,7 @@ public Runner(JanggiService janggiService, Game game) { public int initBoard() { Optional previousBoard = getPreviousBoard(); - return previousBoard.orElseGet(this::createNewBoard); + return previousBoard.orElseGet(() -> createNewBoard().id()); } public void runJanggi(int gameId) { @@ -51,9 +52,10 @@ public void runJanggi(int gameId) { } private Optional getPreviousBoard() { - List gameInfos = janggiService.getEntireGame().stream() + GameInfos gameInfos = new GameInfos(janggiService.getEntireGame().stream() .map(gameResponseDto -> new GameInfo(gameResponseDto.id(), gameResponseDto.name(), gameResponseDto.createdAt(), gameResponseDto.updatedAt(), Side.from(gameResponseDto.side()), gameResponseDto.turn())) - .toList(); + .toList()); + if(gameInfos.isEmpty()) { return Optional.empty(); @@ -68,19 +70,17 @@ private Optional getPreviousBoard() { return Optional.of(selectedGame.id()); } - private GameInfo getSelectedGame(List gameInfos) { - OutputView.printGameRoom(gameInfos); + private GameInfo getSelectedGame(GameInfos gameInfos) { + OutputView.printGameRoom(gameInfos.getGameInfos()); Optional input = InputView.askLoadGame(); - if(input.isEmpty() || (input.get() - 1 > gameInfos.size()) && (input.get() < 0)) { - throw new IllegalArgumentException("잘못된 값을 입력하셨습니다."); + if(input.isEmpty()) { + return createNewBoard(); } - - GameInfo selectedGame = gameInfos.get(input.get() - 1); - return selectedGame; + return gameInfos.getGameInfo(input.get()); } - private int createNewBoard() { + private GameInfo createNewBoard() { GameName gameName = new GameName(InputView.askGameName()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formattedNow = LocalDateTime.now().format(formatter); @@ -95,7 +95,8 @@ private int createNewBoard() { .map(pieceInitInfo -> new PieceDto(pieceInitInfo.position().getX(), pieceInitInfo.position().getY(), pieceInitInfo.pieceType().getName(), pieceInitInfo.side().getName())) .toList(); - return janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.getName(), 1), pieceDtos); + int id = janggiService.addGameData(new GameDto(gameName.name(), formattedNow, formattedNow, Side.CHO.getName(), 1), pieceDtos); + return new GameInfo(id, gameName.name(), formattedNow, formattedNow, Side.CHO, 1); } private boolean playTurnGame(int gameId) { diff --git a/src/main/java/janggi/domain/GameInfos.java b/src/main/java/janggi/domain/GameInfos.java new file mode 100644 index 0000000000..5e682cff04 --- /dev/null +++ b/src/main/java/janggi/domain/GameInfos.java @@ -0,0 +1,28 @@ +package janggi.domain; + +import java.util.List; + +public class GameInfos { + private static final String INVALID_NUMBER = "존재하지 않는 게임 번호입니다."; + + private List gameInfos; + + public GameInfos(List gameInfos) { + this.gameInfos = gameInfos; + } + + public GameInfo getGameInfo(int index) { + if(index < 0 || index > gameInfos.size() - 1) { + throw new IllegalArgumentException(INVALID_NUMBER); + } + return gameInfos.get(index); + } + + public boolean isEmpty() { + return gameInfos.isEmpty(); + } + + public List getGameInfos() { + return List.copyOf(gameInfos); + } +} From 4fdd10b40912954f630ddcb3f50d82cc2824ab59 Mon Sep 17 00:00:00 2001 From: armd482 Date: Fri, 3 Apr 2026 13:52:02 +0900 Subject: [PATCH 27/29] =?UTF-8?q?docs:=20readme=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ src/main/java/janggi/dao/GameRoom.java | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a1f3141ab1..63934eee49 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ - 현재 턴의 진영만 자신의 기물을 이동할 수 있다. - 이동이 완료되면 턴이 상대 진영으로 넘어간다. - 게임 상태는 `ChoTurn`, `HanTurn`, `Finish`로 구분된다. +- 200턴이 넘어가면 종료된다. ### 초기 배치 - 초와 한은 각각 마상 배치를 입력할 수 있다. @@ -67,6 +68,14 @@ - 게임 종료 시 최종 점수와 함께 승리한 진영을 출력한다. - 예외가 발생하면 `[ERROR]` 형식의 메시지를 출력한다. +### 데이터베이스 +- 현재 DB에 저장된 전체 장기 게임방 목록을 조회한다. +- 선택한 게임방의 ID를 기반으로 해당 판의 모든 기물 정보를 불러와 보드를 복원한다. +- 새로운 게임 시작 시 입력받은 게임방 정보(이름, 생성 시간 등)를 DB에 저장한다. +- 새로운 게임의 초기 기물 배치 정보를 DB에 일괄 저장한다. +- 기물 이동 시, 이전 위치의 기물 데이터를 삭제하고 새로운 위치의 정보를 업데이트하여 상태를 영속화한다. +- 게임 종료 시(궁이 잡히거나 중단 동의 시, 또는 200턴을 넘길 시) 해당 게임방과 관련된 모든 데이터를 DB에서 삭제한다. + ### 예외 처리 - 존재하지 않는 마상 배치를 입력한 경우 - 숫자가 아닌 좌표를 입력한 경우 diff --git a/src/main/java/janggi/dao/GameRoom.java b/src/main/java/janggi/dao/GameRoom.java index ad9b21a2a4..66e0a3ec32 100644 --- a/src/main/java/janggi/dao/GameRoom.java +++ b/src/main/java/janggi/dao/GameRoom.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; - public class GameRoom { private final SQLManager sqlManager; From b42a62cadd69b86a7c6b836531ba3caf99fe66e1 Mon Sep 17 00:00:00 2001 From: armd482 Date: Fri, 3 Apr 2026 13:54:36 +0900 Subject: [PATCH 28/29] =?UTF-8?q?refactor:=20=EB=B9=88=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=20=EC=A0=9C=EC=99=B8=ED=95=98=EA=B3=A0=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Game.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index cc00b1f593..dac4c4195b 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -3,6 +3,7 @@ import janggi.domain.board.Board; import janggi.domain.piece.Piece; import janggi.domain.piece.PieceAttribute; +import janggi.domain.piece.PieceType; import janggi.domain.turn.ChoTurn; import janggi.domain.turn.HanTurn; import janggi.domain.turn.PlayerTurn; @@ -73,7 +74,11 @@ public Side getWinnerSide() { private List getPieceInitInfo(Map board) { List pieceInitInfos = new ArrayList<>(); - board.forEach(((position, piece) -> pieceInitInfos.add(piece.getPieceInitInfo(position)))); + board.forEach(((position, piece) -> { + if(!piece.isEqualPieceType(PieceType.NONE)) { + pieceInitInfos.add(piece.getPieceInitInfo(position)); + } + })); return pieceInitInfos; } From 95941a180183de4948ec2a5d428eccda7e4539b8 Mon Sep 17 00:00:00 2001 From: armd482 Date: Fri, 3 Apr 2026 14:01:42 +0900 Subject: [PATCH 29/29] =?UTF-8?q?refactor:=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=ED=84=B4=200=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/janggi/domain/Game.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/janggi/domain/Game.java b/src/main/java/janggi/domain/Game.java index dac4c4195b..d9f6d2f4cc 100644 --- a/src/main/java/janggi/domain/Game.java +++ b/src/main/java/janggi/domain/Game.java @@ -28,7 +28,7 @@ public List init(Arrangement choArrangement, Arrangement hanArran .mapToInt(Piece::getPieceScore) .sum(); - this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 1); + this.playerTurn = new ChoTurn(new Board(initBoard, hanScore, choScore), 0); return getPieceInitInfo(initBoard); }