From b653352d5e252f8cc6f66ed21a961c5fa362d1d2 Mon Sep 17 00:00:00 2001 From: Lucius Bachmann Date: Fri, 3 May 2024 15:06:35 +0200 Subject: [PATCH] sw_concepts: replace code examples slides with code examples in code Only seeing part of the example was difficult. And the slides did not offer enough space to show a meaningful part of an example. So now the students can browse a normal maven repo and look for the patterns they learned. --- topics/sw_concepts/CMakeLists.txt | 1 - .../sw_concepts/code/pattern-examples/pom.xml | 12 + .../pattern/examples/checkers/GameLogic.java | 127 ++++++ .../pattern/examples/checkers/Main.java | 63 +++ .../examples/checkers/MoveExecutor.java | 73 ++++ .../pattern/examples/checkers/README.md | 4 + .../examples/checkers/WinCondition.java | 51 +++ .../checkers/dom/BoardCoordinates.java | 52 +++ .../examples/checkers/dom/BoardObserver.java | 7 + .../checkers/dom/JumpGambleResult.java | 7 + .../pattern/examples/checkers/dom/Move.java | 108 +++++ .../pattern/examples/checkers/dom/Piece.java | 3 + .../pattern/examples/checkers/dom/Player.java | 6 + .../examples/checkers/dom/board/Board.java | 75 ++++ .../examples/checkers/dom/board/Command.java | 7 + .../dom/board/JumpGambleLostMove.java | 26 ++ .../examples/checkers/dom/board/JumpMove.java | 38 ++ .../checkers/dom/board/SimpleMove.java | 31 ++ .../examples/checkers/dom/board/Store.java | 120 +++++ .../movevalidator/MoveIsDiagonal.java | 18 + .../movevalidator/MoveIsForwardIfNotKing.java | 28 ++ .../checkers/movevalidator/MoveLength.java | 19 + .../checkers/movevalidator/MoveValidator.java | 8 + .../NoOtherMoveToJumpPossible.java | 65 +++ .../OpponentPieceBetweenJump.java | 22 + .../movevalidator/StartPieceValid.java | 25 ++ .../movevalidator/TargetFieldEmpty.java | 14 + .../examples/checkers/util/BoardPrinter.java | 60 +++ .../examples/checkers/util/CoinTosser.java | 33 ++ .../examples/checkers/util/Console.java | 30 ++ .../checkers/util/PointsCalculator.java | 49 +++ .../pattern/examples/checkers/util/Tuple.java | 13 + .../pattern/examples/gui/Button.java | 3 + .../pattern/examples/gui/ButtonFactory.java | 8 + .../pattern/examples/gui/FlatButton.java | 3 + .../examples/gui/FlatButtonFactory.java | 14 + .../pattern/examples/gui/RoundedButton.java | 3 + .../examples/gui/RoundedButtonFactory.java | 14 + .../receipts/Customer1ReceiptFactory.java | 22 + .../pattern/examples/receipts/Receipt.java | 3 + .../examples/receipts/ReceiptFactory.java | 10 + .../examples/checkers/GameLogicTest.java | 409 +++++++++++++++++ .../examples/checkers/MoveExecutorTest.java | 115 +++++ .../examples/checkers/WinConditionTest.java | 66 +++ .../examples/checkers/dom/BoardTest.java | 180 ++++++++ .../examples/checkers/dom/MoveTest.java | 168 +++++++ .../checkers/dom/board/StoreTest.java | 62 +++ .../movevalidator/MoveIsDiagonalTest.java | 97 +++++ .../movevalidator/MoveLengthTest.java | 109 +++++ .../NoOtherMoveToJumpPossibleTest.java | 100 +++++ .../OpponentPieceBetweenJumpTest.java | 86 ++++ .../movevalidator/StartPieceValidTest.java | 84 ++++ .../movevalidator/TargetFieldEmptyTest.java | 49 +++ .../checkers/util/BoardPrinterTest.java | 71 +++ .../checkers/util/CoinTosserTest.java | 88 ++++ .../checkers/util/PointsCalculatorTest.java | 46 ++ .../sw_concepts/sw_concept_code_examples.md | 410 ------------------ 57 files changed, 3004 insertions(+), 411 deletions(-) create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogic.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/Main.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutor.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/README.md create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/WinCondition.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardCoordinates.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardObserver.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/JumpGambleResult.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Move.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Piece.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Player.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Board.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Command.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpGambleLostMove.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpMove.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/SimpleMove.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Store.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonal.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsForwardIfNotKing.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLength.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveValidator.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossible.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJump.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValid.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmpty.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinter.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosser.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Console.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculator.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Tuple.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/Button.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/ButtonFactory.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButton.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButtonFactory.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButton.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButtonFactory.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Customer1ReceiptFactory.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Receipt.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/ReceiptFactory.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogicTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutorTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/WinConditionTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/MoveTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/StoreTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonalTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLengthTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossibleTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJumpTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValidTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmptyTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinterTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosserTest.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculatorTest.java delete mode 100644 topics/sw_concepts/sw_concept_code_examples.md diff --git a/topics/sw_concepts/CMakeLists.txt b/topics/sw_concepts/CMakeLists.txt index a8aa080..4066080 100644 --- a/topics/sw_concepts/CMakeLists.txt +++ b/topics/sw_concepts/CMakeLists.txt @@ -1,7 +1,6 @@ include(js_document) js_slides(sw_concept_slides sw_concept_slides.md) -js_slides(sw_concept_code_examples sw_concept_code_examples.md) file(GLOB_RECURSE code RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "code/*") js_add_to_global_archive_file_list(${code}) diff --git a/topics/sw_concepts/code/pattern-examples/pom.xml b/topics/sw_concepts/code/pattern-examples/pom.xml index 904fecd..447cf9d 100644 --- a/topics/sw_concepts/code/pattern-examples/pom.xml +++ b/topics/sw_concepts/code/pattern-examples/pom.xml @@ -17,6 +17,12 @@ + + org.assertj + assertj-core + 3.24.2 + test + org.junit.jupiter junit-jupiter-api @@ -35,6 +41,12 @@ 5.10.2 test + + org.mockito + mockito-core + 5.11.0 + test + diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogic.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogic.java new file mode 100644 index 0000000..8446f7d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogic.java @@ -0,0 +1,127 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.*; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.movevalidator.MoveValidator; +import ch.scs.jumpstart.pattern.examples.checkers.movevalidator.NoOtherMoveToJumpPossible; +import ch.scs.jumpstart.pattern.examples.checkers.util.Console; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("ClassCanBeRecord") +public class GameLogic { + private final Console console; + private final Board board; + private final List moveValidators; + private final MoveExecutor moveExecutor; + private final NoOtherMoveToJumpPossible noOtherMoveToJumpPossible; + private final WinCondition winCondition; + + public GameLogic( + Console console, + Board board, + List moveValidators, + MoveExecutor moveExecutor, + NoOtherMoveToJumpPossible noOtherMoveToJumpPossible, + WinCondition winCondition) { + this.console = console; + this.board = board; + this.moveValidators = moveValidators; + this.moveExecutor = moveExecutor; + this.noOtherMoveToJumpPossible = noOtherMoveToJumpPossible; + this.winCondition = winCondition; + } + + public void run() { + Player currentPlayer = Player.PLAYER_RED; + while (true) { + Player otherPlayer = + currentPlayer == Player.PLAYER_RED ? Player.PLAYER_WHITE : Player.PLAYER_RED; + console.print( + currentPlayer + + ", make your move. Or type 'undo' to go back to the start of the turn of " + + otherPlayer); + if (doPlayerMove(currentPlayer)) { + return; + } + if (currentPlayer == Player.PLAYER_WHITE) { + currentPlayer = Player.PLAYER_RED; + } else { + currentPlayer = Player.PLAYER_WHITE; + } + } + } + + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.CognitiveComplexity"}) + private boolean doPlayerMove(Player player) { + BoardCoordinates startCoordinatesForMultipleJump = null; + while (true) { + String userInput = console.getUserInput(); + if ("undo".equals(userInput.toLowerCase().trim())) { + try { + Player playerOfUndoneMove = board.undoLastTurn(); + if (playerOfUndoneMove.equals(player)) { + continue; + } else { + return false; + } + } catch (Board.NoPreviousMovesException e) { + console.print("There were no previous moves to undo. Please make a move."); + continue; + } + } + Move move; + try { + move = Move.parse(player, userInput); + } catch (IllegalArgumentException e) { + console.print("Invalid input, try again"); + continue; + } + if (startCoordinatesForMultipleJump != null + && !startCoordinatesForMultipleJump.equals(move.start())) { + console.print("For a multiple jump move, the same piece has to be used. Try again"); + continue; + } + Optional pieceAtStart = board.getPieceAt(move.start()); + if (!moveValidators.stream().allMatch(moveValidator -> moveValidator.validate(move, board))) { + console.print("Invalid move, try again"); + continue; + } + + Move executedMove = moveExecutor.executeMove(move); + + boolean hasPlayerWon = winCondition.hasPlayerWon(player, board); + if (hasPlayerWon) { + console.print("Congratulations, player " + player + " has won"); + return true; + } + + if (executedMove.jumpGambleResult() == JumpGambleResult.WON) { + console.print("The gamble has been won, " + player + " can play again."); + console.print( + player + ", make your move. Or type 'undo' to go back to the start of your turn."); + continue; + } + + Optional pieceAtEnd = board.getPieceAt(move.end()); + boolean wasKingCreated = wasKingCreated(pieceAtStart.orElse(null), pieceAtEnd.orElse(null)); + if (wasKingCreated) { + return false; + } + if (!move.isJumpMove() + || !noOtherMoveToJumpPossible.jumpMovePossibleFrom(move.end(), board)) { + return false; + } + console.print("Multiple jump move for " + player + ". Enter your next jump."); + console.print("Or type 'undo' to go back to the start of your turn."); + startCoordinatesForMultipleJump = move.end(); + } + } + + private boolean wasKingCreated(Piece pieceAtStart, Piece pieceAtEnd) { + if (pieceAtStart == null || pieceAtEnd == null) { + return false; + } + return !pieceAtStart.isKing() && pieceAtEnd.isKing(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/Main.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/Main.java new file mode 100644 index 0000000..b741d19 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/Main.java @@ -0,0 +1,63 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.movevalidator.*; +import ch.scs.jumpstart.pattern.examples.checkers.util.BoardPrinter; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser; +import ch.scs.jumpstart.pattern.examples.checkers.util.Console; +import ch.scs.jumpstart.pattern.examples.checkers.util.PointsCalculator; +import java.util.List; + +public class Main { + public static void main(String[] args) { + GameLogic gameLogic = + createGameLogic(Console.getInstance(), new CoinTosser(new PointsCalculator())); + + gameLogic.run(); + } + + public static GameLogic createGameLogic(Console console, CoinTosser coinTosser) { + Board board = new Board(); + BoardPrinter boardPrinter = new BoardPrinter(console); + board.registerObserver(boardPrinter::printBoard); + + MoveIsDiagonal moveIsDiagonal = new MoveIsDiagonal(); + MoveLength moveLength = new MoveLength(); + MoveIsForwardIfNotKing moveIsForwardIfNotKing = new MoveIsForwardIfNotKing(); + OpponentPieceBetweenJump opponentPieceBetweenJump = new OpponentPieceBetweenJump(); + TargetFieldEmpty targetFieldEmpty = new TargetFieldEmpty(); + NoOtherMoveToJumpPossible noOtherMoveToJumpPossible = + new NoOtherMoveToJumpPossible( + moveIsForwardIfNotKing, opponentPieceBetweenJump, targetFieldEmpty); + StartPieceValid startPieceValid = new StartPieceValid(); + + List moveValidators = + List.of( + moveIsDiagonal, + moveIsForwardIfNotKing, + moveLength, + noOtherMoveToJumpPossible, + opponentPieceBetweenJump, + startPieceValid, + targetFieldEmpty); + + WinCondition winCondition = + new WinCondition( + List.of( + moveIsDiagonal, + moveLength, + moveIsForwardIfNotKing, + opponentPieceBetweenJump, + targetFieldEmpty, + startPieceValid)); + + MoveExecutor moveExecutor = new MoveExecutor(board, coinTosser, console); + GameLogic gameLogic = + new GameLogic( + console, board, moveValidators, moveExecutor, noOtherMoveToJumpPossible, winCondition); + + console.print("Welcome to checkers"); + boardPrinter.printBoard(board); + return gameLogic; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutor.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutor.java new file mode 100644 index 0000000..61f0c6c --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutor.java @@ -0,0 +1,73 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import static ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result.HEADS; +import static ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result.TAILS; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser; +import ch.scs.jumpstart.pattern.examples.checkers.util.Console; + +@SuppressWarnings("ClassCanBeRecord") +public class MoveExecutor { + private final Board board; + private final CoinTosser coinTosser; + private final Console console; + + public MoveExecutor(Board board, CoinTosser coinTosser, Console console) { + this.board = board; + this.coinTosser = coinTosser; + this.console = console; + } + + public Move executeMove(Move move) { + if (!move.isJumpMove()) { + board.executeMove(move); + return move; + } + console.print(move.player() + " is making a jump move. Now " + move.player() + " may gamble."); + console.print("If " + move.player() + " does not gamble, the jump move is executed normally."); + console.print("If " + move.player() + " does gamble, a coin will be tossed."); + console.print( + "If the coin toss is " + + HEADS + + ", then the jump move will be executed, but " + + move.player() + + " gets another turn."); + console.print( + "If the coin toss is " + + TAILS + + ", then the jump move fails and the piece at " + + move.start() + + ", which would have been used for the jump move, is removed."); + GambleChoice gambleChoice = null; + do { + console.print( + move.player() + " type \"yes\" to gamble, \"no\" to execute the jump move normally"); + String userInput = console.getUserInput(); + try { + gambleChoice = GambleChoice.valueOf(userInput.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + console.print("The input of " + move.player() + " was invalid. Input was: " + userInput); + } + } while (gambleChoice == null); + if (gambleChoice == GambleChoice.NO) { + board.executeMove(move); + return move; + } + CoinTosser.Result tossResult = coinTosser.toss(board, move.player()); + JumpGambleResult jumpGambleResult = + tossResult == HEADS ? JumpGambleResult.WON : JumpGambleResult.LOST; + console.print("Coin toss resulted in " + tossResult + ", the gamble was: " + jumpGambleResult); + Move newMove = move.withJumpGambleResult(jumpGambleResult); + board.executeMove(newMove); + return newMove; + } + + private enum GambleChoice { + @SuppressWarnings("unused") + YES, + NO + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/README.md b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/README.md new file mode 100644 index 0000000..f3a62aa --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/README.md @@ -0,0 +1,4 @@ +# Checkers pattern examples + +These examples were taken from [Soco21-group8 checkersV3](https://github.com/soco21/soco21-group8/tree/main/assignment-3/checkersv3) +with the permission of it's author [@BacLuc](https://github.com/BacLuc) diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/WinCondition.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/WinCondition.java new file mode 100644 index 0000000..6b26655 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/WinCondition.java @@ -0,0 +1,51 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.movevalidator.MoveValidator; +import java.util.List; +import java.util.Optional; + +/** + * Check if one player cannot move or has no pieces. This can be done in one go, as if one player + * has no pieces, he cannot move. Use MoveValidators to check if any move is possible. + */ +@SuppressWarnings("ClassCanBeRecord") +public class WinCondition { + private final List moveValidators; + + public WinCondition(List moveValidators) { + this.moveValidators = moveValidators; + } + + public boolean hasPlayerWon(Player player, Board board) { + for (Row row : Row.values()) { + for (Column col : Column.values()) { + BoardCoordinates currentCoordinates = new BoardCoordinates(row, col); + Optional pieceAt = board.getPieceAt(currentCoordinates); + if (pieceAt.isEmpty()) { + continue; + } + Piece piece = pieceAt.get(); + if (piece.owner() == player) { + continue; + } + List possibleMoves = + Move.generatePossibleMoves(currentCoordinates, piece.owner(), List.of(1, 2)); + if (possibleMoves.stream() + .anyMatch( + move -> + moveValidators.stream() + .allMatch(moveValidator -> moveValidator.validate(move, board)))) { + return false; + } + } + } + return true; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardCoordinates.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardCoordinates.java new file mode 100644 index 0000000..1d33db9 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardCoordinates.java @@ -0,0 +1,52 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +public record BoardCoordinates(Row row, Column column) { + + public enum Row { + ROW_1, + ROW_2, + ROW_3, + ROW_4, + ROW_5, + ROW_6, + ROW_7, + ROW_8; + + public boolean isLastRow() { + return this == ROW_8; + } + + public boolean isFirstRow() { + return this == ROW_1; + } + + public int diffRow(Row row) { + return this.ordinal() - row.ordinal(); + } + + public Row getRowBetween(Row row) { + int indexBetween = (this.ordinal() + row.ordinal()) / 2; + return Row.values()[indexBetween]; + } + } + + public enum Column { + A, + B, + C, + D, + E, + F, + G, + H; + + public int diffCol(Column column) { + return this.ordinal() - column.ordinal(); + } + + public Column getColBetween(Column column) { + int indexBetween = (this.ordinal() + column.ordinal()) / 2; + return Column.values()[indexBetween]; + } + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardObserver.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardObserver.java new file mode 100644 index 0000000..73be190 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardObserver.java @@ -0,0 +1,7 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; + +public interface BoardObserver { + void boardChanged(Board board); +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/JumpGambleResult.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/JumpGambleResult.java new file mode 100644 index 0000000..2cb6afe --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/JumpGambleResult.java @@ -0,0 +1,7 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +public enum JumpGambleResult { + NO_GAMBLE, + WON, + LOST +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Move.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Move.java new file mode 100644 index 0000000..fe4f855 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Move.java @@ -0,0 +1,108 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.*; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public record Move( + Player player, + BoardCoordinates start, + BoardCoordinates end, + JumpGambleResult jumpGambleResult) { + public static Move parse(Player player, String string) { + String replacedString = string.replace("X", "x").replace("[", "").replace("]", ""); + String[] startAndEnd = replacedString.split("x"); + if (startAndEnd.length < 2) { + throw new IllegalArgumentException("invalid input"); + } + BoardCoordinates start = parseBoardCoordinates(startAndEnd[0]); + BoardCoordinates end = parseBoardCoordinates(startAndEnd[1]); + + return Move.of(player, start, end); + } + + public static Move of(Player player, BoardCoordinates start, BoardCoordinates end) { + return new Move(player, start, end, JumpGambleResult.NO_GAMBLE); + } + + public static List generatePossibleMoves( + BoardCoordinates boardCoordinates, Player pieceOwner, List distances) { + int rowIndex = boardCoordinates.row().ordinal(); + int colIndex = boardCoordinates.column().ordinal(); + List possibleJumpMoves = new ArrayList<>(); + + for (Integer distance : distances) { + of(pieceOwner, boardCoordinates, rowIndex + distance, colIndex + distance) + .ifPresent(possibleJumpMoves::add); + of(pieceOwner, boardCoordinates, rowIndex + distance, colIndex - distance) + .ifPresent(possibleJumpMoves::add); + of(pieceOwner, boardCoordinates, rowIndex - distance, colIndex + distance) + .ifPresent(possibleJumpMoves::add); + of(pieceOwner, boardCoordinates, rowIndex - distance, colIndex - distance) + .ifPresent(possibleJumpMoves::add); + } + return possibleJumpMoves; + } + + private static Optional of( + Player player, BoardCoordinates start, int rowIndex, int colIndex) { + Row[] rows = Row.values(); + Column[] columns = Column.values(); + + if (rowIndex >= 0 && rowIndex < rows.length && colIndex >= 0 && colIndex < columns.length) { + return Optional.of( + Move.of(player, start, new BoardCoordinates(rows[rowIndex], columns[colIndex]))); + } + return Optional.empty(); + } + + private static BoardCoordinates parseBoardCoordinates(String s) { + String[] columnAndRow = s.split(""); + if (columnAndRow.length != 2) { + throw new IllegalArgumentException("invalid input"); + } + String columnString = columnAndRow[0]; + Column column = Column.valueOf(columnString.toUpperCase()); + + String rowString = columnAndRow[1]; + for (Row row : Row.values()) { + int enumValue = row.ordinal() + 1; + if (rowString.equals(String.valueOf(enumValue))) { + return new BoardCoordinates(row, column); + } + } + throw new IllegalArgumentException("invalid input"); + } + + public Optional getCoordinatesBetween() { + int rowDiff = start.row().diffRow(end.row()); + int colDiff = start.column().diffCol(end.column()); + + if (Math.abs(rowDiff) != 2 || Math.abs(colDiff) != 2) { + return Optional.empty(); + } + + return Optional.of( + new BoardCoordinates( + start.row().getRowBetween(end.row()), start.column().getColBetween(end.column()))); + } + + public boolean isJumpMove() { + int diffMoveColumn = start.column().diffCol(end.column()); + int diffMoveRow = start.row().diffRow(end.row()); + return Math.abs(diffMoveRow) == 2 && Math.abs(diffMoveColumn) == 2; + } + + public Move withJumpGambleResult(JumpGambleResult jumpGambleResult) { + if (jumpGambleResult != JumpGambleResult.NO_GAMBLE && !isJumpMove()) { + throw new IllegalArgumentException( + "cannot create move with JumpGambleResult" + + jumpGambleResult + + ", when the move is no jump move"); + } + return new Move(player, start, end, jumpGambleResult); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Piece.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Piece.java new file mode 100644 index 0000000..c2d3cb3 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Piece.java @@ -0,0 +1,3 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +public record Piece(Player owner, boolean isKing) {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Player.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Player.java new file mode 100644 index 0000000..ff48a42 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Player.java @@ -0,0 +1,6 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +public enum Player { + PLAYER_WHITE, + PLAYER_RED +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Board.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Board.java new file mode 100644 index 0000000..74d4949 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Board.java @@ -0,0 +1,75 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.*; +import ch.scs.jumpstart.pattern.examples.checkers.util.Tuple; +import java.util.*; + +public class Board { + private final List boardObservers = new ArrayList<>(); + private final Store store = new Store(); + private final List> executedCommands = new ArrayList<>(); + + public void executeMove(Move move) { + Command command = createCommand(move); + command.execute(); + executedCommands.add(Tuple.of(move.player(), command)); + boardObservers.forEach(boardObserver -> boardObserver.boardChanged(this)); + } + + public Player undoLastTurn() throws NoPreviousMovesException { + if (executedCommands.isEmpty()) { + throw new NoPreviousMovesException(); + } + Tuple lastCommandTuple = executedCommands.get(executedCommands.size() - 1); + for (int i = executedCommands.size() - 1; i >= 0; i--) { + Tuple currentCommandTuple = executedCommands.get(i); + if (!currentCommandTuple.getKey().equals(lastCommandTuple.getKey())) { + break; + } + currentCommandTuple.getValue().undo(); + executedCommands.remove(i); + } + boardObservers.forEach(boardObserver -> boardObserver.boardChanged(this)); + return lastCommandTuple.getKey(); + } + + public Optional getPieceAt(BoardCoordinates boardCoordinates) { + return store.getPieceAt(boardCoordinates); + } + + public void registerObserver(BoardObserver boardObserver) { + boardObservers.add(boardObserver); + } + + private Command createCommand(Move move) { + Piece pieceAtStart = store.getPieceAt(move.start()).orElseThrow(); + Tuple start = Tuple.of(move.start(), pieceAtStart); + if (move.jumpGambleResult() == JumpGambleResult.LOST) { + return new JumpGambleLostMove(store, start); + } + + boolean convertToKing = isConvertToKing(move); + Piece pieceAtEnd = new Piece(move.player(), pieceAtStart.isKing() || convertToKing); + Tuple end = Tuple.of(move.end(), pieceAtEnd); + if (move.isJumpMove()) { + Piece pieceBetween = move.getCoordinatesBetween().flatMap(store::getPieceAt).orElseThrow(); + return new JumpMove( + store, start, Tuple.of(move.getCoordinatesBetween().orElseThrow(), pieceBetween), end); + } else { + return new SimpleMove(store, start, end); + } + } + + private boolean isConvertToKing(Move move) { + if (move.player() == Player.PLAYER_WHITE) { + return move.end().row().isLastRow(); + } + return move.end().row().isFirstRow(); + } + + public static class NoPreviousMovesException extends Exception { + private NoPreviousMovesException() { + super(); + } + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Command.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Command.java new file mode 100644 index 0000000..e4ad021 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Command.java @@ -0,0 +1,7 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +interface Command { + void execute(); + + void undo(); +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpGambleLostMove.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpGambleLostMove.java new file mode 100644 index 0000000..879fbd1 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpGambleLostMove.java @@ -0,0 +1,26 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.util.Tuple; + +@SuppressWarnings("ClassCanBeRecord") +class JumpGambleLostMove implements Command { + private final Store store; + private final Tuple start; + + JumpGambleLostMove(Store store, Tuple start) { + this.store = store; + this.start = start; + } + + @Override + public void execute() { + store.removePiece(start.getKey()); + } + + @Override + public void undo() { + store.addPiece(start.getKey(), start.getValue()); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpMove.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpMove.java new file mode 100644 index 0000000..aad2cc7 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpMove.java @@ -0,0 +1,38 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.util.Tuple; + +@SuppressWarnings("ClassCanBeRecord") +public class JumpMove implements Command { + private final Store store; + private final Tuple start; + private final Tuple pieceBetween; + private final Tuple end; + + public JumpMove( + Store store, + Tuple start, + Tuple pieceBetween, + Tuple end) { + this.store = store; + this.start = start; + this.pieceBetween = pieceBetween; + this.end = end; + } + + @Override + public void execute() { + store.removePiece(start.getKey()); + store.removePiece(pieceBetween.getKey()); + store.addPiece(end.getKey(), end.getValue()); + } + + @Override + public void undo() { + store.addPiece(start.getKey(), start.getValue()); + store.addPiece(pieceBetween.getKey(), pieceBetween.getValue()); + store.removePiece(end.getKey()); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/SimpleMove.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/SimpleMove.java new file mode 100644 index 0000000..caf6a7f --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/SimpleMove.java @@ -0,0 +1,31 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.util.Tuple; + +@SuppressWarnings("ClassCanBeRecord") +public class SimpleMove implements Command { + private final Store store; + private final Tuple start; + private final Tuple end; + + public SimpleMove( + Store store, Tuple start, Tuple end) { + this.store = store; + this.start = start; + this.end = end; + } + + @Override + public void execute() { + store.removePiece(start.getKey()); + store.addPiece(end.getKey(), end.getValue()); + } + + @Override + public void undo() { + store.addPiece(start.getKey(), start.getValue()); + store.removePiece(end.getKey()); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Store.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Store.java new file mode 100644 index 0000000..3b4bfb5 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Store.java @@ -0,0 +1,120 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.*; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +class Store { + final Map> boardMatrix = new HashMap<>(); + + Store() { + boardMatrix.put( + Row.ROW_1, + Map.of( + Column.A, + new Piece(Player.PLAYER_WHITE, false), + Column.C, + new Piece(Player.PLAYER_WHITE, false), + Column.E, + new Piece(Player.PLAYER_WHITE, false), + Column.G, + new Piece(Player.PLAYER_WHITE, false))); + boardMatrix.put( + Row.ROW_3, + Map.of( + Column.A, + new Piece(Player.PLAYER_WHITE, false), + Column.C, + new Piece(Player.PLAYER_WHITE, false), + Column.E, + new Piece(Player.PLAYER_WHITE, false), + Column.G, + new Piece(Player.PLAYER_WHITE, false))); + boardMatrix.put( + Row.ROW_2, + Map.of( + Column.B, + new Piece(Player.PLAYER_WHITE, false), + Column.D, + new Piece(Player.PLAYER_WHITE, false), + Column.F, + new Piece(Player.PLAYER_WHITE, false), + Column.H, + new Piece(Player.PLAYER_WHITE, false))); + + boardMatrix.put( + Row.ROW_8, + Map.of( + Column.B, + new Piece(Player.PLAYER_RED, false), + Column.D, + new Piece(Player.PLAYER_RED, false), + Column.F, + new Piece(Player.PLAYER_RED, false), + Column.H, + new Piece(Player.PLAYER_RED, false))); + boardMatrix.put( + Row.ROW_6, + Map.of( + Column.B, + new Piece(Player.PLAYER_RED, false), + Column.D, + new Piece(Player.PLAYER_RED, false), + Column.F, + new Piece(Player.PLAYER_RED, false), + Column.H, + new Piece(Player.PLAYER_RED, false))); + boardMatrix.put( + Row.ROW_7, + Map.of( + Column.A, + new Piece(Player.PLAYER_RED, false), + Column.C, + new Piece(Player.PLAYER_RED, false), + Column.E, + new Piece(Player.PLAYER_RED, false), + Column.G, + new Piece(Player.PLAYER_RED, false))); + } + + Optional getPieceAt(BoardCoordinates boardCoordinates) { + Map columnOptionalMap = boardMatrix.get(boardCoordinates.row()); + if (columnOptionalMap == null) { + return Optional.empty(); + } + return Optional.ofNullable(columnOptionalMap.get(boardCoordinates.column())); + } + + void removePiece(BoardCoordinates start) { + boardMatrix.compute( + start.row(), + (row, columnPieceMap) -> { + if (columnPieceMap == null) { + return new HashMap<>(); + } else { + HashMap columnPieceHashMap = new HashMap<>(columnPieceMap); + columnPieceHashMap.remove(start.column()); + return columnPieceHashMap; + } + }); + } + + void addPiece(BoardCoordinates boardCoordinates, Piece piece) { + boardMatrix.compute( + boardCoordinates.row(), + (row, columnPieceMap) -> { + if (columnPieceMap == null) { + return Map.of(boardCoordinates.column(), piece); + } else { + HashMap columnPieceHashMap = new HashMap<>(columnPieceMap); + columnPieceHashMap.put(boardCoordinates.column(), piece); + return columnPieceHashMap; + } + }); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonal.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonal.java new file mode 100644 index 0000000..bdbae8e --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonal.java @@ -0,0 +1,18 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; + +public class MoveIsDiagonal implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + BoardCoordinates start = move.start(); + BoardCoordinates end = move.end(); + boolean bothDirectionsChange = start.row() != end.row() && start.column() != end.column(); + + int rowDiff = Math.abs(start.row().ordinal() - end.row().ordinal()); + int colDiff = Math.abs(start.column().ordinal() - end.column().ordinal()); + return bothDirectionsChange && rowDiff == colDiff; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsForwardIfNotKing.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsForwardIfNotKing.java new file mode 100644 index 0000000..68c790b --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsForwardIfNotKing.java @@ -0,0 +1,28 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; + +public class MoveIsForwardIfNotKing implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + Optional pieceAt = board.getPieceAt(move.start()); + if (pieceAt.isEmpty()) { + return true; + } + Piece piece = pieceAt.get(); + if (piece.isKing()) { + return true; + } + if (piece.owner().equals(Player.PLAYER_WHITE)) { + return move.start().row().ordinal() < move.end().row().ordinal(); + + } else if (piece.owner().equals(Player.PLAYER_RED)) { + return move.start().row().ordinal() > move.end().row().ordinal(); + } + return false; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLength.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLength.java new file mode 100644 index 0000000..3380c0f --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLength.java @@ -0,0 +1,19 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; + +public class MoveLength implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + BoardCoordinates start = move.start(); + BoardCoordinates end = move.end(); + int diffMoveColumn = start.column().diffCol(end.column()); + int diffMoveRow = start.row().diffRow(end.row()); + if (diffMoveColumn == 0 && diffMoveRow == 0) { + return false; + } + return Math.abs(diffMoveRow) <= 2 && Math.abs(diffMoveColumn) <= 2; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveValidator.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveValidator.java new file mode 100644 index 0000000..42dc6b2 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveValidator.java @@ -0,0 +1,8 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; + +public interface MoveValidator { + boolean validate(Move move, Board board); +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossible.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossible.java new file mode 100644 index 0000000..6db87cf --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossible.java @@ -0,0 +1,65 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.*; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.List; +import java.util.Optional; + +public class NoOtherMoveToJumpPossible implements MoveValidator { + + private final List jumpValidators; + + public NoOtherMoveToJumpPossible( + MoveIsForwardIfNotKing moveIsForwardIfNotKings, + OpponentPieceBetweenJump opponentPieceBetweenJump, + TargetFieldEmpty targetFieldEmpty) { + jumpValidators = List.of(moveIsForwardIfNotKings, opponentPieceBetweenJump, targetFieldEmpty); + } + + @Override + public boolean validate(Move move, Board board) { + Row[] rows = Row.values(); + Column[] columns = Column.values(); + if (move.isJumpMove()) { + return true; + } + for (Row row : rows) { + for (Column col : columns) { + BoardCoordinates currentCoordinates = new BoardCoordinates(row, col); + Optional pieceAt = board.getPieceAt(currentCoordinates); + if (pieceAt.isEmpty()) { + continue; + } + if (pieceAt.get().owner() != move.player()) { + continue; + } + if (jumpMovePossibleFrom(currentCoordinates, board)) { + return false; + } + } + } + return true; + } + + public boolean jumpMovePossibleFrom(BoardCoordinates boardCoordinates, Board board) { + Optional pieceAt = board.getPieceAt(boardCoordinates); + if (pieceAt.isEmpty()) { + return false; + } + Player pieceOwner = pieceAt.get().owner(); + + List possibleJumpMoves = + Move.generatePossibleMoves(boardCoordinates, pieceOwner, List.of(2)); + + return possibleJumpMoves.stream() + .anyMatch( + move -> + jumpValidators.stream() + .allMatch(moveValidator -> moveValidator.validate(move, board))); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJump.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJump.java new file mode 100644 index 0000000..99e77f8 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJump.java @@ -0,0 +1,22 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; + +public class OpponentPieceBetweenJump implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + Optional coordinatesBetween = move.getCoordinatesBetween(); + if (coordinatesBetween.isEmpty()) { + return true; + } + Optional pieceBetweenJump = board.getPieceAt(coordinatesBetween.get()); + if (pieceBetweenJump.isEmpty()) { + return false; + } + return pieceBetweenJump.get().owner() != move.player(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValid.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValid.java new file mode 100644 index 0000000..aa7b22b --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValid.java @@ -0,0 +1,25 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; + +public class StartPieceValid implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + BoardCoordinates start = move.start(); + + BoardCoordinates.Row[] row = BoardCoordinates.Row.values(); + BoardCoordinates.Column[] col = BoardCoordinates.Column.values(); + + Optional startPiece = board.getPieceAt(start); + + if (startPiece.isEmpty()) { + return false; + } + + return startPiece.get().owner() == move.player(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmpty.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmpty.java new file mode 100644 index 0000000..ae41a8d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmpty.java @@ -0,0 +1,14 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; + +public class TargetFieldEmpty implements MoveValidator { + @Override + public boolean validate(Move move, Board board) { + Optional pieceAt = board.getPieceAt(move.end()); + return pieceAt.isEmpty(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinter.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinter.java new file mode 100644 index 0000000..e8742c0 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinter.java @@ -0,0 +1,60 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.*; + +public class BoardPrinter { + private final Console console; + + public BoardPrinter(Console console) { + this.console = console; + } + + @SuppressWarnings("PMD.CognitiveComplexity") + public void printBoard(Board board) { + StringBuilder header = new StringBuilder(); + header.append(" "); + for (Column col : Column.values()) { + header.append(col.name().toLowerCase(Locale.ROOT)).append(" "); + } + console.print(header.toString()); + console.print(" +-------------------------------------------------+"); + + List rows = Arrays.asList(Row.values()); + Collections.reverse(rows); + for (Row row : rows) { + StringBuilder rowString = new StringBuilder((row.ordinal() + 1) + " |"); + for (Column col : Column.values()) { + Optional pieceAt = board.getPieceAt(new BoardCoordinates(row, col)); + rowString.append(" "); + if (pieceAt.isEmpty()) { + rowString.append("[ ]"); + } else { + Piece piece = pieceAt.get(); + rowString.append("["); + if (piece.owner() == Player.PLAYER_WHITE) { + rowString.append("W"); + } else { + rowString.append("R"); + } + rowString.append("_"); + if (piece.isKing()) { + rowString.append("K"); + } else { + rowString.append("P"); + } + rowString.append("]"); + } + } + console.print(rowString + " | " + (row.ordinal() + 1)); + } + + console.print(" +-------------------------------------------------+"); + console.print(header.toString()); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosser.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosser.java new file mode 100644 index 0000000..2eadbb6 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosser.java @@ -0,0 +1,33 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Map; +import java.util.Random; + +public class CoinTosser { + private final PointsCalculator pointsCalculator; + private final Random random; + + public CoinTosser(PointsCalculator pointsCalculator) { + this(pointsCalculator, new Random()); + } + + CoinTosser(PointsCalculator pointsCalculator, Random random) { + this.pointsCalculator = pointsCalculator; + this.random = random; + } + + public Result toss(Board board, Player player) { + Map playerPointsMap = pointsCalculator.calculatePoints(board); + int totalPoints = + playerPointsMap.get(Player.PLAYER_RED) + playerPointsMap.get(Player.PLAYER_WHITE); + float winChance = 1 - playerPointsMap.get(player).floatValue() / totalPoints; + return random.nextFloat() <= winChance ? Result.HEADS : Result.TAILS; + } + + public enum Result { + HEADS, + TAILS + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Console.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Console.java new file mode 100644 index 0000000..a047b3e --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Console.java @@ -0,0 +1,30 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Console { + private static final Console instance = new Console(); + + public static Console getInstance() { + return instance; + } + + private Console() {} + + @SuppressWarnings("PMD.SystemPrintln") + public void print(String string) { + System.out.println(string); + } + + public String getUserInput() { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + + try { + return reader.readLine(); + } catch (IOException e) { + return ""; + } + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculator.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculator.java new file mode 100644 index 0000000..1bc27b9 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculator.java @@ -0,0 +1,49 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.Player.PLAYER_RED; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.Player.PLAYER_WHITE; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class PointsCalculator { + + @SuppressWarnings("PMD.CognitiveComplexity") + public Map calculatePoints(Board board) { + int pointsPlayerWhite = 0; + int pointsPlayerRed = 0; + BoardCoordinates.Row[] rows = BoardCoordinates.Row.values(); + BoardCoordinates.Column[] columns = BoardCoordinates.Column.values(); + for (BoardCoordinates.Row row : rows) { + for (BoardCoordinates.Column col : columns) { + BoardCoordinates currentCoordinates = new BoardCoordinates(row, col); + Optional pieceAt = board.getPieceAt(currentCoordinates); + if (pieceAt.isPresent()) { + if (pieceAt.get().owner() == PLAYER_WHITE) { + if (pieceAt.get().isKing()) { + pointsPlayerWhite += 2; + } else { + pointsPlayerWhite += 1; + } + } else { + if (pieceAt.get().isKing()) { + pointsPlayerRed += 2; + } else { + pointsPlayerRed += 1; + } + } + } + } + } + HashMap pointsOnBoard = new HashMap<>(); + pointsOnBoard.put(PLAYER_WHITE, pointsPlayerWhite); + pointsOnBoard.put(PLAYER_RED, pointsPlayerRed); + + return pointsOnBoard; + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Tuple.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Tuple.java new file mode 100644 index 0000000..11c1fb3 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Tuple.java @@ -0,0 +1,13 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import java.util.AbstractMap; + +public class Tuple extends AbstractMap.SimpleImmutableEntry { + public static Tuple of(K key, V value) { + return new Tuple<>(key, value); + } + + private Tuple(K key, V value) { + super(key, value); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/Button.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/Button.java new file mode 100644 index 0000000..4ba1e8d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/Button.java @@ -0,0 +1,3 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +public interface Button {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/ButtonFactory.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/ButtonFactory.java new file mode 100644 index 0000000..63da870 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/ButtonFactory.java @@ -0,0 +1,8 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +@SuppressWarnings("unused") +public interface ButtonFactory { + Button createBackButton(); + + Button createForwardButton(); +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButton.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButton.java new file mode 100644 index 0000000..64580b8 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButton.java @@ -0,0 +1,3 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +public record FlatButton(String buttonName) implements Button {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButtonFactory.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButtonFactory.java new file mode 100644 index 0000000..d1cf12b --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButtonFactory.java @@ -0,0 +1,14 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +@SuppressWarnings("unused") +public class FlatButtonFactory implements ButtonFactory { + @Override + public Button createBackButton() { + return new FlatButton("back"); + } + + @Override + public Button createForwardButton() { + return new FlatButton("forward"); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButton.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButton.java new file mode 100644 index 0000000..00751de --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButton.java @@ -0,0 +1,3 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +public record RoundedButton(String buttonName) implements Button {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButtonFactory.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButtonFactory.java new file mode 100644 index 0000000..cc33a6d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButtonFactory.java @@ -0,0 +1,14 @@ +package ch.scs.jumpstart.pattern.examples.gui; + +@SuppressWarnings("unused") +public class RoundedButtonFactory implements ButtonFactory { + @Override + public Button createBackButton() { + return new RoundedButton("back"); + } + + @Override + public Button createForwardButton() { + return new RoundedButton("forward"); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Customer1ReceiptFactory.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Customer1ReceiptFactory.java new file mode 100644 index 0000000..930e22b --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Customer1ReceiptFactory.java @@ -0,0 +1,22 @@ +package ch.scs.jumpstart.pattern.examples.receipts; + +// details omitted +public class Customer1ReceiptFactory implements ReceiptFactory { + @Override + public Receipt createEftReceipt() { + // details omitted + return new Receipt(); + } + + @Override + public Receipt createRefundReceipt() { + throw new UnsupportedOperationException( + "%s does not support refund".formatted(getClass().getSimpleName())); + } + + @Override + public Receipt createContactlessReceipt() { + // details omitted + return new Receipt(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Receipt.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Receipt.java new file mode 100644 index 0000000..76dc0f4 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Receipt.java @@ -0,0 +1,3 @@ +package ch.scs.jumpstart.pattern.examples.receipts; + +public class Receipt {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/ReceiptFactory.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/ReceiptFactory.java new file mode 100644 index 0000000..6d3e9f4 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/ReceiptFactory.java @@ -0,0 +1,10 @@ +package ch.scs.jumpstart.pattern.examples.receipts; + +@SuppressWarnings("unused") +public interface ReceiptFactory { + Receipt createEftReceipt(); + + Receipt createRefundReceipt(); + + Receipt createContactlessReceipt(); +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogicTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogicTest.java new file mode 100644 index 0000000..41d6d15 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogicTest.java @@ -0,0 +1,409 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result; +import ch.scs.jumpstart.pattern.examples.checkers.util.Console; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +class GameLogicTest { + + private CoinTosser coinTosser; + private GameLogic gameLogic; + private Console console; + + @BeforeEach + void setup() { + console = mock(Console.class); + doCallRealMethod().when(console).print(notNull()); + coinTosser = mock(CoinTosser.class); + gameLogic = Main.createGameLogic(console, coinTosser); + } + + @Test + void end_game_with_winner() { + // numbers were taken from: http://www.quadibloc.com/other/bo1211.htm + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(23, 19)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(8, 11)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(22, 17)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(11, 16)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(24, 20)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(16, 23)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(27, 18)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(18, 11)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(7, 16)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(20, 11)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(3, 7)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(28, 24)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(7, 16)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(24, 20)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(16, 19)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(25, 22)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(4, 8)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(29, 25)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(9, 14)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(22, 18)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(14, 23)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(17, 14)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(10, 17)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(21, 14)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(2, 7)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(31, 27)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(6, 10)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(27, 18)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(10, 17)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(25, 21)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(1, 6)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(21, 14)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(6, 10)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(30, 25)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(10, 17)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(25, 21)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(19, 23)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(26, 19)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(17, 22)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(19, 15)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(22, 26)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(18, 14)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(26, 31)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(15, 10)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(5, 9)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(10, 3)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(9, 18)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(21, 17)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(18, 22)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(17, 14)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(22, 26)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(20, 16)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(12, 19)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(3, 12)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(26, 30)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(12, 16)) + .thenReturn("no") + + // Added moves that white wins + // RED + .thenReturn(fromNumbers(30, 26)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(16, 23)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(23, 30)) + .thenReturn("no") + // RED + .thenReturn(fromNumbers(31, 27)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(32, 23)) + .thenReturn("no"); + + gameLogic.run(); + + verify(console).print("Congratulations, player " + Player.PLAYER_WHITE + " has won"); + } + + @Test + void only_allow_multiple_jump_with_same_piece() { + // numbers were taken from: http://www.quadibloc.com/other/bo1211.htm + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + // WHITE + .thenReturn(fromNumbers(23, 19)) + // RED + .thenReturn(fromNumbers(8, 11)) + // WHITE + .thenReturn(fromNumbers(22, 17)) + // RED + .thenReturn(fromNumbers(11, 16)) + // WHITE + .thenReturn(fromNumbers(24, 20)) + // RED + .thenReturn(fromNumbers(16, 23)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(27, 18)) + .thenReturn("no") + // WHITE + .thenReturn(fromNumbers(26, 11)) + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + verify(console).print("For a multiple jump move, the same piece has to be used. Try again"); + } + + @Test + void let_same_player_play_again_if_jumpgamble_is_won() { + when(coinTosser.toss(notNull(), notNull())).thenReturn(Result.HEADS); + // numbers were taken from: http://www.quadibloc.com/other/bo1211.htm + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + // WHITE + .thenReturn(fromNumbers(23, 19)) + // RED + .thenReturn(fromNumbers(8, 11)) + // WHITE + .thenReturn(fromNumbers(22, 17)) + // RED + .thenReturn(fromNumbers(11, 16)) + // WHITE + .thenReturn(fromNumbers(24, 20)) + // RED + .thenReturn(fromNumbers(16, 23)) + .thenReturn("yes") + // RED + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + verify(console).print("The gamble has been won, PLAYER_RED can play again."); + } + + @Test + void switch_player_if_jumpgamble_is_lost() { + when(coinTosser.toss(notNull(), notNull())).thenReturn(Result.TAILS); + InOrder inOrder = inOrder(console); + // numbers were taken from: http://www.quadibloc.com/other/bo1211.htm + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + // WHITE + .thenReturn(fromNumbers(23, 19)) + // RED + .thenReturn(fromNumbers(8, 11)) + // WHITE + .thenReturn(fromNumbers(22, 17)) + // RED + .thenReturn(fromNumbers(11, 16)) + // WHITE + .thenReturn(fromNumbers(24, 20)) + // RED + .thenReturn(fromNumbers(16, 23)) + .thenReturn("yes") + // RED + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + inOrder.verify(console).print("Coin toss resulted in TAILS, the gamble was: LOST"); + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [R_P] [ ] [ ] [ ] | 5"); + } + + @Test + void show_error_message_if_undo_is_done_at_start_of_the_game() { + when(console.getUserInput()) + // RED + .thenReturn("undo") + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + verify(console).print("There were no previous moves to undo. Please make a move."); + } + + @Test + void undo_previous_turn_if_undo_was_used_at_start_of_turn() { + InOrder inOrder = inOrder(console); + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + // WHITE + .thenReturn(fromNumbers(23, 19)) + // RED + .thenReturn("undo") + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + inOrder.verify(console).print("4 | [ ] [ ] [ ] [ ] [ ] [W_P] [ ] [ ] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [W_P] [ ] [ ] [ ] [W_P] [ ] | 3"); + inOrder + .verify(console) + .print( + "PLAYER_RED, make your move. Or type 'undo' to go back to the start of the turn of PLAYER_WHITE"); + inOrder.verify(console).print("4 | [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] | 3"); + } + + @Test + void undo_current_move_if_player_inputs_undo_during_turn() { + when(coinTosser.toss(notNull(), notNull())).thenReturn(Result.HEADS); + InOrder inOrder = inOrder(console); + // numbers were taken from: http://www.quadibloc.com/other/bo1211.htm + when(console.getUserInput()) + // RED + .thenReturn(fromNumbers(11, 15)) + // WHITE + .thenReturn(fromNumbers(23, 19)) + // RED + .thenReturn(fromNumbers(8, 11)) + // WHITE + .thenReturn(fromNumbers(22, 17)) + // RED + .thenReturn(fromNumbers(11, 16)) + // WHITE + .thenReturn(fromNumbers(24, 20)) + // RED + .thenReturn(fromNumbers(16, 23)) + .thenReturn("yes") + // RED + .thenReturn("undo") + .thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, gameLogic::run); + + // start of move + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [R_P] [ ] [R_P] [ ] | 5"); + inOrder.verify(console).print("4 | [ ] [W_P] [ ] [ ] [ ] [W_P] [ ] [W_P] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 3"); + // jump move + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [R_P] [ ] [ ] [ ] | 5"); + inOrder.verify(console).print("4 | [ ] [W_P] [ ] [ ] [ ] [ ] [ ] [W_P] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [ ] [ ] [R_P] [ ] [ ] [ ] | 3"); + // undo + inOrder + .verify(console) + .print("PLAYER_RED, make your move. Or type 'undo' to go back to the start of your turn."); + // board back at same state as start + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [R_P] [ ] [R_P] [ ] | 5"); + inOrder.verify(console).print("4 | [ ] [W_P] [ ] [ ] [ ] [W_P] [ ] [W_P] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 3"); + } + + private static String fromNumbers(int start, int end) { + List numberCoordinatesMap = + List.of( + "B8", "D8", "F8", "H8", "A7", "C7", "E7", "G7", "B6", "D6", "F6", "H6", "A5", "C5", + "E5", "G5", "B4", "D4", "F4", "H4", "A3", "C3", "E3", "G3", "B2", "D2", "F2", "H2", + "A1", "C1", "E1", "G1"); + return String.format( + "%sx%s", numberCoordinatesMap.get(start - 1), numberCoordinatesMap.get(end - 1)); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutorTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutorTest.java new file mode 100644 index 0000000..8bde14d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutorTest.java @@ -0,0 +1,115 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser; +import ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result; +import ch.scs.jumpstart.pattern.examples.checkers.util.Console; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MoveExecutorTest { + + private static final Move NORMAL_MOVE = + Move.of(Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + private static final Move JUMP_MOVE = + Move.of(Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + private static final Move JUMP_WITH_WON_GAMBLE_RESULT = + JUMP_MOVE.withJumpGambleResult(JumpGambleResult.WON); + private static final Move JUMP_WITH_LOST_GAMBLE_RESULT = + JUMP_MOVE.withJumpGambleResult(JumpGambleResult.LOST); + private CoinTosser coinTosser; + private Board board; + private MoveExecutor moveExecutor; + private Console console; + + @BeforeEach + void setup() { + coinTosser = mock(CoinTosser.class); + board = mock(Board.class); + console = mock(Console.class); + moveExecutor = new MoveExecutor(board, coinTosser, console); + } + + @Test + void execute_move_normally_if_no_jump_move() { + Move move = NORMAL_MOVE; + + Move executedMove = moveExecutor.executeMove(move); + + verify(board).executeMove(same(move)); + assertThat(executedMove).isSameAs(move); + } + + @Test + void ask_player_if_he_wants_to_gamble_for_jump_move() { + when(console.getUserInput()).thenThrow(RuntimeException.class); + + Assertions.assertThrows(RuntimeException.class, () -> moveExecutor.executeMove(JUMP_MOVE)); + + verify(console).getUserInput(); + } + + @Test + void ask_player_if_he_wants_to_gamble_until_he_types_yes() { + when(console.getUserInput()).thenReturn("blabla").thenReturn("invalid").thenReturn("yes"); + + moveExecutor.executeMove(JUMP_MOVE); + + verify(console, times(3)).getUserInput(); + } + + @Test + void ask_player_if_he_wants_to_gamble_until_he_types_no() { + when(console.getUserInput()).thenReturn("blabla").thenReturn("invalid").thenReturn("no"); + + moveExecutor.executeMove(JUMP_MOVE); + + verify(console, times(3)).getUserInput(); + } + + @Test + void execute_move_normally_if_player_types_no() { + when(console.getUserInput()).thenReturn("no"); + + Move executedMove = moveExecutor.executeMove(JUMP_MOVE); + + verify(board).executeMove(same(JUMP_MOVE)); + assertThat(executedMove).isSameAs(JUMP_MOVE); + } + + @Test + void execute_gamble_win_move_if_player_types_yes_and_wins() { + when(console.getUserInput()).thenReturn("yes"); + when(coinTosser.toss(notNull(), notNull())).thenReturn(Result.HEADS); + + Move executedMove = moveExecutor.executeMove(JUMP_MOVE); + + verify(board).executeMove(eq(JUMP_WITH_WON_GAMBLE_RESULT)); + assertThat(executedMove).isEqualTo(JUMP_WITH_WON_GAMBLE_RESULT); + } + + @Test + void execute_gamble_lost_move_if_player_types_yes_and_loses() { + when(console.getUserInput()).thenReturn("yes"); + when(coinTosser.toss(notNull(), notNull())).thenReturn(Result.TAILS); + + Move executedMove = moveExecutor.executeMove(JUMP_MOVE); + + verify(board).executeMove(eq(JUMP_WITH_LOST_GAMBLE_RESULT)); + assertThat(executedMove).isEqualTo(JUMP_WITH_LOST_GAMBLE_RESULT); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/WinConditionTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/WinConditionTest.java new file mode 100644 index 0000000..150b532 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/WinConditionTest.java @@ -0,0 +1,66 @@ +package ch.scs.jumpstart.pattern.examples.checkers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import ch.scs.jumpstart.pattern.examples.checkers.movevalidator.MoveValidator; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class WinConditionTest { + + private static final Piece WHITE_PAWN = new Piece(Player.PLAYER_WHITE, false); + private static final Piece RED_PAWN = new Piece(Player.PLAYER_RED, false); + private MoveValidator moveValidator1; + private MoveValidator moveValidator2; + private WinCondition winCondition; + private Board board; + + @BeforeEach + void setup() { + moveValidator1 = mock(MoveValidator.class); + moveValidator2 = mock(MoveValidator.class); + winCondition = new WinCondition(List.of(moveValidator1, moveValidator2)); + + board = mock(Board.class); + } + + @Test + void player_has_won_if_other_player_has_no_pieces() { + when(board.getPieceAt(notNull())).thenReturn(Optional.empty()); + when(board.getPieceAt(new BoardCoordinates(Row.ROW_4, Column.E))) + .thenReturn(Optional.of(RED_PAWN)); + + assertThat(winCondition.hasPlayerWon(Player.PLAYER_RED, board)).isTrue(); + } + + @Test + void player_has_not_won_if_other_player_can_move_a_piece() { + when(board.getPieceAt(new BoardCoordinates(Row.ROW_4, Column.E))) + .thenReturn(Optional.of(WHITE_PAWN)); + when(moveValidator1.validate(notNull(), notNull())).thenReturn(true); + when(moveValidator2.validate(notNull(), notNull())).thenReturn(true); + + assertThat(winCondition.hasPlayerWon(Player.PLAYER_RED, board)).isFalse(); + } + + @Test + void player_has_won_if_other_player_has_a_piece_but_cannot_move_it() { + when(board.getPieceAt(new BoardCoordinates(Row.ROW_4, Column.E))) + .thenReturn(Optional.of(WHITE_PAWN)); + when(moveValidator1.validate(notNull(), notNull())).thenReturn(false); + when(moveValidator2.validate(notNull(), notNull())).thenReturn(true); + + assertThat(winCondition.hasPlayerWon(Player.PLAYER_RED, board)).isTrue(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardTest.java new file mode 100644 index 0000000..3d07b8c --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardTest.java @@ -0,0 +1,180 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +import static java.util.Optional.empty; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class BoardTest { + @Test + void move_a_piece_from_a_to_b() { + Board board = new Board(); + Move move = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.A), + new BoardCoordinates(Row.ROW_4, Column.B)); + + board.executeMove(move); + + assertThat(board.getPieceAt(move.start())).isEmpty(); + assertThat(board.getPieceAt(move.end())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_WHITE, false))); + } + + @Test + void remove_piece_between_jump_move() { + Board board = new Board(); + Move move1 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.A), + new BoardCoordinates(Row.ROW_4, Column.B)); + Move move2 = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_6, Column.B), + new BoardCoordinates(Row.ROW_5, Column.C)); + Move move3 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.C), + new BoardCoordinates(Row.ROW_4, Column.D)); + Move jumpMove = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_5, Column.C), + new BoardCoordinates(Row.ROW_3, Column.A)); + + List.of(move1, move2, move3, jumpMove).forEach(board::executeMove); + + assertThat(board.getPieceAt(move1.start())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_RED, false))); + assertThat(board.getPieceAt(move1.end())).isEqualTo(empty()); + } + + @Test + void convert_piece_to_king_if_at_end_for_white() { + Board board = new Board(); + // this move is not valid, but the board doesn't know that + Move move = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.A), + new BoardCoordinates(Row.ROW_8, Column.B)); + + board.executeMove(move); + + assertThat(board.getPieceAt(move.start())).isEqualTo(empty()); + assertThat(board.getPieceAt(move.end())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_WHITE, true))); + } + + @Test + void convert_piece_to_king_if_at_end_for_red() { + Board board = new Board(); + // this move is not valid, but the board doesn't know that + Move move = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_6, Column.B), + new BoardCoordinates(Row.ROW_1, Column.B)); + + board.executeMove(move); + + assertThat(board.getPieceAt(move.start())).isEqualTo(empty()); + assertThat(board.getPieceAt(move.end())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_RED, true))); + } + + @Test + void piece_stays_king_if_it_was_king() { + Board board = new Board(); + // this move is not valid, but the board doesn't know that + Move move1 = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_6, Column.B), + new BoardCoordinates(Row.ROW_1, Column.B)); + Move move2 = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_1, Column.B), + new BoardCoordinates(Row.ROW_2, Column.C)); + + List.of(move1, move2).forEach(board::executeMove); + + assertThat(board.getPieceAt(move1.start())).isEmpty(); + assertThat(board.getPieceAt(move1.end())).isEmpty(); + assertThat(board.getPieceAt(move2.end())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_RED, true))); + } + + @Test + void remove_start_piece_when_JumpGambleResult_is_lost() { + Board board = new Board(); + Move move1 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.A), + new BoardCoordinates(Row.ROW_4, Column.B)); + Move move2 = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_6, Column.B), + new BoardCoordinates(Row.ROW_5, Column.C)); + Move move3 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.C), + new BoardCoordinates(Row.ROW_4, Column.D)); + Move jumpMove = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_5, Column.C), + new BoardCoordinates(Row.ROW_3, Column.A)) + .withJumpGambleResult(JumpGambleResult.LOST); + + List.of(move1, move2, move3, jumpMove).forEach(board::executeMove); + + assertThat(board.getPieceAt(jumpMove.start())).isEmpty(); + assertThat(board.getPieceAt(jumpMove.getCoordinatesBetween().orElseThrow())).isNotEmpty(); + } + + @Test + void execute_normal_jump_move_if_JumpGambleResult_is_won() { + Board board = new Board(); + Move move1 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.A), + new BoardCoordinates(Row.ROW_4, Column.B)); + Move move2 = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_6, Column.B), + new BoardCoordinates(Row.ROW_5, Column.C)); + Move move3 = + Move.of( + Player.PLAYER_WHITE, + new BoardCoordinates(Row.ROW_3, Column.C), + new BoardCoordinates(Row.ROW_4, Column.D)); + Move jumpMove = + Move.of( + Player.PLAYER_RED, + new BoardCoordinates(Row.ROW_5, Column.C), + new BoardCoordinates(Row.ROW_3, Column.A)) + .withJumpGambleResult(JumpGambleResult.WON); + + List.of(move1, move2, move3, jumpMove).forEach(board::executeMove); + + assertThat(board.getPieceAt(move1.start())) + .isEqualTo(Optional.of(new Piece(Player.PLAYER_RED, false))); + assertThat(board.getPieceAt(move1.end())).isEmpty(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/MoveTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/MoveTest.java new file mode 100644 index 0000000..eabf88d --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/MoveTest.java @@ -0,0 +1,168 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class MoveTest { + @Test + void throw_for_empty_string() { + assertThrows(IllegalArgumentException.class, () -> Move.parse(Player.PLAYER_WHITE, "")); + } + + @Test + void throw_if_string_contains_only_delimiter() { + assertThrows(IllegalArgumentException.class, () -> Move.parse(Player.PLAYER_WHITE, "x")); + } + + @Test + void throw_if_string_contains_only_move_start() { + assertThrows(IllegalArgumentException.class, () -> Move.parse(Player.PLAYER_WHITE, "a3x")); + } + + @ParameterizedTest + @ValueSource( + strings = { + "a9xb3", "i9xb3", "y9xb3", "a9x33", "a9xä3", + }) + void throw_if_string_contains_invalid_column(String input) { + assertThrows(IllegalArgumentException.class, () -> Move.parse(Player.PLAYER_WHITE, input)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "a9xb3", "a0xb3", "a-1xb3", "a0.5xb3", "a xb3", + }) + void throw_if_string_contains_invalid_row(String input) { + assertThrows(IllegalArgumentException.class, () -> Move.parse(Player.PLAYER_WHITE, input)); + } + + @Test + void parse_correct_move() { + Move expectedMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_3, A), new BoardCoordinates(ROW_4, B)); + assertThat(Move.parse(Player.PLAYER_WHITE, "a3xb4")).isEqualTo(expectedMove); + assertThat(Move.parse(Player.PLAYER_WHITE, "A3xB4")).isEqualTo(expectedMove); + assertThat(Move.parse(Player.PLAYER_WHITE, "a3Xb4")).isEqualTo(expectedMove); + assertThat(Move.parse(Player.PLAYER_WHITE, "[a3]Xb4")).isEqualTo(expectedMove); + assertThat(Move.parse(Player.PLAYER_WHITE, "[a3Xb4")).isEqualTo(expectedMove); + assertThat(Move.parse(Player.PLAYER_WHITE, "[a3]X[b4]")).isEqualTo(expectedMove); + } + + @Test + void generate_possible_moves() { + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_4, D), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(8); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_1, A), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(2); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_8, A), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(2); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_8, H), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(2); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_1, H), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(2); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_1, G), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(3); + assertThat( + Move.generatePossibleMoves( + new BoardCoordinates(ROW_1, F), Player.PLAYER_WHITE, List.of(1, 2))) + .hasSize(4); + } + + @Test + void return_empty_for_getCoordinatesBetween_for_simple_move() { + Move simpleMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(simpleMove.getCoordinatesBetween()).isEmpty(); + } + + @Test + void calculate_correct_coordinates_between_for_jump_move() { + Move jumpMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(jumpMove.getCoordinatesBetween()) + .isEqualTo(Optional.of(new BoardCoordinates(ROW_2, B))); + } + + @Test + void return_true_for_isJumpMove_on_jump_move() { + Move jumpMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(jumpMove.isJumpMove()).isTrue(); + } + + @Test + void return_false_for_isJumpMove_on_move_from_same_position_to_same_position() { + Move jumpMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, A)); + assertThat(jumpMove.isJumpMove()).isFalse(); + } + + @Test + void return_false_for_isJumpMove_on_simple_move() { + Move simpleMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(simpleMove.isJumpMove()).isFalse(); + } + + @Test + void throw_if_withJumpGambleResult_called_with_WON_on_simple_move() { + Move simpleMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThrows(IllegalArgumentException.class, () -> simpleMove.withJumpGambleResult(WON)); + } + + @Test + void throw_if_withJumpGambleResult_called_with_LOST_on_simple_move() { + Move simpleMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThrows(IllegalArgumentException.class, () -> simpleMove.withJumpGambleResult(LOST)); + } + + @Test + void not_change_anything_if_withJumpGambleResult_called_with_NO_GAMBLE_on_simple_move() { + Move simpleMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(simpleMove.withJumpGambleResult(NO_GAMBLE)).isEqualTo(simpleMove); + } + + @Test + void change_to_jumpGambleResult_given_with_withJumpGambleResult() { + Move jumpMove = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(jumpMove.withJumpGambleResult(WON).jumpGambleResult()).isEqualTo(WON); + assertThat(jumpMove.withJumpGambleResult(NO_GAMBLE).jumpGambleResult()).isEqualTo(NO_GAMBLE); + assertThat(jumpMove.withJumpGambleResult(LOST).jumpGambleResult()).isEqualTo(LOST); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/StoreTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/StoreTest.java new file mode 100644 index 0000000..37ab7f8 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/StoreTest.java @@ -0,0 +1,62 @@ +package ch.scs.jumpstart.pattern.examples.checkers.dom.board; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StoreTest { + private static final Piece WHITE_PIECE = new Piece(Player.PLAYER_WHITE, false); + private static final BoardCoordinates EMPTY_COORDINATES_AT_START = new BoardCoordinates(ROW_4, A); + private static final BoardCoordinates OCCUPIED_COORDINATES_AT_START = + new BoardCoordinates(ROW_6, B); + private static final Piece RED_PIECE = new Piece(Player.PLAYER_RED, false); + private Store store; + + @BeforeEach + void setup() { + store = new Store(); + } + + @Test + void add_piece_on_empty_place() { + assertThat(store.getPieceAt(EMPTY_COORDINATES_AT_START)).isEmpty(); + store.addPiece(EMPTY_COORDINATES_AT_START, WHITE_PIECE); + assertThat(store.getPieceAt(EMPTY_COORDINATES_AT_START)).isEqualTo(Optional.of(WHITE_PIECE)); + } + + @Test + void add_piece_on_occupied_place() { + assertThat(store.getPieceAt(OCCUPIED_COORDINATES_AT_START)).isEqualTo(Optional.of(RED_PIECE)); + store.addPiece(OCCUPIED_COORDINATES_AT_START, WHITE_PIECE); + assertThat(store.getPieceAt(OCCUPIED_COORDINATES_AT_START)).isEqualTo(Optional.of(WHITE_PIECE)); + } + + @Test + void remove_piece_at_empty_place() { + store.removePiece(EMPTY_COORDINATES_AT_START); + assertThat(store.getPieceAt(EMPTY_COORDINATES_AT_START)).isEmpty(); + } + + @Test + void remove_piece_at_occupied_place() { + store.removePiece(OCCUPIED_COORDINATES_AT_START); + assertThat(store.getPieceAt(OCCUPIED_COORDINATES_AT_START)).isEmpty(); + } + + @Test + void get_piece_of_empty_place() { + assertThat(store.getPieceAt(EMPTY_COORDINATES_AT_START)).isEmpty(); + } + + @Test + void get_piece_at_occupied_place() { + assertThat(store.getPieceAt(OCCUPIED_COORDINATES_AT_START)).isEqualTo(Optional.of(RED_PIECE)); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonalTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonalTest.java new file mode 100644 index 0000000..3a2cc5c --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonalTest.java @@ -0,0 +1,97 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MoveIsDiagonalTest { + + private MoveIsDiagonal moveIsDiagonal; + private Board board; + + @BeforeEach + void setup() { + moveIsDiagonal = new MoveIsDiagonal(); + board = new Board(); + } + + @Test + void return_true_for_diagonal_move_up_right() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_true_for_diagonal_move_up_left() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, B), new BoardCoordinates(ROW_2, A)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_true_for_diagonal_move_down_right() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_2, A), new BoardCoordinates(ROW_1, B)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_true_for_diagonal_move_down_left() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_2, B), new BoardCoordinates(ROW_1, A)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_true_for_diagonal_jump_move_up_right() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_true_for_diagonal_jump_move_down_left() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_3, C), new BoardCoordinates(ROW_1, A)); + + assertThat(moveIsDiagonal.validate(move, board)).isTrue(); + } + + @Test + void return_false_for_move_to_the_right() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, B)); + + assertThat(moveIsDiagonal.validate(move, board)).isFalse(); + } + + @Test + void return_false_for_move_up() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, A)); + + assertThat(moveIsDiagonal.validate(move, board)).isFalse(); + } + + @Test + void return_false_for_move_one_up_and_two_right() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, C)); + + assertThat(moveIsDiagonal.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLengthTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLengthTest.java new file mode 100644 index 0000000..2b8cfe4 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLengthTest.java @@ -0,0 +1,109 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MoveLengthTest { + private MoveLength moveLength; + private Board board; + + @BeforeEach + void setup() { + moveLength = new MoveLength(); + board = new Board(); + } + + @Test + void return_true_if_player_moves_1_row() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, A)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_player_moves_2_row() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, A)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_player_moves_3_row() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_4, A)); + assertThat(moveLength.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_player_moves_7_row() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_8, A)); + assertThat(moveLength.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_player_moves_1_col() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, B)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_player_moves_2_col() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, C)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_player_moves_3_col() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, D)); + assertThat(moveLength.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_player_moves_7_col() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, H)); + assertThat(moveLength.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_player_moves_1_row_back() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_2, A), new BoardCoordinates(ROW_1, A)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_player_moves_1_col_back() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, B), new BoardCoordinates(ROW_1, A)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_player_moves_1_col_and_2_rows() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, B), new BoardCoordinates(ROW_3, A)); + assertThat(moveLength.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_move_does_not_change_position() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_1, A)); + assertThat(moveLength.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossibleTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossibleTest.java new file mode 100644 index 0000000..c599197 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossibleTest.java @@ -0,0 +1,100 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class NoOtherMoveToJumpPossibleTest { + private static final Piece WHITE_PAWN = new Piece(Player.PLAYER_WHITE, false); + private static final Piece RED_PAWN = new Piece(Player.PLAYER_RED, false); + private static final Piece RED_KING = new Piece(Player.PLAYER_RED, true); + + private NoOtherMoveToJumpPossible noOtherMoveToJumpPossible; + private Board board; + + @BeforeEach + void setup() { + noOtherMoveToJumpPossible = + new NoOtherMoveToJumpPossible( + new MoveIsForwardIfNotKing(), new OpponentPieceBetweenJump(), new TargetFieldEmpty()); + board = mock(Board.class); + } + + @Test + void return_true_if_no_other_pieces_exist() { + when(board.getPieceAt(notNull())).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_move_is_jump_move() { + when(board.getPieceAt(notNull())).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isTrue(); + } + + @Test + void does_not_crash_if_piece_is_at_edge() { + when(board.getPieceAt(new BoardCoordinates(ROW_1, A))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_8, A))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_1, G))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_8, G))).thenReturn(Optional.of(WHITE_PAWN)); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_other_jump_move_possible_for_white() { + when(board.getPieceAt(new BoardCoordinates(ROW_4, E))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_5, F))).thenReturn(Optional.of(RED_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_6, G))).thenReturn(Optional.empty()); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isFalse(); + } + + @Test + void return_false_if_other_jump_move_possible_for_red() { + when(board.getPieceAt(new BoardCoordinates(ROW_6, G))).thenReturn(Optional.of(RED_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_5, F))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_4, E))).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isFalse(); + } + + @Test + void return_false_if_other_jump_move_with_king_possible_for_red() { + when(board.getPieceAt(new BoardCoordinates(ROW_4, E))).thenReturn(Optional.of(RED_KING)); + when(board.getPieceAt(new BoardCoordinates(ROW_5, F))).thenReturn(Optional.of(WHITE_PAWN)); + when(board.getPieceAt(new BoardCoordinates(ROW_6, G))).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(noOtherMoveToJumpPossible.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJumpTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJumpTest.java new file mode 100644 index 0000000..f651157 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJumpTest.java @@ -0,0 +1,86 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class OpponentPieceBetweenJumpTest { + private static final Piece WHITE_PAWN = new Piece(Player.PLAYER_WHITE, false); + private static final Piece RED_PAWN = new Piece(Player.PLAYER_RED, false); + + private OpponentPieceBetweenJump opponentPieceBetweenJump; + private Board board; + + @BeforeEach + void setup() { + opponentPieceBetweenJump = new OpponentPieceBetweenJump(); + board = mock(Board.class); + } + + @Test + void return_true_if_no_jump() { + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_no_piece_between_jump() { + when(board.getPieceAt(new BoardCoordinates(ROW_2, B))).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_piece_between_belongs_to_opponent_white() { + when(board.getPieceAt(new BoardCoordinates(ROW_2, B))).thenReturn(Optional.of(RED_PAWN)); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_3, A), new BoardCoordinates(ROW_1, C)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_piece_between_belongs_to_opponent_red() { + when(board.getPieceAt(new BoardCoordinates(ROW_2, B))).thenReturn(Optional.of(WHITE_PAWN)); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, C), new BoardCoordinates(ROW_3, A)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_piece_between_belongs_to_same_player_white() { + when(board.getPieceAt(new BoardCoordinates(ROW_2, B))).thenReturn(Optional.of(WHITE_PAWN)); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_3, C), new BoardCoordinates(ROW_1, A)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isFalse(); + } + + @Test + void return_false_if_piece_between_belongs_to_same_player_red() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(RED_PAWN)); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_3, C)); + assertThat(opponentPieceBetweenJump.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValidTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValidTest.java new file mode 100644 index 0000000..a3dea96 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValidTest.java @@ -0,0 +1,84 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StartPieceValidTest { + static final Piece WHITE_PAWN = new Piece(Player.PLAYER_WHITE, false); + static final Piece RED_PAWN = new Piece(Player.PLAYER_RED, false); + + private StartPieceValid startPieceValid; + private Board board; + + @BeforeEach + void setup() { + startPieceValid = new StartPieceValid(); + board = mock(Board.class); + } + + @Test + void return_false_if_no_piece() { + when(board.getPieceAt(notNull())).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(startPieceValid.validate(move, board)).isFalse(); + } + + @Test + void return_true_if_piece_belongs_to_player_red() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(RED_PAWN)); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(startPieceValid.validate(move, board)).isTrue(); + } + + @Test + void return_true_if_piece_belongs_to_player_white() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(WHITE_PAWN)); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(startPieceValid.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_piece_belongs_to_opponent_player_white() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(RED_PAWN)); + + Move move = + Move.of( + Player.PLAYER_WHITE, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(startPieceValid.validate(move, board)).isFalse(); + } + + @Test + void return_false_if_piece_belongs_to_opponent_player_red() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(WHITE_PAWN)); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, B)); + + assertThat(startPieceValid.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmptyTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmptyTest.java new file mode 100644 index 0000000..e290b25 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmptyTest.java @@ -0,0 +1,49 @@ +package ch.scs.jumpstart.pattern.examples.checkers.movevalidator; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row.*; +import static ch.scs.jumpstart.pattern.examples.checkers.dom.JumpGambleResult.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Move; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TargetFieldEmptyTest { + public static final Piece PIECE = new Piece(Player.PLAYER_WHITE, false); + + private TargetFieldEmpty targetFieldEmpty; + private Board board; + + @BeforeEach + void setup() { + targetFieldEmpty = new TargetFieldEmpty(); + board = mock(Board.class); + } + + @Test + void return_true_if_target_field_empty() { + when(board.getPieceAt(notNull())).thenReturn(Optional.empty()); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, A)); + assertThat(targetFieldEmpty.validate(move, board)).isTrue(); + } + + @Test + void return_false_if_target_field_not_empty() { + when(board.getPieceAt(notNull())).thenReturn(Optional.of(PIECE)); + + Move move = + Move.of(Player.PLAYER_RED, new BoardCoordinates(ROW_1, A), new BoardCoordinates(ROW_2, A)); + assertThat(targetFieldEmpty.validate(move, board)).isFalse(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinterTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinterTest.java new file mode 100644 index 0000000..55db415 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinterTest.java @@ -0,0 +1,71 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import static org.mockito.Mockito.*; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Column; +import ch.scs.jumpstart.pattern.examples.checkers.dom.BoardCoordinates.Row; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +class BoardPrinterTest { + + private Board board; + private Console console; + private BoardPrinter boardPrinter; + private InOrder inOrder; + + @BeforeEach + void setup() { + board = spy(new Board()); + console = mock(Console.class); + boardPrinter = new BoardPrinter(console); + inOrder = inOrder(console); + } + + @Test + void prints_initial_board_state() { + boardPrinter.printBoard(board); + + inOrder.verify(console).print(" a b c d e f g h "); + inOrder.verify(console).print(" +-------------------------------------------------+"); + inOrder.verify(console).print("8 | [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] | 8"); + inOrder.verify(console).print("7 | [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] | 7"); + inOrder.verify(console).print("6 | [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] | 6"); + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 5"); + inOrder.verify(console).print("4 | [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] | 3"); + inOrder.verify(console).print("2 | [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] | 2"); + inOrder.verify(console).print("1 | [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] | 1"); + inOrder.verify(console).print(" +-------------------------------------------------+"); + inOrder.verify(console).print(" a b c d e f g h "); + inOrder.verifyNoMoreInteractions(); + } + + @Test + void print_king() { + when(board.getPieceAt(new BoardCoordinates(Row.ROW_1, Column.A))) + .thenReturn(Optional.of(new Piece(Player.PLAYER_WHITE, true))); + + boardPrinter.printBoard(board); + + inOrder.verify(console).print(" a b c d e f g h "); + inOrder.verify(console).print(" +-------------------------------------------------+"); + inOrder.verify(console).print("8 | [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] | 8"); + inOrder.verify(console).print("7 | [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] | 7"); + inOrder.verify(console).print("6 | [ ] [R_P] [ ] [R_P] [ ] [R_P] [ ] [R_P] | 6"); + inOrder.verify(console).print("5 | [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 5"); + inOrder.verify(console).print("4 | [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] | 4"); + inOrder.verify(console).print("3 | [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] | 3"); + inOrder.verify(console).print("2 | [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] [W_P] | 2"); + inOrder.verify(console).print("1 | [W_K] [ ] [W_P] [ ] [W_P] [ ] [W_P] [ ] | 1"); + inOrder.verify(console).print(" +-------------------------------------------------+"); + inOrder.verify(console).print(" a b c d e f g h "); + inOrder.verifyNoMoreInteractions(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosserTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosserTest.java new file mode 100644 index 0000000..d054c26 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosserTest.java @@ -0,0 +1,88 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.Player.PLAYER_RED; +import static ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result.HEADS; +import static ch.scs.jumpstart.pattern.examples.checkers.util.CoinTosser.Result.TAILS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Player; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Map; +import java.util.Random; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CoinTosserTest { + + private PointsCalculator pointsCalculator; + private Random random; + private Board board; + + @BeforeEach + void setup() { + pointsCalculator = mock(PointsCalculator.class); + random = mock(RandomWrapper.class); + board = mock(Board.class); + } + + @Test + void return_heads_and_tails_evenly_distributed() { + when(pointsCalculator.calculatePoints(notNull())) + .thenReturn(Map.of(PLAYER_RED, 1, Player.PLAYER_WHITE, 1)); + CoinTosser coinTosser = new CoinTosser(pointsCalculator); + long count = + IntStream.range(0, 100) + .mapToObj(__ -> coinTosser.toss(board, PLAYER_RED)) + .filter(HEADS::equals) + .count(); + // use assume because of the random element + assumeTrue(count < 70); + assumeTrue(count > 30); + } + + @Test + void use_even_chances_if_points_are_evenly_distributed() { + when(pointsCalculator.calculatePoints(notNull())) + .thenReturn(Map.of(PLAYER_RED, 1, Player.PLAYER_WHITE, 1)); + CoinTosser coinTosser = new CoinTosser(pointsCalculator, random); + + when(random.nextFloat()).thenReturn(0.5f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(HEADS); + + when(random.nextFloat()).thenReturn(0.51f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(TAILS); + } + + @Test + void increase_the_chance_for_the_weaker_player() { + when(pointsCalculator.calculatePoints(notNull())) + .thenReturn(Map.of(PLAYER_RED, 1, Player.PLAYER_WHITE, 2)); + CoinTosser coinTosser = new CoinTosser(pointsCalculator, random); + + when(random.nextFloat()).thenReturn(0.6666f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(HEADS); + + when(random.nextFloat()).thenReturn(0.66667f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(TAILS); + } + + @Test + void decrease_the_chance_for_the_stronger_player() { + when(pointsCalculator.calculatePoints(notNull())) + .thenReturn(Map.of(PLAYER_RED, 2, Player.PLAYER_WHITE, 1)); + CoinTosser coinTosser = new CoinTosser(pointsCalculator, random); + + when(random.nextFloat()).thenReturn(0.3333f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(HEADS); + + when(random.nextFloat()).thenReturn(0.33334f); + assertThat(coinTosser.toss(board, PLAYER_RED)).isEqualTo(TAILS); + } + + private static class RandomWrapper extends Random {} +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculatorTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculatorTest.java new file mode 100644 index 0000000..a6cbc08 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculatorTest.java @@ -0,0 +1,46 @@ +package ch.scs.jumpstart.pattern.examples.checkers.util; + +import static ch.scs.jumpstart.pattern.examples.checkers.dom.Player.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.*; + +import ch.scs.jumpstart.pattern.examples.checkers.dom.Piece; +import ch.scs.jumpstart.pattern.examples.checkers.dom.board.Board; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PointsCalculatorTest { + private PointsCalculator pointsCalculator; + private Board board; + + @BeforeEach + void setup() { + pointsCalculator = new PointsCalculator(); + board = spy(new Board()); + } + + @Test + void calculate_points_correctly_at_start() { + assertThat(pointsCalculator.calculatePoints(board)) + .isEqualTo(Map.of(PLAYER_WHITE, 12, PLAYER_RED, 12)); + } + + @Test + void calculate_points_if_no_pieces_are_on_board() { + doReturn(Optional.empty()).when(board).getPieceAt(notNull()); + assertThat(pointsCalculator.calculatePoints(board)) + .isEqualTo(Map.of(PLAYER_WHITE, 0, PLAYER_RED, 0)); + } + + @Test + void use_2_as_value_for_kings() { + doReturn(Optional.of(new Piece(PLAYER_WHITE, true)), Optional.of(new Piece(PLAYER_RED, true))) + .when(board) + .getPieceAt(notNull()); + assertThat(pointsCalculator.calculatePoints(board)) + .isEqualTo(Map.of(PLAYER_WHITE, 2, PLAYER_RED, 126)); + } +} diff --git a/topics/sw_concepts/sw_concept_code_examples.md b/topics/sw_concepts/sw_concept_code_examples.md deleted file mode 100644 index 06e50d5..0000000 --- a/topics/sw_concepts/sw_concept_code_examples.md +++ /dev/null @@ -1,410 +0,0 @@ -<#include meta/slides.md> - ---- -title: "SW Konzepte Code Beispiele" -date: \today ---- - -Code Beispiele -------- - -Überlege für die folgenden Code Beispiele folgendes: - -1. Hat das Beispiel etwas mit DRY, KISS, YAGNI oder NIH zu tun? -2. Hat es mit den SOLID Principles zu tun? -3. Welche Design Pattern siehst du? -4. Was könnte sonst noch verbessert werden? - -OrganisationSupplier -------- - -```python -class OrganisationSupplier: - def get_current_organisation(self): - pass - -class ConfigurationOrganisationSupplier(OrganisationSupplier): - def __init__(self, device_location_dao, konfiguration_dao, default_organisation): - self.konfiguration_dao = konfiguration_dao - self.default_organisation = default_organisation - - def get_current_organisation(self): - return self.konfiguration_dao.get_konfigurationKonfigurationDAO.ORGANISATION_ID, self.default_organisation) - -class DeviceLocationOrganisationSupplier(OrganisationSupplier): - def __init__(self, konfiguration_dao, device_location_dao): - self.konfiguration_dao = konfiguration_dao - self.device_location_dao = device_location_dao - - def get_current_organisation(self): - return Optional.ofNullable(self.konfiguration_dao.get_konfiguration(KonfigurationDAO.STANDORT_KENNUNG, None)) \ - .map(int) \ - .map(self.device_location_dao.get_device_location_by_id) \ - .map(lambda device_location_entity: device_location_entity.get_organisation()) -``` - -OrganisationSupplier Auswertung -------- - -1. DRY...: Property Keys werden geteilt (e.g. STANDORT_KENNUNG) -2. SOLID: nicht speziell -3. Pattern: Strategy -4. Verbesserung: - - -AtmelObservableFactory -------- - -```python -class AtmelObservableFactory: - @staticmethod - def create_power_state(host, port, scheduler): - return AtmelObservableFactory.create_power_state_with_interval(host, port, INTERVAL, scheduler) - - @staticmethod - def create_power_state_with_interval(host, port, interval, scheduler): - simple_request = SimpleRequest("inputGetIgnitionState") - ignition_state_request_executor = AtmelRequestExecutor(host, port, simple_request, IgnitionStateResult) - return PollingObservable.create(scheduler, lambda: ..., interval, scheduler) - - @staticmethod - def create_system_state(host, port, scheduler): - return AtmelObservableFactory.create_system_state_with_interval(host, port, INTERVAL, scheduler) - - @staticmethod - def create_system_state_with_interval(host, port, interval, scheduler): - simple_request = SimpleRequest("registerObject", ["InfovisionSystemState"]) - return RetryUntilSuccess.create(AtmelRequestExecutor(host, port, simple_request, SystemStateResult), lambda: interval, scheduler) -``` - -AtmelObservableFactory Auswertung -------- - -1. DRY...: YAGNI: muss das Interval konfigurierbar sein? -2. SOLID: Single Responsibility: muss beides hier erstellt werden? -3. Pattern: AbstractFactory, Factory method -4. Verbesserung: - - -BrightnessParserTest -------- - -```python -def test_parses_brightness_correctly(self): - windows_brightness_output = """ - Power Setting GUID: 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e (Turn off display after) - Minimum Possible Setting: 0x00000000 - Maximum Possible Setting: 0xffffffff - Possible Settings increment: 0x00000001 - Current AC Power Setting Index: 0x00000000 - Current DC Power Setting Index: 0x00003840 - - Power Setting GUID: aded5e82-b909-4619-9949-f5d71dac0bcb (Display brightness) - Minimum Possible Setting: 0x00000000 - Maximum Possible Setting: 0x00000064 - Possible Settings increment: 0x00000001 - Possible Settings units: % - Current AC Power Setting Index: 0x0000005a - Current DC Power Setting Index: 0x0000005a -""" - self.assertEqual(parse(windows_brightness_output), "0000005a") -``` - -BrightnessParserTest Auswertung -------- - -1. DRY...: "0000005a" könnte geteilt und benannt werden -2. SOLID: - -3. Pattern: - -4. Verbesserung: - - -DoorState -------- - -```java -private synchronized void setDoorState(DoorState newDoorState, - DoorComponent doorComponent) { - doorContext.setDoorState(newDoorState, doorComponent); - if (doorContext.isInconsistent()) { - observers.forEach(DoorObserver::inconsistentSensors); - } else if (DoorState.OPEN.equals(newDoorState)) { - observers.forEach(DoorObserver::switchOpened); - } else if (DoorState.CLOSED.equals(newDoorState) && doorContext.isDoorCompletelyClosed()) { - observers.forEach(DoorObserver::doorClosed); - } - } -``` - -DoorState Auswertung -------- - -1. DRY...: - -2. SOLID: Open/Closed: Für alle neuen Events muss das if statement und das DoorObserver Interface geändert werden. -3. Pattern: Observer -4. Verbesserung: - - -TransactionRepository -------- - -```java -public interface TransactionRepository { - Transaction addTransactions(Collection transactions); - - void updateTransactions(Collection transactions); - - Optional findTransaction(UUID uuid); -} - -HibernateTransactionRepository implements TransactionRepository {} - -public class TransactionObserverTest { - private TransactionObserver transactionObserver; - private TransactionRepository transactionRepository; - @Before - public void givenTransactionObserver() { - transactionRepository = mock(TransactionRepository.class); - transactionObserver = new TransactionObserver(transactionRepository, createTerminalInfoExporter(), createLocationInfoExporter()); - } -``` - -TransactionRepository Auswertung -------- - -1. DRY...: KISS: Ein Mock kann auch einfach mit einer Klasse umgesetzt werden -2. SOLID: Dependency Inversion -3. Pattern: - -4. Verbesserung: - - -CreditCardAppIdMapperProvider -------- - -```java -public class CreditCardAppIdMapperProvider implements Provider { - private final CreditCardAppIdMapper creditCardAppIdMapper; - - public CreditCardAppIdMapperProvider(CreditCardAppIdMappingConfiguration configuration, - CreditCardAppIdCategoryMappingParser creditCardAppIdCategoryMappingParser) { - if (configuration.isEnabled()) { - List mappingEntries = creditCardAppIdCategoryMappingParser.parse(new File(configuration.getMappingFilePath())); - creditCardAppIdMapper = new CreditCardAppIdMapper(createMap(mappingEntries)); - } else { - creditCardAppIdMapper = new CreditCardAppIdMapper(new HashMap<>()); - } - } - - public CreditCardAppIdMapper get() { - return creditCardAppIdMapper; - } - //... -} -``` - -CreditCardAppIdMapperProvider Auswertung -------- - -1. DRY...: - -2. SOLID: - -3. Pattern: Factory, [Null Object](https://github.com/iluwatar/java-design-patterns/tree/master/null-object) -4. Verbesserung: Das parsen des Files (Input/Output oder I/O) im Konstruktor -kann das aufstarten Verzögern. Das könnte man wenn möglich asynchron machen. - -Board -------- - -```java -public class Board { - private final List boardObservers = new ArrayList<>(); - - public void executeMove(Move move) { - Command command = createCommand(move); - command.execute(); - boardObservers.forEach(boardObserver -> boardObserver.boardChanged(this)); - } - - public void registerObserver(BoardObserver boardObserver) { - boardObservers.add(boardObserver); - } -} -``` - -Board Auswertung -------- - -1. DRY...: - -2. SOLID: - -3. Pattern: Observer -4. Verbesserung: Keine Möglichkeit um Observer zu entfernen $\rightarrow$ Memory Leak - -Board2 -------- - -\colBegin{0.5} - -```python -class SimpleMove(Command): - def execute(self): - remove_piece(self.start) - add_piece(self.end) - - def undo(self): - add_piece(self.start) - remove_piece(self.end) -``` - -\colNext{0.5} - -```python -class Board: - def __init__(self): - self.executed_commands = [] - - def execute_move(self, move): - command = self.create_command(move) - command.execute() - self.executed_commands.append(command) - - def undo_last_turn(self): - if len(self.executed_commands) == 0: - raise NoPreviousMovesException() - - last_command = get_executed_commands(-1) - last_command.undo() - self.executed_commands.pop() -``` - -\colEnd - -Board2 Auswertung -------- - -1. DRY...: - -2. SOLID: - -3. Pattern: Command -4. Verbesserung: - - -DoorEventHandler -------- - -```java -public class DoorEventHandler { - private static final Logger LOGGER = Logger.getLogger(DoorEventHandler.class); - - private final DoorContext doorContext; - - private final Set observers = new HashSet<>(); - - public DoorEventHandler(DoorContext doorContext) { - this.doorContext = doorContext; - } - - public void addObserver(DoorObserver observer) { - } - - @Handler - public void handle(DoorEvent event) { - } -} -``` - -DoorEventHandler Auswertung -------- - -1. DRY...: - -2. SOLID: - -3. Pattern: Observer -4. Verbesserung: - 1. Liste der Observer muss je nach Bedarf Threadsafe sein - 2. Keine Möglichkeit Observer zu entfernen -> Memory Leak - -ButtonFactory -------- - -```python -class ButtonFactory: - def get_bwd_button(self, name, dim, text): - return BwdButton(name, dim, text) - - def get_circle_button(self, name, color, text): - return CircleButton(name, color, text) - - def get_fwd_button(self, name, dim, text): - return FwdButton(name, dim, text) - - def get_info_button(self, name, info_text): - return InfoButton(name, info_text) - -class ButtonFactoryC(ButtonFactory): - pass -``` - -ButtonFactory Auswertung -------- - -1. DRY...: - -2. SOLID: - -3. Pattern: AbstractFactory -4. Verbesserung: Inheritance hat in diesem Fall zu Problemen geführt. -Da man nicht Voraussagen kann, wo welche Buttons gebraucht werden, -bietet sich hier Composition + Interface besser an. - -ReceiptFactory -------- - -```python -class ReceiptFactory: - def create_eft_receipt(self, receipt_lines): - pass - - def create_refund_receipt(self, kunden_session_nr, fehl_betrag, beleg_nr, mfk): - pass - -class Customer1ReceiptFactory(ReceiptFactory): - def __init__(self, printer_config): - self.printer_config = printer_config - - def create_eft_receipt(self, receipt_lines): - pass - - def create_refund_receipt(self, kunden_session_nr, fehl_betrag, beleg_nr, mfk): - print("No refund receipt implemented") - return None -``` - -ReceiptFactory Auswertung -------- - -1. DRY...: - -2. SOLID: create_refund_receipt verletzt Liskov's Substitution Principle -3. Pattern: AbstractFactory -4. Verbesserung: - - -OperatingPoint ------ - -```python -class OperatingPointNumberResolveStrategy: - """ - Returns the vending location (Verkaufspunkt) assigned to the operating point number (Betriebspunkt) - """ - def get_net_point(self, operating_point_number): - pass - -class DidokStrategy(OperatingPointNumberResolveStrategy): - def get_net_point(self, operating_point_number): - return self.assortment_provider.get_current_netpoint_by_didok_number( - operating_point_number - ) - -class SubstopNetpointIdStrategy(OperatingPointNumberResolveStrategy): - def get_net_point(self, operating_point_number): - return self.assortment_provider.get_current_netpoint_by_netpoint_id( - operating_point_number, - "SUBSTOP" - ) -``` - -OperatingPoint Auswertung -------- - -1. DRY...: KISS: Hätte auch ein IF Statement gereicht? -2. SOLID: - -3. Pattern: Strategy -4. Verbesserung: -