diff --git a/README.md b/README.md index e9fa6ae3ae..668d64f5e0 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ - 목적지에 같은 팀 기물이 존재한다면 이동하지 못한다. - 목적지에 다른 팀 기물이 존재한다면 해당 기물을 잡을 수 있다. - 이동할 기물이 포이고 이동 경로에 포를 제외한 다른 기물이 있다면 기물을 넘어서 이동할 수 있다. +- [x] 기물의 이동에 궁성(宮城) 영역의 규칙을 포함한다. +- [x] 왕이 잡히는 경우 게임에서 진다. + - 왕이 잡혔을 때 게임을 종료한다. +- [x] 현재 남아 있는 기물에 대한 점수를 구할수 있어야 한다. +- [x] 애플리케이션을 재시작하더라도 이전에 하던 장기 게임을 다시 시작할 수 있어야 한다. + - DB를 적용할 때 도메인 객체의 변경을 최소화해야 한다. ### 예외 기능 - [x] 장기판을 벗어나면 예외를 발생한다. diff --git a/build.gradle b/build.gradle index ce846f70cc..dcca1dae54 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,8 @@ repositories { } dependencies { + implementation 'org.mongodb:mongodb-driver-sync:5.3.1' + 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/Application.java b/src/main/java/Application.java index 528a3ad0d0..8660a5c33c 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,11 +1,15 @@ import controller.JanggiController; +import dao.mongodb.BoardDao; +import dao.mongodb.MongoConnection; import view.InputView; import view.OutputView; public class Application { public static void main(String[] args) { - JanggiController janggiController = new JanggiController(new InputView(), new OutputView()); - janggiController.run(); + try (MongoConnection connection = new MongoConnection()) { + JanggiController janggiController = new JanggiController(new InputView(), new OutputView(), new BoardDao(connection)); + janggiController.run(); + } } -} +} \ No newline at end of file diff --git a/src/main/java/controller/JanggiController.java b/src/main/java/controller/JanggiController.java index 1bfc2c393f..98c97ddb53 100644 --- a/src/main/java/controller/JanggiController.java +++ b/src/main/java/controller/JanggiController.java @@ -1,35 +1,95 @@ package controller; +import dao.mongodb.BoardDao; import domain.Board; import domain.BoardFactory; import domain.Formation; +import domain.JanggiGame; import domain.Team; import domain.vo.Position; +import dto.PieceDto; +import java.util.List; +import presentation.PositionCommand; import view.InputView; import view.OutputView; public class JanggiController { + private static final String RESUME_GAME_NUMBER = "2"; + private static final String SURRENDER = "항복"; + private final InputView inputView; private final OutputView outputView; + private final BoardDao boardDao; - public JanggiController(InputView inputView, OutputView outputView) { + public JanggiController(InputView inputView, OutputView outputView, BoardDao boardDao) { this.inputView = inputView; this.outputView = outputView; + this.boardDao = boardDao; } public void run() { + String command = inputView.readCommand(); + + if (command.equals(RESUME_GAME_NUMBER)) { + resumeGame(); + return; + } + + startNewGame(); + } + + private void startNewGame() { Board board = BoardFactory.setUp(); board = readHanFormation(board); board = readChuFormation(board); - outputView.printBoard(board.getBoard()); + JanggiGame janggiGame = JanggiGame.of(board); + String gameId = boardDao.save(PieceDto.fromBoard(board.getBoard()), janggiGame.getTurnCount()); + + playGame(gameId, janggiGame); + } + + private void resumeGame() { + while (true) { + try { + String gameId = inputView.readGameId(); + List pieceDtos = boardDao.findPiecesByGameId(gameId); + + int turnCount = boardDao.findTurnCountByGameId(gameId); + Board board = PieceDto.toBoard(pieceDtos); + JanggiGame janggiGame = JanggiGame.of(board, turnCount); + + playGame(gameId, janggiGame); + return; + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + System.out.println(); + } + } + } + + private void playGame(String gameId, JanggiGame janggiGame) { + outputView.printCurrentGameId(gameId); + outputView.printBoard(janggiGame.getBoardStatus()); - int turnCount = 0; - turnCount = movePosition(board, turnCount); + while (true) { + movePosition(janggiGame); + boardDao.update(gameId, PieceDto.fromBoard(janggiGame.getBoardStatus()), janggiGame.getTurnCount()); + + if (janggiGame.isFinished()) { + boardDao.finish(gameId); + outputView.printGameFinishMessage(); + outputView.printScore(janggiGame.calculateScore(Team.HAN), janggiGame.calculateScore(Team.CHU)); + break; + } - while (inputView.readRetryCommand()) { - turnCount = movePosition(board, turnCount); + if (janggiGame.isSurrendered()) { + boardDao.finish(gameId); + outputView.printGameSurrenderMessage(); + outputView.printScore(janggiGame.calculateScore(Team.HAN), janggiGame.calculateScore(Team.CHU)); + break; + } } } @@ -37,7 +97,7 @@ private Board readChuFormation(Board board) { while (true) { try { String chuArrangement = inputView.readArrangement(Team.CHU); - board = applyArrangement(chuArrangement, board, Team.CHU); + board = Formation.from(chuArrangement, board, Team.CHU); return board; } catch (Exception e) { System.out.println(e.getMessage()); @@ -50,7 +110,7 @@ private Board readHanFormation(Board board) { while (true) { try { String hanArrangement = inputView.readArrangement(Team.HAN); - board = applyArrangement(hanArrangement, board, Team.HAN); + board = Formation.from(hanArrangement, board, Team.HAN); return board; } catch (Exception e) { @@ -60,35 +120,27 @@ private Board readHanFormation(Board board) { } } - private static Board applyArrangement(String arrangement, Board board, Team team) { - if (Formation.from(arrangement) == Formation.SANG_MA_SANG_MA) { - return BoardFactory.setUpLeftElephantFormation(board.getBoard(), team); - } - if (Formation.from(arrangement) == Formation.MA_SANG_MA_SANG) { - return BoardFactory.setUpRightElephantFormation(board.getBoard(), team); - } - if (Formation.from(arrangement) == Formation.MA_SANG_SANG_MA) { - return BoardFactory.setUpInnerElephantFormation(board.getBoard(), team); - } - if (Formation.from(arrangement) == Formation.SANG_MA_MA_SANG) { - return BoardFactory.setUpOuterElephantFormation(board.getBoard(), team); - } - - return board; - } - - private int movePosition(Board board, int turnCount) { + private void movePosition(JanggiGame janggiGame) { while (true) { try { - outputView.printCurrentTurn(turnCount); + outputView.printCurrentTurn(janggiGame.currentTurn()); + + String input = inputView.readPosition(); + if (input.equals(SURRENDER)) { + janggiGame.surrender(); + return; + } + + String targetInput = inputView.readTargetPosition(); + + Position position = PositionCommand.from(input).toPosition(); + Position targetPosition = PositionCommand.from(targetInput).toPosition(); - Position position = inputView.readPosition(); - Position targetPosition = inputView.readTargetPosition(); + janggiGame.move(position, targetPosition); + outputView.printBoard(janggiGame.getBoardStatus()); + janggiGame.passTheTurn(); - Team currentTeam = Team.from(turnCount); - board.move(position, targetPosition, currentTeam); - outputView.printBoard(board.getBoard()); - return turnCount += 1; + return; } catch (Exception e) { System.out.println(e.getMessage()); System.out.println(); diff --git a/src/main/java/dao/mongodb/BoardDao.java b/src/main/java/dao/mongodb/BoardDao.java new file mode 100644 index 0000000000..ee0e611b01 --- /dev/null +++ b/src/main/java/dao/mongodb/BoardDao.java @@ -0,0 +1,111 @@ +package dao.mongodb; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import dto.PieceDto; +import java.util.ArrayList; +import java.util.List; +import org.bson.Document; +import org.bson.types.ObjectId; + +public class BoardDao { + + private final MongoCollection collection; + + public BoardDao(MongoConnection connection) { + this.collection = connection.getCollection("board"); + } + + public String save(List pieces, int turnCount) { + ArrayList pieceDocuments = new ArrayList<>(); + + for (PieceDto pieceDto : pieces) { + pieceDocuments.add(toDocument(pieceDto)); + } + + Document gameDocument = getDocument(turnCount, pieceDocuments); + + collection.insertOne(gameDocument); + return gameDocument.getObjectId("_id").toString(); + } + + public List findPiecesByGameId(String gameId) { + Document gameDocument = findGameDocument(gameId); + + if (gameDocument.getBoolean("isFinished")) { + throw new IllegalArgumentException("[ERROR] 이미 종료된 게임입니다: " + gameId); + } + + List pieces = gameDocument.getList("pieces", Document.class); + List pieceDtos = new ArrayList<>(); + + for (Document doc : pieces) { + pieceDtos.add(toPieceDto(doc)); + } + + return pieceDtos; + } + + public int findTurnCountByGameId(String gameId) { + Document gameDocument = findGameDocument(gameId); + return gameDocument.getInteger("turnCount"); + } + + public void update(String gameId, List pieces, int turnCount) { + ArrayList pieceDocuments = new ArrayList<>(); + + for (PieceDto pieceDto : pieces) { + pieceDocuments.add(toDocument(pieceDto)); + } + + Document updateFields = new Document("$set", new Document() + .append("pieces", pieceDocuments) + .append("turnCount", turnCount)); + + collection.updateOne( + Filters.eq("_id", new ObjectId(gameId)), + updateFields + ); + } + + public void finish(String gameId) { + Document updateFields = new Document("$set", new Document("isFinished", true)); + + collection.updateOne( + Filters.eq("_id", new ObjectId(gameId)), + updateFields + ); + } + + private Document findGameDocument(String gameId) { + Document gameDocument = collection.find(Filters.eq("_id", new ObjectId(gameId))).first(); + if (gameDocument == null) { + throw new IllegalArgumentException("[ERROR] 해당 게임을 찾을 수 없습니다: " + gameId); + } + return gameDocument; + } + + private Document getDocument(int turnCount, ArrayList pieceDocuments) { + return new Document() + .append("pieces", pieceDocuments) + .append("turnCount", turnCount) + .append("isFinished", false); + } + + private Document toDocument(PieceDto pieceDto) { + return new Document() + .append("row", pieceDto.row()) + .append("col", pieceDto.col()) + .append("team", pieceDto.team()) + .append("type", pieceDto.type()); + } + + private PieceDto toPieceDto(Document doc) { + return new PieceDto( + doc.getInteger("row"), + doc.getInteger("col"), + doc.getString("team"), + doc.getString("type") + ); + } +} \ No newline at end of file diff --git a/src/main/java/dao/mongodb/MongoConnection.java b/src/main/java/dao/mongodb/MongoConnection.java new file mode 100644 index 0000000000..0220c59b1e --- /dev/null +++ b/src/main/java/dao/mongodb/MongoConnection.java @@ -0,0 +1,30 @@ +package dao.mongodb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; + +public class MongoConnection implements AutoCloseable { + + private static final String CONNECTION_STRING = "mongodb://admin:password123@localhost:27017"; + private static final String DATABASE_NAME = "janggi"; + + private final MongoClient client; + private final MongoDatabase database; + + public MongoConnection() { + this.client = MongoClients.create(CONNECTION_STRING); + this.database = client.getDatabase(DATABASE_NAME); + } + + public MongoCollection getCollection(String name) { + return database.getCollection(name); + } + + @Override + public void close() { + client.close(); + } +} \ No newline at end of file diff --git a/src/main/java/domain/Board.java b/src/main/java/domain/Board.java index 1b5ccd7eff..32e0780bd4 100644 --- a/src/main/java/domain/Board.java +++ b/src/main/java/domain/Board.java @@ -38,14 +38,6 @@ public void move(final Position from, final Position to, final Team currentTeam) board.put(to, fromPiece); } - private Map findPiecesAt(final List positions) { - Map result = new HashMap<>(); - for (Position pos : positions) { - findPieceByPosition(pos).ifPresent(piece -> result.put(pos, piece)); - } - return result; - } - public boolean isExistPosition(final Position tempPosition) { return board.containsKey(tempPosition); } @@ -57,4 +49,26 @@ public Optional findPieceByPosition(final Position position) { public Map getBoard() { return Map.copyOf(board); } + + public boolean isGeneralAlive(Team team) { + return board.values().stream() + .anyMatch(piece -> piece.getTeam() == team && piece.getType() == Type.GENERAL); + } + + public int calculateScore(Team team) { + return board.values() + .stream() + .filter(piece -> piece.getTeam() == team) + .map(piece -> piece.getType().getScore()) + .mapToInt(Integer::intValue) + .sum(); + } + + private Map findPiecesAt(final List positions) { + Map result = new HashMap<>(); + for (Position pos : positions) { + findPieceByPosition(pos).ifPresent(piece -> result.put(pos, piece)); + } + return result; + } } \ No newline at end of file diff --git a/src/main/java/domain/BoardFactory.java b/src/main/java/domain/BoardFactory.java index 7cf0f64c2a..ef99bd9873 100644 --- a/src/main/java/domain/BoardFactory.java +++ b/src/main/java/domain/BoardFactory.java @@ -29,10 +29,10 @@ public static Board setUpLeftElephantFormation(Map initialBoard board.put(Position.of(0, 7), Piece.of(Team.CHU, Type.HORSE)); } if (team == Team.HAN) { - board.put(Position.of(9, 2), Piece.of(Team.HAN, Type.ELEPHANT)); - board.put(Position.of(9, 7), Piece.of(Team.HAN, Type.ELEPHANT)); - board.put(Position.of(9, 1), Piece.of(Team.HAN, Type.HORSE)); - board.put(Position.of(9, 6), Piece.of(Team.HAN, Type.HORSE)); + board.put(Position.of(9, 1), Piece.of(Team.HAN, Type.ELEPHANT)); + board.put(Position.of(9, 6), Piece.of(Team.HAN, Type.ELEPHANT)); + board.put(Position.of(9, 2), Piece.of(Team.HAN, Type.HORSE)); + board.put(Position.of(9, 7), Piece.of(Team.HAN, Type.HORSE)); } return Board.of(board); @@ -48,10 +48,10 @@ public static Board setUpRightElephantFormation(Map initialBoar board.put(Position.of(0, 6), Piece.of(Team.CHU, Type.HORSE)); } if (team == Team.HAN) { - board.put(Position.of(9, 1), Piece.of(Team.HAN, Type.ELEPHANT)); - board.put(Position.of(9, 6), Piece.of(Team.HAN, Type.ELEPHANT)); - board.put(Position.of(9, 2), Piece.of(Team.HAN, Type.HORSE)); - board.put(Position.of(9, 7), Piece.of(Team.HAN, Type.HORSE)); + board.put(Position.of(9, 2), Piece.of(Team.HAN, Type.ELEPHANT)); + board.put(Position.of(9, 7), Piece.of(Team.HAN, Type.ELEPHANT)); + board.put(Position.of(9, 1), Piece.of(Team.HAN, Type.HORSE)); + board.put(Position.of(9, 6), Piece.of(Team.HAN, Type.HORSE)); } return Board.of(board); diff --git a/src/main/java/domain/Formation.java b/src/main/java/domain/Formation.java index 9d516bcd45..768d9953be 100644 --- a/src/main/java/domain/Formation.java +++ b/src/main/java/domain/Formation.java @@ -15,10 +15,25 @@ public enum Formation { this.koreanName = koreanName; } - public static Formation from(String input) { - return Arrays.stream(values()) + public static Board from(String input, Board board, Team team) { + Formation matchedFormation = Arrays.stream(values()) .filter(formation -> formation.koreanName.equals(input)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("[ERROR] 잘못된 진형 입력입니다.")); + + if (matchedFormation == Formation.SANG_MA_SANG_MA) { + return BoardFactory.setUpLeftElephantFormation(board.getBoard(), team); + } + if (matchedFormation == Formation.MA_SANG_MA_SANG) { + return BoardFactory.setUpRightElephantFormation(board.getBoard(), team); + } + if (matchedFormation == Formation.MA_SANG_SANG_MA) { + return BoardFactory.setUpInnerElephantFormation(board.getBoard(), team); + } + if (matchedFormation == Formation.SANG_MA_MA_SANG) { + return BoardFactory.setUpOuterElephantFormation(board.getBoard(), team); + } + + return board; } } diff --git a/src/main/java/domain/JanggiGame.java b/src/main/java/domain/JanggiGame.java new file mode 100644 index 0000000000..1d81ffb16d --- /dev/null +++ b/src/main/java/domain/JanggiGame.java @@ -0,0 +1,66 @@ +package domain; + +import domain.vo.Position; +import java.util.Map; + +public class JanggiGame { + + private static final int DEFAULT_TURN_COUNT = 0; + + private final Board board; + private TurnCount turnCount; + private boolean isSurrender; + + private JanggiGame(final Board board, final TurnCount turnCount, boolean isSurrender) { + this.board = board; + this.turnCount = turnCount; + this.isSurrender = isSurrender; + } + + public static JanggiGame of(final Board board) { + return new JanggiGame(board, TurnCount.of(DEFAULT_TURN_COUNT), false); + } + + public static JanggiGame of(final Board board, final int turnCount) { + JanggiGame game = new JanggiGame(board, TurnCount.of(turnCount), false); + game.turnCount = TurnCount.of(turnCount); + + return game; + } + + public Team currentTurn() { + return Team.from(turnCount.getTurnCount()); + } + + public int getTurnCount() { + return turnCount.getTurnCount(); + } + + public void passTheTurn() { + turnCount = TurnCount.of(turnCount.getTurnCount() + 1); + } + + public void move(Position from, Position to) { + board.move(from, to, currentTurn()); + } + + public Map getBoardStatus() { + return board.getBoard(); + } + + public boolean isFinished() { + return !board.isGeneralAlive(Team.CHU) || !board.isGeneralAlive(Team.HAN); + } + + public boolean isSurrendered() { + return isSurrender; + } + + public void surrender() { + isSurrender = true; + } + + public int calculateScore(Team team) { + return board.calculateScore(team); + } +} diff --git a/src/main/java/domain/Piece.java b/src/main/java/domain/Piece.java index f8c349f60f..b663f31c0e 100644 --- a/src/main/java/domain/Piece.java +++ b/src/main/java/domain/Piece.java @@ -1,6 +1,8 @@ package domain; import domain.strategy.MoveStrategy; +import domain.strategy.Palace; +import domain.strategy.PalaceMoveStrategy; import domain.vo.Position; import java.util.List; @@ -25,12 +27,12 @@ public boolean isAnotherTeam(final Piece anotherPiece) { } public List getPathPositions(final Position from, final Position to) { - MoveStrategy strategy = type.getStrategy(); + MoveStrategy strategy = resolveStrategy(from, to); return strategy.getPath(from, to); } public boolean canMovePiece(final Position from, final Position to, final Map piecesOnPath) { - MoveStrategy strategy = type.getStrategy(); + MoveStrategy strategy = resolveStrategy(from, to); return strategy.canMove(this, from, to, piecesOnPath); } @@ -49,4 +51,13 @@ public Team getTeam() { public String getTeamName() { return team.getName(); } + + private MoveStrategy resolveStrategy(final Position from, final Position to) { + MoveStrategy base = type.getStrategy(); + if (Palace.isInPalace(from) || Palace.isInPalace(to)) { + return new PalaceMoveStrategy(base); + } + + return base; + } } diff --git a/src/main/java/domain/Team.java b/src/main/java/domain/Team.java index 6747774ca1..d365447c58 100644 --- a/src/main/java/domain/Team.java +++ b/src/main/java/domain/Team.java @@ -14,10 +14,20 @@ public String getName() { return name; } + public static Team fromName(final String name) { + for (Team team : values()) { + if (team.name.equals(name)) { + return team; + } + } + throw new IllegalArgumentException("[ERROR] 알 수 없는 팀입니다: " + name); + } + public static Team from(final int turnCount) { if (turnCount % 2 == 0) { return HAN; } + return CHU; } } diff --git a/src/main/java/domain/TurnCount.java b/src/main/java/domain/TurnCount.java new file mode 100644 index 0000000000..f61b1d7fd7 --- /dev/null +++ b/src/main/java/domain/TurnCount.java @@ -0,0 +1,25 @@ +package domain; + +public class TurnCount { + + private final int turnCount; + + private TurnCount(final int turnCount) { + this.turnCount = turnCount; + } + + public static TurnCount of(final int turnCount) { + validate(turnCount); + return new TurnCount(turnCount); + } + + public int getTurnCount() { + return turnCount; + } + + private static void validate(final int turnCount) { + if (turnCount < 0) { + throw new IllegalArgumentException("[ERROR] 턴은 0 이하가 될 수 없습니다."); + } + } +} diff --git a/src/main/java/domain/Type.java b/src/main/java/domain/Type.java index 28476f94f6..43d20ac5c3 100644 --- a/src/main/java/domain/Type.java +++ b/src/main/java/domain/Type.java @@ -8,30 +8,44 @@ import domain.strategy.HorseMoveStrategy; import domain.strategy.MoveStrategy; import domain.strategy.SoldierMoveStrategy; -import java.util.function.Supplier; public enum Type { - GENERAL("궁", GeneralMoveStrategy::new), - CHARIOT("차", ChariotMoveStrategy::new), - CANNON("포", CannonMoveStrategy::new), - HORSE("마", HorseMoveStrategy::new), - ELEPHANT("상", ElephantMoveStrategy::new), - GUARD("사", GuardMoveStrategy::new), - SOLDIER("졸", SoldierMoveStrategy::new); + GENERAL("궁", new GeneralMoveStrategy(), 0), + CHARIOT("차", new ChariotMoveStrategy(), 13), + CANNON("포", new CannonMoveStrategy(), 7), + HORSE("마", new HorseMoveStrategy(), 5), + ELEPHANT("상", new ElephantMoveStrategy(), 3), + GUARD("사", new GuardMoveStrategy(), 3), + SOLDIER("졸", new SoldierMoveStrategy(), 2); private final String name; - private final Supplier strategySupplier; + private final MoveStrategy strategy; + private final int score; - Type(String name, Supplier strategySupplier) { + Type(String name, MoveStrategy strategy, int score) { this.name = name; - this.strategySupplier = strategySupplier; + this.strategy = strategy; + this.score = score; + } + + public static Type fromName(final String name) { + for (Type type : values()) { + if (type.name.equals(name)) { + return type; + } + } + throw new IllegalArgumentException("[ERROR] 알 수 없는 기물입니다: " + name); } public MoveStrategy getStrategy() { - return strategySupplier.get(); + return strategy; } public String getName() { return name; } + + public int getScore() { + return score; + } } diff --git a/src/main/java/domain/strategy/CannonMoveStrategy.java b/src/main/java/domain/strategy/CannonMoveStrategy.java index 23a09dd64a..485b49c0cb 100644 --- a/src/main/java/domain/strategy/CannonMoveStrategy.java +++ b/src/main/java/domain/strategy/CannonMoveStrategy.java @@ -62,6 +62,9 @@ public boolean canMove(final Piece mover, final Position from, final Position to } private boolean isNotCorrectPath(final Position from, final Position to) { - return from.getRow() != to.getRow() && from.getCol() != to.getCol(); + if (Palace.canDiagonalInPalace(from, to)) { + return false; + } + return from.getCol() != to.getCol() && from.getRow() != to.getRow(); } } \ No newline at end of file diff --git a/src/main/java/domain/strategy/ChariotMoveStrategy.java b/src/main/java/domain/strategy/ChariotMoveStrategy.java index f57309a5bb..05fb1f9704 100644 --- a/src/main/java/domain/strategy/ChariotMoveStrategy.java +++ b/src/main/java/domain/strategy/ChariotMoveStrategy.java @@ -50,6 +50,9 @@ public boolean canMove(final Piece mover, final Position from, final Position to } private boolean isNotCorrectPath(final Position from, final Position to) { + if (Palace.canDiagonalInPalace(from, to)) { + return false; + } return from.getCol() != to.getCol() && from.getRow() != to.getRow(); } } \ No newline at end of file diff --git a/src/main/java/domain/strategy/GeneralMoveStrategy.java b/src/main/java/domain/strategy/GeneralMoveStrategy.java index 83683c1da6..77b6be9b3b 100644 --- a/src/main/java/domain/strategy/GeneralMoveStrategy.java +++ b/src/main/java/domain/strategy/GeneralMoveStrategy.java @@ -17,7 +17,8 @@ public List getPath(final Position from, final Position to) { } @Override - public boolean canMove(final Piece mover, final Position from, final Position to, final Map piecesOnPath) { + public boolean canMove(final Piece mover, final Position from, final Position to, + final Map piecesOnPath) { if (isNotCorrectPath(from, to)) { return false; } @@ -30,14 +31,20 @@ public boolean canMove(final Piece mover, final Position from, final Position to } private boolean isNotCorrectPath(final Position from, final Position to) { - if (from.getRow() == to.getRow() && Math.abs(from.getCol() - to.getCol()) != 1) { + if (!palaceInRange(to)) { return true; } - - if (from.getCol() == to.getCol() && Math.abs(from.getRow() - to.getRow()) != 1) { - return true; + if (Palace.canDiagonalInPalace(from, to) + && Math.abs(from.getRow() - to.getRow()) == 1 + && Math.abs(from.getCol() - to.getCol()) == 1) { + return false; } + return Math.abs(from.getRow() - to.getRow()) + Math.abs(from.getCol() - to.getCol()) != 1; + } - return false; + private boolean palaceInRange(final Position position) { + return (((0 <= position.getRow() && position.getRow() <= 2) || (7 <= position.getRow() + && position.getRow() <= 9)) + && (3 <= position.getCol() && position.getCol() <= 5)); } } \ No newline at end of file diff --git a/src/main/java/domain/strategy/GuardMoveStrategy.java b/src/main/java/domain/strategy/GuardMoveStrategy.java index 65d6ce5f8f..5984a2b332 100644 --- a/src/main/java/domain/strategy/GuardMoveStrategy.java +++ b/src/main/java/domain/strategy/GuardMoveStrategy.java @@ -26,22 +26,16 @@ public boolean canMove(final Piece mover, final Position from, final Position to if (target == null) { return true; } + return mover.isAnotherTeam(target); } private boolean isNotCorrectPath(final Position from, final Position to) { - if (Math.abs(from.getRow() - to.getRow()) + Math.abs(from.getCol() - to.getCol()) != 1) { - return true; - } - - if (from.getRow() == to.getRow() && Math.abs(from.getCol() - to.getCol()) != 1) { - return true; - } - - if (from.getCol() == to.getCol() && Math.abs(from.getRow() - to.getRow()) != 1) { - return true; + if (Palace.canDiagonalInPalace(from, to) + && Math.abs(from.getRow() - to.getRow()) == 1 + && Math.abs(from.getCol() - to.getCol()) == 1) { + return false; } - - return false; + return Math.abs(from.getRow() - to.getRow()) + Math.abs(from.getCol() - to.getCol()) != 1; } } \ No newline at end of file diff --git a/src/main/java/domain/strategy/Palace.java b/src/main/java/domain/strategy/Palace.java new file mode 100644 index 0000000000..a4b7ebb80b --- /dev/null +++ b/src/main/java/domain/strategy/Palace.java @@ -0,0 +1,62 @@ +package domain.strategy; + +import domain.vo.Position; + +public class Palace { + + private static final int[][] DIAGONAL_CENTERS = {{1, 4}, {8, 4}}; + private static final int[][][] DIAGONAL_CORNERS = { + {{0, 3}, {0, 5}, {2, 3}, {2, 5}}, + {{7, 3}, {7, 5}, {9, 3}, {9, 5}} + }; + + public static boolean isInPalace(Position pos) { + int r = pos.getRow(), c = pos.getCol(); + return (3 <= c && c <= 5) && + ((0 <= r && r <= 2) || (7 <= r && r <= 9)); + } + + public static boolean canDiagonalInPalace(Position from, Position to) { + if (!isInPalace(from) || !isInPalace(to)) { + return false; + } + int rowDiff = Math.abs(from.getRow() - to.getRow()); + int colDiff = Math.abs(from.getCol() - to.getCol()); + if (rowDiff < 1 || colDiff < 1) { + return false; + } + + if (isCenter(from)) { + return true; + } + + if (isCorner(from) && isCenter(to)) { + return true; + } + + if (isCorner(from) && isCorner(to) && isSamePalace(from, to)) { + return true; + } + return false; + } + + private static boolean isSamePalace(Position a, Position b) { + return (a.getRow() <= 2 && b.getRow() <= 2) || (a.getRow() >= 7 && b.getRow() >= 7); + } + + private static boolean isCenter(Position pos) { + for (int[] c : DIAGONAL_CENTERS) { + if (pos.getRow() == c[0] && pos.getCol() == c[1]) return true; + } + return false; + } + + private static boolean isCorner(Position pos) { + for (int[][] group : DIAGONAL_CORNERS) { + for (int[] c : group) { + if (pos.getRow() == c[0] && pos.getCol() == c[1]) return true; + } + } + return false; + } +} diff --git a/src/main/java/domain/strategy/PalaceMoveStrategy.java b/src/main/java/domain/strategy/PalaceMoveStrategy.java new file mode 100644 index 0000000000..03ffd82180 --- /dev/null +++ b/src/main/java/domain/strategy/PalaceMoveStrategy.java @@ -0,0 +1,45 @@ +package domain.strategy; + +import domain.Piece; +import domain.vo.Position; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PalaceMoveStrategy implements MoveStrategy { + + private final MoveStrategy base; + + public PalaceMoveStrategy(MoveStrategy base) { + this.base = base; + } + + @Override + public List getPath(Position from, Position to) { + if (Palace.canDiagonalInPalace(from, to)) { + return buildDiagonalPath(from, to); + } + return base.getPath(from, to); + } + + @Override + public boolean canMove(Piece mover, Position from, Position to, + Map piecesOnPath) { + return base.canMove(mover, from, to, piecesOnPath); + } + + private List buildDiagonalPath(Position from, Position to) { + List path = new ArrayList<>(); + int dr = Integer.compare(to.getRow(), from.getRow()); + int dc = Integer.compare(to.getCol(), from.getCol()); + int r = from.getRow() + dr; + int c = from.getCol() + dc; + while (r != to.getRow() || c != to.getCol()) { + path.add(Position.of(r, c)); + r += dr; + c += dc; + } + path.add(to); + return path; + } +} diff --git a/src/main/java/domain/strategy/SoldierMoveStrategy.java b/src/main/java/domain/strategy/SoldierMoveStrategy.java index 5efd816787..a857f306af 100644 --- a/src/main/java/domain/strategy/SoldierMoveStrategy.java +++ b/src/main/java/domain/strategy/SoldierMoveStrategy.java @@ -17,16 +17,14 @@ public List getPath(final Position from, final Position to) { return List.of(to); } - @Override - public boolean canMove(final Piece mover, final Position from, final Position to, final Map piecesOnPath) { + public boolean canMove(final Piece mover, final Position from, final Position to, + final Map piecesOnPath) { if (isNotCorrectPath(from, to)) { return false; } - if (isWithdraw(from, to, mover)) { return false; } - Piece target = piecesOnPath.get(to); if (target == null) { return true; @@ -48,18 +46,11 @@ private boolean isWithdraw(final Position from, final Position to, final Piece m } private boolean isNotCorrectPath(final Position from, final Position to) { - if (Math.abs(from.getRow() - to.getRow()) + Math.abs(from.getCol() - to.getCol()) != 1) { - return true; - } - - if (from.getRow() == to.getRow() && Math.abs(from.getCol() - to.getCol()) != 1) { - return true; - } - - if (from.getCol() == to.getCol() && Math.abs(from.getRow() - to.getRow()) != 1) { - return true; + if (Palace.canDiagonalInPalace(from, to) + && Math.abs(from.getRow() - to.getRow()) == 1 + && Math.abs(from.getCol() - to.getCol()) == 1) { + return false; } - - return false; + return Math.abs(from.getRow() - to.getRow()) + Math.abs(from.getCol() - to.getCol()) != 1; } } \ No newline at end of file diff --git a/src/main/java/dto/PieceDto.java b/src/main/java/dto/PieceDto.java new file mode 100644 index 0000000000..f5182772a2 --- /dev/null +++ b/src/main/java/dto/PieceDto.java @@ -0,0 +1,34 @@ +package dto; + +import domain.Board; +import domain.Piece; +import domain.Team; +import domain.Type; +import domain.vo.Position; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public record PieceDto(int row, int col, String team, String type) { + + public static List fromBoard(Map boardStatus) { + List pieceDtos = new ArrayList<>(); + for (Map.Entry entry : boardStatus.entrySet()) { + Position position = entry.getKey(); + Piece piece = entry.getValue(); + pieceDtos.add(new PieceDto(position.getRow(), position.getCol(), piece.getTeamName(), piece.getTypeName())); + } + return pieceDtos; + } + + public static Board toBoard(List pieceDtos) { + Map boardMap = new HashMap<>(); + for (PieceDto dto : pieceDtos) { + Position position = Position.of(dto.row(), dto.col()); + Piece piece = Piece.of(Team.fromName(dto.team()), Type.fromName(dto.type())); + boardMap.put(position, piece); + } + return Board.of(boardMap); + } +} \ No newline at end of file diff --git a/src/main/java/presentation/PositionCommand.java b/src/main/java/presentation/PositionCommand.java new file mode 100644 index 0000000000..9a2ce70709 --- /dev/null +++ b/src/main/java/presentation/PositionCommand.java @@ -0,0 +1,33 @@ +package presentation; + +import domain.vo.Position; + +public class PositionCommand { + + private static final String POSITION_PATTERN = "^\\d+\\s+\\d+$"; + + private final Position position; + + private PositionCommand(final Position position) { + this.position = position; + } + + public static PositionCommand from(final String input) { + validate(input); + + String[] tokens = input.split(" "); + return new PositionCommand( + Position.of(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])) + ); + } + + private static void validate(final String input) { + if (!input.matches(POSITION_PATTERN)) { + throw new IllegalArgumentException("[ERROR] 입력 형식이 잘못되었습니다. '숫자 공백 숫자' 형식이어야 합니다."); + } + } + + public Position toPosition() { + return position; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 1af5a84077..cf584aa323 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,77 +1,35 @@ package view; import domain.Team; -import domain.vo.Position; import java.util.Scanner; public class InputView { - private static final String POSITION_PATTERN = "^\\d+\\s+\\d+$"; final Scanner scanner = new Scanner(System.in); - public Position readPosition() { + public String readPosition() { System.out.println("움직일 기물의 위치를 입력해주세요. (예: 0 0)"); - - String input = scanner.nextLine(); - try { - validatePositionFormat(input); - - String[] tokens = input.split(" "); - return Position.of(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])); - } catch (Exception e) { - System.out.println(e.getMessage()); - System.out.println(); - return readPosition(); - } + return scanner.nextLine(); } - public Position readTargetPosition() { + public String readTargetPosition() { System.out.println("기물을 움직일 위치를 입력해주세요. (예: 0 0)"); - - String input = scanner.nextLine(); - try { - validatePositionFormat(input); - - String[] tokens = input.split(" "); - return Position.of(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])); - } catch (Exception e) { - System.out.println(e.getMessage()); - System.out.println(); - return readTargetPosition(); - } - } - - public Boolean readRetryCommand() { - System.out.println("계속 하시겠습니까?(y/n)"); - - String input = scanner.nextLine(); - try { - validateRetryCommand(input); - } catch (Exception e) { - System.out.println(e.getMessage()); - System.out.println(); - return readRetryCommand(); - } - - return input.equals("y"); + return scanner.nextLine(); } - public String readArrangement(Team team) { + public String readArrangement(final Team team) { System.out.println(team.getName() + "나라의 판차림 방식을 선택해주세요.(상마상마, 마상마상, 마상상마, 상마마상)"); - return scanner.nextLine(); } - private void validateRetryCommand(final String input) { - if (!input.equals("y") && !input.equals("n")) { - throw new IllegalArgumentException("[ERROR] y 또는 n만 입력 가능합니다."); - } + public String readCommand() { + System.out.println("1. 새 게임 2. 이어하기"); + return scanner.nextLine(); } - private void validatePositionFormat(final String input) { - if (!input.matches(POSITION_PATTERN)) { - throw new IllegalArgumentException("[ERROR] 입력 형식이 잘못되었습니다. '숫자 공백 숫자' 형식이어야 합니다."); - } + public String readGameId() { + System.out.println("이어서 진행할 게임 id를 입력해주세요."); + return scanner.nextLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 767436ecea..ef46e660d5 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,6 +1,7 @@ package view; import domain.Piece; +import domain.Team; import domain.vo.Position; import java.util.Map; @@ -49,11 +50,11 @@ public void printBoard(Map board) { System.out.println(); } - public void printCurrentTurn(int turnCount) { - if (turnCount % 2 == 0) { + public void printCurrentTurn(Team team) { + if (team == Team.HAN) { System.out.println("현재는 한나라 차례입니다."); } - if (turnCount % 2 != 0) { + if (team == Team.CHU) { System.out.println("현재는 초나라 차례입니다."); } } @@ -70,4 +71,22 @@ private String matchSoldierName(String type, Piece piece) { } return type; } + + public void printGameFinishMessage() { + System.out.println("왕이 잡혀서 게임을 종료합니다."); + } + + public void printScore(int hanScore, int chuScore) { + System.out.println("== 최종 점수 =="); + System.out.println("한: " + hanScore + "점"); + System.out.println("초: " + chuScore + "점"); + } + + public void printCurrentGameId(String gameId) { + System.out.println("현재 진행중인 게임의 아이디는 " + gameId); + } + + public void printGameSurrenderMessage() { + System.out.println("유저의 항복으로 인해 게임을 종료합니다."); + } } diff --git a/src/test/java/dao/BoardDaoTest.java b/src/test/java/dao/BoardDaoTest.java new file mode 100644 index 0000000000..ca8d85e42a --- /dev/null +++ b/src/test/java/dao/BoardDaoTest.java @@ -0,0 +1,108 @@ +package dao; + +import static org.assertj.core.api.Assertions.assertThat; + +import dao.mongodb.BoardDao; +import dao.mongodb.MongoConnection; +import dto.PieceDto; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BoardDaoTest { + + private BoardDao boardDao; + + @BeforeEach + void setUp() { + boardDao = new BoardDao(new MongoConnection()); + } + + @Test + @DisplayName("보드를 저장하고 gameId를 반환한다") + void 보드를_저장하고_gameId를_반환한다() { + // given + List pieces = List.of( + new PieceDto(0, 0, "초", "차"), + new PieceDto(9, 8, "한", "궁") + ); + + // when + String gameId = boardDao.save(pieces, 0); + + // then + assertThat(gameId).isNotNull(); + } + + @Test + @DisplayName("저장한 보드를 gameId로 조회할 수 있다") + void 저장한_보드를_gameId로_조회할_수_있다() { + // given + List pieces = List.of( + new PieceDto(0, 0, "초", "차"), + new PieceDto(9, 8, "한", "궁") + ); + String gameId = boardDao.save(pieces, 0); + + // when + List foundPieces = boardDao.findPiecesByGameId(gameId); + + // then + assertThat(foundPieces).hasSize(2); + } + + @Test + @DisplayName("저장한 보드의 기물 정보가 일치한다") + void 저장한_보드의_기물_정보가_일치한다() { + // given + List pieces = List.of( + new PieceDto(0, 0, "초", "차"), + new PieceDto(9, 8, "한", "궁") + ); + String gameId = boardDao.save(pieces, 0); + + // when + List foundPieces = boardDao.findPiecesByGameId(gameId); + + // then + PieceDto chariot = foundPieces.stream() + .filter(p -> p.row() == 0 && p.col() == 0) + .findFirst().get(); + assertThat(chariot.team()).isEqualTo("초"); + assertThat(chariot.type()).isEqualTo("차"); + } + + @Test + @DisplayName("턴 카운트를 저장하고 조회할 수 있다") + void 턴_카운트를_저장하고_조회할_수_있다() { + // given + List pieces = List.of(new PieceDto(0, 0, "초", "차")); + String gameId = boardDao.save(pieces, 3); + + // when + int turnCount = boardDao.findTurnCountByGameId(gameId); + + // then + assertThat(turnCount).isEqualTo(3); + } + + @Test + @DisplayName("보드를 업데이트하면 변경된 내용이 반영된다") + void 보드를_업데이트하면_변경된_내용이_반영된다() { + // given + List pieces = List.of(new PieceDto(0, 0, "초", "차")); + String gameId = boardDao.save(pieces, 0); + + List updatedPieces = List.of(new PieceDto(5, 5, "한", "졸")); + + // when + boardDao.update(gameId, updatedPieces, 1); + + // then + List foundPieces = boardDao.findPiecesByGameId(gameId); + assertThat(foundPieces).hasSize(1); + int turnCount = boardDao.findTurnCountByGameId(gameId); + assertThat(turnCount).isEqualTo(1); + } +} \ No newline at end of file diff --git a/src/test/java/dao/MongoConnectionTest.java b/src/test/java/dao/MongoConnectionTest.java new file mode 100644 index 0000000000..fb4769c31e --- /dev/null +++ b/src/test/java/dao/MongoConnectionTest.java @@ -0,0 +1,28 @@ +package dao; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class MongoConnectionTest { + + private static final String CONNECTION_STRING = "mongodb://admin:password123@localhost:27017"; + private static final String DATABASE_NAME = "janggi"; + + @Test + @DisplayName("MongoDB 서버에 연결할 수 있다") + void MongoDB_서버에_연결할_수_있다() { + try (MongoClient client = MongoClients.create(CONNECTION_STRING)) { + MongoDatabase database = client.getDatabase(DATABASE_NAME); + + Document result = database.runCommand(new Document("ping", 1)); + + assertThat(result.getDouble("ok")).isEqualTo(1.0); + } + } +} diff --git a/src/test/java/domain/BoardTest.java b/src/test/java/domain/BoardTest.java index 294333baba..1b7d489ce1 100644 --- a/src/test/java/domain/BoardTest.java +++ b/src/test/java/domain/BoardTest.java @@ -1,6 +1,7 @@ package domain; import domain.vo.Position; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -61,6 +62,18 @@ class BoardTest { assertFalse(board.isExistPosition(from)); } + @Test + @DisplayName("모든 기물이 살아 있다면 현재 점수는 72점이다.") + void 현재_점수_계산() { + // given + Board board = BoardFactory.setUp(); + JanggiGame janggiGame = JanggiGame.of(board); + + // when // then + Assertions.assertThat(janggiGame.calculateScore(Team.HAN)).isEqualTo(72); + Assertions.assertThat(janggiGame.calculateScore(Team.CHU)).isEqualTo(72); + } + private static Stream providePiece() { return Stream.of( Arguments.of(0, 0, Type.CHARIOT), diff --git a/src/test/java/domain/JanggiGameTest.java b/src/test/java/domain/JanggiGameTest.java new file mode 100644 index 0000000000..ef880f1c12 --- /dev/null +++ b/src/test/java/domain/JanggiGameTest.java @@ -0,0 +1,177 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import domain.vo.Position; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class JanggiGameTest { + + @Test + @DisplayName("게임 시작 시 한나라가 먼저 시작한다") + void 게임_시작_시_한나라가_먼저_시작한다() { + // given + Board board = BoardFactory.setUp(); + + // when + JanggiGame game = JanggiGame.of(board); + + // then + assertThat(game.currentTurn()).isEqualTo(Team.HAN); + } + + @Test + @DisplayName("턴을 넘기면 초나라 차례가 된다") + void 턴을_넘기면_초나라_차례가_된다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + + // when + game.passTheTurn(); + + // then + assertThat(game.currentTurn()).isEqualTo(Team.CHU); + } + + @Test + @DisplayName("턴을 두 번 넘기면 다시 한나라 차례가 된다") + void 턴을_두_번_넘기면_다시_한나라_차례가_된다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + + // when + game.passTheTurn(); + game.passTheTurn(); + + // then + assertThat(game.currentTurn()).isEqualTo(Team.HAN); + } + + @Test + @DisplayName("한나라 졸을 앞으로 한 칸 이동할 수 있다") + void 한나라_졸을_앞으로_한_칸_이동할_수_있다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + Position from = Position.of(6, 0); + Position to = Position.of(5, 0); + + // when + game.move(from, to); + + // then + Map boardStatus = game.getBoardStatus(); + assertThat(boardStatus.containsKey(to)).isTrue(); + assertThat(boardStatus.containsKey(from)).isFalse(); + assertThat(boardStatus.get(to).getType()).isEqualTo(Type.SOLDIER); + } + + @Test + @DisplayName("상대편 기물을 이동하려 하면 예외가 발생한다") + void 상대편_기물을_이동하려_하면_예외가_발생한다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + Position from = Position.of(3, 0); + Position to = Position.of(4, 0); + + // when // then + assertThatThrownBy(() -> game.move(from, to)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 상대편의 기물은 움직일 수 없습니다"); + } + + @Test + @DisplayName("빈 위치의 기물을 이동하려 하면 예외가 발생한다") + void 빈_위치의_기물을_이동하려_하면_예외가_발생한다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + Position from = Position.of(5, 0); + Position to = Position.of(4, 0); + + // when // then + assertThatThrownBy(() -> game.move(from, to)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 해당 위치에 기물이 존재하지 않습니다"); + } + + @Test + @DisplayName("양쪽 궁이 모두 살아있으면 게임이 끝나지 않는다") + void 양쪽_궁이_모두_살아있으면_게임이_끝나지_않는다() { + // given + Board board = BoardFactory.setUp(); + + // when + JanggiGame game = JanggiGame.of(board); + + // then + assertThat(game.isFinished()).isFalse(); + } + + @Test + @DisplayName("초나라 궁이 잡히면 게임이 종료된다") + void 초나라_궁이_잡히면_게임이_종료된다() { + // given + Map pieces = new HashMap<>(); + pieces.put(Position.of(8, 4), Piece.of(Team.HAN, Type.GENERAL)); + Board board = Board.of(pieces); + + // when + JanggiGame game = JanggiGame.of(board); + + // then + assertThat(game.isFinished()).isTrue(); + } + + @Test + @DisplayName("한나라 궁이 잡히면 게임이 종료된다") + void 한나라_궁이_잡히면_게임이_종료된다() { + // given + Map pieces = new HashMap<>(); + pieces.put(Position.of(1, 4), Piece.of(Team.CHU, Type.GENERAL)); + Board board = Board.of(pieces); + + // when + JanggiGame game = JanggiGame.of(board); + + // then + assertThat(game.isFinished()).isTrue(); + } + + @Test + @DisplayName("초기 보드 상태를 조회할 수 있다") + void 초기_보드_상태를_조회할_수_있다() { + // given + Board board = BoardFactory.setUp(); + + // when + JanggiGame game = JanggiGame.of(board); + + // then + assertThat(game.getBoardStatus()).isNotEmpty(); + } + + @Test + @DisplayName("기물 이동 후 보드 상태가 반영된다") + void 기물_이동_후_보드_상태가_반영된다() { + // given + Board board = BoardFactory.setUp(); + JanggiGame game = JanggiGame.of(board); + Position from = Position.of(6, 0); + Position to = Position.of(5, 0); + + // when + game.move(from, to); + + // then + Map boardStatus = game.getBoardStatus(); + assertThat(boardStatus.get(Position.of(5, 0)).getTeam()).isEqualTo(Team.HAN); + } +} diff --git a/src/test/java/domain/strategy/CannonMoveStrategyTest.java b/src/test/java/domain/strategy/CannonMoveStrategyTest.java index 49a33962b7..51863ddaaa 100644 --- a/src/test/java/domain/strategy/CannonMoveStrategyTest.java +++ b/src/test/java/domain/strategy/CannonMoveStrategyTest.java @@ -121,4 +121,18 @@ class CannonMoveStrategyTest { Assertions.assertFalse(strategy.canMove(mover, from, to, Map.of(Position.of(3, 1), pathCannon))); } + + @Test + @DisplayName("궁성 안에서 포의 이동 가능한 대각선 이동 시 이동한다.") + void 궁성_안_가능한_포의_대각선_이동() { + // given + Position from = Position.of(7, 3); + Position to = Position.of(9, 5); + + Piece mover = Piece.of(Team.CHU, Type.CANNON); + Piece general = Piece.of(Team.CHU, Type.GENERAL); + + // when // then + Assertions.assertTrue(mover.canMovePiece(from, to, Map.of(Position.of(8, 4), general))); + } } diff --git a/src/test/java/domain/strategy/ChariotMoveStrategyTest.java b/src/test/java/domain/strategy/ChariotMoveStrategyTest.java index a47cda8456..dbd7707fd6 100644 --- a/src/test/java/domain/strategy/ChariotMoveStrategyTest.java +++ b/src/test/java/domain/strategy/ChariotMoveStrategyTest.java @@ -1,5 +1,7 @@ package domain.strategy; +import static org.junit.jupiter.api.Assertions.*; + import domain.Piece; import domain.Team; import domain.Type; @@ -31,8 +33,8 @@ void setUp() { Position from = Position.of(0, 0); Position to = Position.of(2, 0); - // then - Assertions.assertTrue(chariotPiece.canMovePiece(from, to, Map.of())); + // when // then + assertTrue(chariotPiece.canMovePiece(from, to, Map.of())); } @Test @@ -42,8 +44,8 @@ void setUp() { Position from = Position.of(0, 0); Position to = Position.of(3, 0); - // then - Assertions.assertFalse(chariotPiece.canMovePiece(from, to, Map.of(to, horsePiece))); + // when // then + assertFalse(chariotPiece.canMovePiece(from, to, Map.of(to, horsePiece))); } @Test @@ -53,7 +55,29 @@ void setUp() { Position from = Position.of(0, 0); Position to = Position.of(0, 3); - // then - Assertions.assertTrue(chariotPiece.canMovePiece(from, to, Map.of(to, elephantPiece))); + // when // then + assertTrue(chariotPiece.canMovePiece(from, to, Map.of(to, elephantPiece))); + } + + @Test + @DisplayName("궁성 안에서 가능한 대각선 이동 시 이동한다.") + void 궁성_안_가능한_대각선_이동_가능() { + // given + Position from = Position.of(2, 3); + Position to = Position.of(0, 5); + + // when // then + assertTrue(chariotPiece.canMovePiece(from, to, Map.of())); + } + + @Test + @DisplayName("궁성 안에서 불가능한 대각선 이동 시 이동하지 않는다.") + void 궁성_안_불가능한_대각선_이동은_불가() { + // given + Position from = Position.of(0, 4); + Position to = Position.of(2, 6); + + // when // then + assertFalse(chariotPiece.canMovePiece(from, to, Map.of())); } } diff --git a/src/test/java/domain/strategy/GeneralMoveStrategyTest.java b/src/test/java/domain/strategy/GeneralMoveStrategyTest.java index 6121d83ef3..10f37ae458 100644 --- a/src/test/java/domain/strategy/GeneralMoveStrategyTest.java +++ b/src/test/java/domain/strategy/GeneralMoveStrategyTest.java @@ -1,13 +1,17 @@ package domain.strategy; +import static org.junit.jupiter.api.Assertions.*; + import domain.*; import domain.vo.Position; -import org.junit.jupiter.api.Assertions; +import java.util.HashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Map; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class GeneralMoveStrategyTest { @@ -27,8 +31,8 @@ void setUp() { Position from = Position.of(1, 4); Position to = Position.of(1, 5); - // when & then - Assertions.assertTrue(strategy.canMove(mover, from, to, Map.of())); + // when // then + assertTrue(strategy.canMove(mover, from, to, Map.of())); } @Test @@ -39,8 +43,8 @@ void setUp() { Position to = Position.of(1, 5); Piece target = Piece.of(Team.CHU, Type.SOLDIER); - // when & then - Assertions.assertFalse(strategy.canMove(mover, from, to, Map.of(to, target))); + // when // then + assertFalse(strategy.canMove(mover, from, to, Map.of(to, target))); } @Test @@ -51,7 +55,57 @@ void setUp() { Position to = Position.of(1, 5); Piece target = Piece.of(Team.HAN, Type.SOLDIER); - // when & then - Assertions.assertTrue(strategy.canMove(mover, from, to, Map.of(to, target))); + // when // then + assertTrue(strategy.canMove(mover, from, to, Map.of(to, target))); + } + + @Test + @DisplayName("궁의 목적지가 궁성 밖이라면 이동하지 않는다.") + void 궁_목적지가_궁성_밖이라면_이동_불가() { + // given + Position from = Position.of(2, 3); + Position to = Position.of(2, 2); + Piece piece = Piece.of(Team.CHU, Type.GENERAL); + HashMap pieceOfPath = new HashMap<>(); + + pieceOfPath.put(to, null); + + // when // then + assertFalse(piece.canMovePiece(from, to, pieceOfPath)); + } + + @Test + @DisplayName("궁의 대각선 이동이 불가능한 위치로 이동 시 이동하지 않는다.") + void 궁_대각선_이동이_불가능한_위치면_이동_불가() { + // given + Position from = Position.of(2, 4); + Position to = Position.of(1, 3); + Piece piece = Piece.of(Team.CHU, Type.GENERAL); + HashMap pieceOfPath = new HashMap<>(); + + pieceOfPath.put(to, null); + + // when // then + assertFalse(piece.canMovePiece(from, to, pieceOfPath)); + } + + @ParameterizedTest + @DisplayName("궁의 0,4 또는 9,4 위치에서 대각선 이동 시 이동하지 않는다.") + @CsvSource({ + "0, 4, 1, 3", + "0, 4, 1, 5", + "9, 4, 8, 3", + "9, 4, 8, 5" + }) + void 궁_대각선_이동이_불가능한_위치면_이동_불가2(int fromRow, int fromCol, int toRow, int toCol) { + // given + Position from = Position.of(fromRow, fromCol); + Position to = Position.of(toRow, toCol); + HashMap pieceOfPath = new HashMap<>(); + + pieceOfPath.put(to, null); + + // when // then + assertFalse(mover.canMovePiece(from, to, pieceOfPath)); } } diff --git a/src/test/java/domain/strategy/GuardMoveStrategyTest.java b/src/test/java/domain/strategy/GuardMoveStrategyTest.java index fd741ccf27..822c914767 100644 --- a/src/test/java/domain/strategy/GuardMoveStrategyTest.java +++ b/src/test/java/domain/strategy/GuardMoveStrategyTest.java @@ -1,10 +1,12 @@ package domain.strategy; +import static org.junit.jupiter.api.Assertions.*; + import domain.Piece; import domain.Team; import domain.Type; import domain.vo.Position; -import org.junit.jupiter.api.Assertions; +import java.util.HashMap; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,11 +21,11 @@ class GuardMoveStrategyTest { MoveStrategy strategy = new GuardMoveStrategy(); Piece mover = Piece.of(Team.CHU, Type.GUARD); - Position position = Position.of(0, 3); - Position targetPosition = Position.of(1, 3); + Position from = Position.of(0, 3); + Position to = Position.of(1, 3); // when & then - Assertions.assertTrue(strategy.canMove(mover, position, targetPosition, Map.of())); + assertTrue(strategy.canMove(mover, from, to, Map.of())); } @Test @@ -32,11 +34,11 @@ class GuardMoveStrategyTest { // given Piece guardPiece = Piece.of(Team.CHU, Type.GUARD); - Position position = Position.of(0, 3); - Position targetPosition = Position.of(0, 2); + Position from = Position.of(0, 3); + Position to = Position.of(0, 2); // when & then - Assertions.assertFalse(guardPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, guardPiece))); + assertFalse(guardPiece.canMovePiece(from, to, Map.of(to, guardPiece))); } @Test @@ -46,11 +48,11 @@ class GuardMoveStrategyTest { Piece guardPiece = Piece.of(Team.CHU, Type.GUARD); Piece soldierPiece = Piece.of(Team.HAN, Type.SOLDIER); - Position position = Position.of(0, 3); - Position targetPosition = Position.of(1, 3); + Position from = Position.of(0, 3); + Position to = Position.of(1, 3); // when & then - Assertions.assertTrue(guardPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, soldierPiece))); + assertTrue(guardPiece.canMovePiece(from, to, Map.of(to, soldierPiece))); } @Test @@ -59,10 +61,25 @@ class GuardMoveStrategyTest { // given Piece guardPiece = Piece.of(Team.CHU, Type.GUARD); - Position position = Position.of(0, 3); - Position targetPosition = Position.of(1, 2); + Position from = Position.of(0, 3); + Position to = Position.of(1, 2); // when & then - Assertions.assertFalse(guardPiece.canMovePiece(position, targetPosition, Map.of())); + assertFalse(guardPiece.canMovePiece(from, to, Map.of())); + } + + @Test + @DisplayName("사가 궁성 안에서 이동할 수 없는 곳으로 대각선 이동하려고 하면 이동하지 않는다.") + void 사_궁성_안_이동_불가능한_대각선_이동_불가() { + // given + Piece guardPiece = Piece.of(Team.CHU, Type.GUARD); + + Position from = Position.of(1, 3); + Position to = Position.of(2, 4); + Map pieceOnPath = new HashMap<>(); + pieceOnPath.put(to, null); + + // when // then + assertFalse(guardPiece.canMovePiece(from, to, pieceOnPath)); } } diff --git a/src/test/java/domain/strategy/SoldierMoveStrategyTest.java b/src/test/java/domain/strategy/SoldierMoveStrategyTest.java index 90c67e8584..ec64496b05 100644 --- a/src/test/java/domain/strategy/SoldierMoveStrategyTest.java +++ b/src/test/java/domain/strategy/SoldierMoveStrategyTest.java @@ -1,8 +1,11 @@ package domain.strategy; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import domain.*; import domain.vo.Position; -import org.junit.jupiter.api.Assertions; +import java.util.HashMap; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,7 +23,7 @@ class SoldierMoveStrategyTest { Position targetPosition = Position.of(4, 4); // then - Assertions.assertTrue(soldierPiece.canMovePiece(position, targetPosition, Map.of())); + assertTrue(soldierPiece.canMovePiece(position, targetPosition, Map.of())); } @Test @@ -34,7 +37,8 @@ class SoldierMoveStrategyTest { Position targetPosition = Position.of(4, 4); // then - Assertions.assertFalse(soldierPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, sameTeamPiece))); + assertFalse( + soldierPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, sameTeamPiece))); } @Test @@ -48,7 +52,8 @@ class SoldierMoveStrategyTest { Position targetPosition = Position.of(4, 4); // then - Assertions.assertTrue(chuSoldierPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, hanSoldierPiece))); + assertTrue( + chuSoldierPiece.canMovePiece(position, targetPosition, Map.of(targetPosition, hanSoldierPiece))); } @Test @@ -61,7 +66,7 @@ class SoldierMoveStrategyTest { Position targetPosition = Position.of(2, 4); // then - Assertions.assertFalse(chuSoldierPiece.canMovePiece(position, targetPosition, Map.of())); + assertFalse(chuSoldierPiece.canMovePiece(position, targetPosition, Map.of())); } @Test @@ -70,10 +75,53 @@ class SoldierMoveStrategyTest { // given Piece hanSoldierPiece = Piece.of(Team.HAN, Type.SOLDIER); + // when Position position = Position.of(6, 4); Position targetPosition = Position.of(7, 4); // then - Assertions.assertFalse(hanSoldierPiece.canMovePiece(position, targetPosition, Map.of())); + assertFalse(hanSoldierPiece.canMovePiece(position, targetPosition, Map.of())); + } + + @Test + @DisplayName("한나라 졸의 이동 경로가 궁성 중앙(1, 4)에서 대각선 전진방향이면 이동한다.") + void 한나라_졸의_이동_경로가_궁성_중앙에서_대각선_전진방향이면_이동한다() { + // given + Piece hanSoldierPiece = Piece.of(Team.HAN, Type.SOLDIER); + Position from = Position.of(1, 4); + Position to = Position.of(0, 3); + HashMap piecesOnPath = new HashMap<>(); + piecesOnPath.put(to, null); + + //when // then + assertTrue(hanSoldierPiece.canMovePiece(from, to, piecesOnPath)); + } + + @Test + @DisplayName("한나라 졸의 이동 경로가 궁성 밖에서 대각선 전진이면 이동할 수 없다.") + void 한나라_졸의_이동_경로가_궁성_밖에서_대각선_전진이면_이동할_수_없다() { + // given + Piece hanSoldierPiece = Piece.of(Team.HAN, Type.SOLDIER); + Position from = Position.of(6, 4); + Position to = Position.of(5, 3); + HashMap piecesOnPath = new HashMap<>(); + piecesOnPath.put(to, null); + + // when // then + assertFalse(hanSoldierPiece.canMovePiece(from, to, piecesOnPath)); + } + + @Test + @DisplayName("초나라 졸의 궁성 안에서 궁성 밖 대각선 전진이면 이동할 수 없다.") + void 초나라_졸의_궁성_안에서_궁성_밖_대각선_전진이면_이동할_수_없다() { + // given + Piece chuSoldierPiece = Piece.of(Team.CHU, Type.SOLDIER); + Position from = Position.of(8, 3); + Position to = Position.of(9, 2); + HashMap piecesOnPath = new HashMap<>(); + piecesOnPath.put(to, null); + + // when // then + assertFalse(chuSoldierPiece.canMovePiece(from, to, piecesOnPath)); } }