diff --git a/.devcontainer/java/devcontainer.json b/.devcontainer/java/devcontainer.json index e8ca14bb..a9abca6d 100644 --- a/.devcontainer/java/devcontainer.json +++ b/.devcontainer/java/devcontainer.json @@ -2,7 +2,7 @@ "name": "Jumpstart java", "image": "mcr.microsoft.com/devcontainers/java:dev-21-jdk-bookworm", "features": { - "ghcr.io/devcontainers-contrib/features/maven-sdkman": { + "ghcr.io/devcontainers-extra/features/maven-sdkman": { "jdkVersion": "21", "jdkDistro": "tem" } @@ -10,7 +10,6 @@ "customizations": { "vscode": { "extensions": [ - "Codeium.codeium", "GitHub.copilot", "redhat.java", "ryanluker.vscode-coverage-gutters", diff --git a/.devcontainer/python/devcontainer.json b/.devcontainer/python/devcontainer.json new file mode 100644 index 00000000..2fb0a6cc --- /dev/null +++ b/.devcontainer/python/devcontainer.json @@ -0,0 +1,14 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Jumpstart Python 3", + "image": "mcr.microsoft.com/devcontainers/python:2-3.14-trixie", + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "ms-python.python" + ] + } + } +} diff --git a/.github/workflows/pattern-examples.yml b/.github/workflows/pattern-examples.yml deleted file mode 100644 index 862b2342..00000000 --- a/.github/workflows/pattern-examples.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Pattern examples -on: - - push - - pull_request -jobs: - pattern-examples: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - java: - - '21' - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: ${{ matrix.java }} - - - name: compile - working-directory: topics/sw_concepts/code/pattern-examples - run: mvn install -DskipTests=true - - - name: lint - working-directory: topics/sw_concepts/code/pattern-examples - run: mvn pmd:check - - - name: check format - working-directory: topics/sw_concepts/code/pattern-examples - run: mvn spotless:check - - - name: run tests - working-directory: topics/sw_concepts/code/pattern-examples - run: mvn test - - - uses: actions/upload-artifact@v4.6.2 - if: always() - with: - path: 'topics/sw_concepts/code/pattern-examples/target' diff --git a/.github/workflows/python-patterns.yml b/.github/workflows/python-patterns.yml new file mode 100644 index 00000000..6a618215 --- /dev/null +++ b/.github/workflows/python-patterns.yml @@ -0,0 +1,94 @@ +name: Python Design Patterns + +on: + push: + paths: + - 'topics/sw_concepts/code/**/*.py' + - '.github/workflows/python-patterns.yml' + - 'pyproject.toml' + pull_request: + paths: + - 'topics/sw_concepts/code/**/*.py' + - '.github/workflows/python-patterns.yml' + - 'pyproject.toml' + +jobs: + lint-and-test: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + python-version: ['3.14'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Lint with ruff + run: | + # Check for linting issues + ruff check topics/sw_concepts/code/ --output-format=github + + - name: Format check with ruff + run: | + # Check if code is formatted correctly + ruff format topics/sw_concepts/code/ --check + + - name: Run Python pattern examples + run: | + # Run all Python files to ensure they execute without errors + echo "Testing abstract factory patterns..." + python topics/sw_concepts/code/slides/abstract-factory/abstract-factory-bad-case.py + python topics/sw_concepts/code/slides/abstract-factory/abstract-factory-good-case.py + + echo "Testing command patterns..." + python topics/sw_concepts/code/slides/command/command-bad-case.py + python topics/sw_concepts/code/slides/command/command-good-case.py + + echo "Testing dependency inversion..." + python topics/sw_concepts/code/slides/dependency-inversion/no-dependency-inversion.py + python topics/sw_concepts/code/slides/dependency-inversion/with-dependency-inversion.py + + echo "Testing factory patterns..." + python topics/sw_concepts/code/slides/factory/factory-bad-case.py + python topics/sw_concepts/code/slides/factory/factory-good-case.py + + echo "Testing interface segregation..." + python topics/sw_concepts/code/slides/interface-segregation/interface-segregation-bad-case.py + python topics/sw_concepts/code/slides/interface-segregation/interface-segregation-good-case.py + + echo "Testing strategy patterns..." + python topics/sw_concepts/code/slides/strategy/strategy-bad-case.py + python topics/sw_concepts/code/slides/strategy/strategy-good-case.py + + echo "Testing UML examples..." + python topics/sw_concepts/code/slides/uml/class-diagram-parts.py + + echo "Testing exercise examples (where possible)..." + python topics/sw_concepts/code/exercise/01-intro.py + python topics/sw_concepts/code/exercise/05-observer-bad-case.py + + echo "Testing solution examples..." + python topics/sw_concepts/code/solution/01-intro-refactored.py + python topics/sw_concepts/code/solution/02-interfaces.py + python topics/sw_concepts/code/solution/03-factory.py + python topics/sw_concepts/code/solution/04-abstract-factory.py + python topics/sw_concepts/code/solution/05-observer-good-case.py + + echo "All Python pattern examples executed successfully!" + + - name: Run type checking (if mypy is available) + continue-on-error: true + run: | + pip install mypy + mypy topics/sw_concepts/code/slides/ --ignore-missing-imports || true diff --git a/.vscode/settings.json b/.vscode/settings.json index b2e0277b..c58c7f25 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,4 @@ { "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "cmake.generator": "Unix Makefiles", - "codeium.disableSupercomplete": true, - "codeium.enableExplainProblem": false, - "codeium.enableInComments": false, - "codeium.enableCodeLens": false, - "codeium.enableConfig": { - "*": false - }, } diff --git a/README.md b/README.md index 961ca5e9..e61d47c1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ jumpstart-docs [![CMake exercise](https://github.com/scs/jumpstart-docs/actions/workflows/cmake-exercise.yml/badge.svg)](https://github.com/scs/jumpstart-docs/actions/workflows/cmake-exercise.yml) [![Docker exercise](https://github.com/scs/jumpstart-docs/actions/workflows/docker-exercise.yml/badge.svg)](https://github.com/scs/jumpstart-docs/actions/workflows/docker-exercise.yml) [![Java exercise](https://github.com/scs/jumpstart-docs/actions/workflows/java-exercise.yml/badge.svg)](https://github.com/scs/jumpstart-docs/actions/workflows/java-exercise.yml) -[![Pattern examples](https://github.com/scs/jumpstart-docs/actions/workflows/pattern-examples.yml/badge.svg)](https://github.com/scs/jumpstart-docs/actions/workflows/pattern-examples.yml) +[![Python Design Patterns](https://github.com/scs/jumpstart-docs/actions/workflows/python-patterns.yml/badge.svg)](https://github.com/scs/jumpstart-docs/actions/workflows/python-patterns.yml) Die SCS Jumpstart-Kurse bieten einen effizienten Einstieg in den praktischen Alltag eines Entwicklers. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..f3bbff72 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[project] +name = "jumpstart-docs" +version = "0.1.0" +description = "Software concepts and design patterns examples" +requires-python = ">=3.10" + +[tool.ruff] +# Python 3.10+ compatible +target-version = "py310" + +# Line length +line-length = 100 + +# Exclude directories +exclude = [ + ".git", + ".venv", + "venv", + "build", + "__pycache__", + "*.pyc", +] + +[tool.ruff.lint] +# Enable specific rule sets +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "N", # pep8-naming + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "SIM", # flake8-simplify + "RET", # flake8-return + "ARG", # flake8-unused-arguments + "PTH", # flake8-use-pathlib + "ERA", # eradicate (commented-out code) + "PL", # pylint + "PERF", # perflint + "RUF", # ruff-specific rules +] + +# Ignore specific rules +ignore = [ + "E501", # Line too long (handled by formatter) + "PLR0913", # Too many arguments + "PLR2004", # Magic value used in comparison +] + +[tool.ruff.lint.per-file-ignores] +# Allow unused imports in __init__.py files +"__init__.py" = ["F401"] + +[tool.ruff.format] +# Use double quotes for strings +quote-style = "double" + +# Indent with spaces +indent-style = "space" + +# Unix line endings +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings +docstring-code-format = true diff --git a/topics/sw_concepts/code/exercise/01-intro.py b/topics/sw_concepts/code/exercise/01-intro.py new file mode 100644 index 00000000..f4709927 --- /dev/null +++ b/topics/sw_concepts/code/exercise/01-intro.py @@ -0,0 +1,60 @@ +import math + + +def main() -> None: + # --- Order 1: Alice buys 3 Widgets and 1 Gadget --- + o1_total = 0.0 + o1_total += 3 * 9.99 + o1_total += 1 * 24.99 + if o1_total > 50: + o1_total *= 0.9 + o1_tax = o1_total * 0.19 + o1_total += o1_tax + o1_total = math.floor(o1_total * 100) / 100 + print(f"Order 1 (Alice): {o1_total} CHF") + + # --- Order 2: Bob buys 5 Widgets --- + o2_total = 0.0 + o2_total += 5 * 9.99 + if o2_total > 50: + o2_total *= 0.9 + o2_tax = o2_total * 0.19 + o2_total += o2_tax + o2_total = math.floor(o2_total * 100) / 100 + print(f"Order 2 (Bob): {o2_total} CHF") + + # --- Order 3: Charlie buys 2 Gadgets and 4 Widgets, pays in USD --- + o3_total = 0.0 + o3_total += 4 * 9.99 + o3_total += 2 * 24.99 + if o3_total > 50: + o3_total *= 0.9 + o3_tax = o3_total * 0.19 + o3_total += o3_tax + o3_total = math.floor(o3_total * 100) / 100 + o3_total_usd = o3_total * 1.08 + o3_total_usd = math.floor(o3_total_usd * 100) / 100 + print(f"Order 3 (Charlie): {o3_total_usd} USD") + + # --- Print receipt for Alice --- + print("=" * 40) + print("RECEIPT") + print("=" * 40) + r1_total = 0.0 + r1_total += 3 * 9.99 + r1_total += 1 * 24.99 + print(f" 3x Widget @ 9.99 = {3 * 9.99}") + print(f" 1x Gadget @ 24.99 = {1 * 24.99}") + if r1_total > 50: + print(f" Discount (10%): -{r1_total * 0.1:.2f}") + r1_total *= 0.9 + r1_tax = r1_total * 0.19 + r1_total += r1_tax + r1_total = math.floor(r1_total * 100) / 100 + print(f" Tax (19%): {r1_tax:.2f}") + print(f" TOTAL: {r1_total} CHF") + print("=" * 40) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/exercise/02-03-interfaces.py b/topics/sw_concepts/code/exercise/02-03-interfaces.py new file mode 100644 index 00000000..e32da5d2 --- /dev/null +++ b/topics/sw_concepts/code/exercise/02-03-interfaces.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +from enum import StrEnum, auto + + +class Washer(ABC): + @abstractmethod + def wash_clothes(self, item: str): + pass + + @abstractmethod + def wash_dishes(self, item: str): + pass + + +class MachineWasher: + # TODO + pass + + +class HandWasher: + # TODO + pass + + +class ThingsToWash(StrEnum): + PANTS = auto() + SHIRT = auto() + SPOON = auto() + PLATE = auto() + FORK = auto() + + +def wash_things(washer: Washer, things: list[ThingsToWash]) -> None: + for thing in things: + if thing in (ThingsToWash.PANTS, ThingsToWash.SHIRT): + washer.wash_clothes(str(thing)) + elif thing in (ThingsToWash.SPOON, ThingsToWash.PLATE, ThingsToWash.FORK): + washer.wash_dishes(str(thing)) + + +def main() -> None: + machine_washer = MachineWasher() + hand_washer = HandWasher() + + wash_things(machine_washer, [ThingsToWash.PLATE, ThingsToWash.SHIRT, ThingsToWash.SPOON]) + wash_things(hand_washer, [ThingsToWash.PANTS, ThingsToWash.FORK]) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/exercise/04-abstract-factory.py b/topics/sw_concepts/code/exercise/04-abstract-factory.py new file mode 100644 index 00000000..3d29f6cb --- /dev/null +++ b/topics/sw_concepts/code/exercise/04-abstract-factory.py @@ -0,0 +1,90 @@ +from abc import ABC, abstractmethod +from enum import StrEnum, auto + + +class ClothesWasher(ABC): + @abstractmethod + def wash(self, item: str) -> None: + pass + + +class DishWasher(ABC): + @abstractmethod + def wash(self, item: str) -> None: + pass + + +class MachineClothesWasher(ClothesWasher): + def wash(self, item: str) -> None: + print(f"Machine washing clothes: {item}.") + + +class MachineDishWasher(DishWasher): + def wash(self, item: str) -> None: + print(f"Machine washing dishes: {item}.") + + +class HandClothesWasher(ClothesWasher): + def wash(self, item: str) -> None: + print(f"Hand washing clothes: {item}.") + + +class HandDishWasher(DishWasher): + def wash(self, item: str) -> None: + print(f"Hand washing dishes: {item}.") + + +class WasherFactory(ABC): + @abstractmethod + def create_clothes_washer(self) -> ClothesWasher: + pass + + @abstractmethod + def create_dish_washer(self) -> DishWasher: + pass + + +# TODO: Implement MachineWasherFactory and HandWasherFactory + + +class ThingsToWash(StrEnum): + PANTS = auto() + SHIRT = auto() + SPOON = auto() + PLATE = auto() + FORK = auto() + + +def wash_things(factory: WasherFactory, things: list[ThingsToWash]) -> None: + clothes_washer = factory.create_clothes_washer() + dish_washer = factory.create_dish_washer() + + for thing in things: + if thing in (ThingsToWash.PANTS, ThingsToWash.SHIRT): + clothes_washer.wash(str(thing)) + elif thing in (ThingsToWash.SPOON, ThingsToWash.PLATE, ThingsToWash.FORK): + dish_washer.wash(str(thing)) + + +def get_washer_factory(can_wash_fragile_things: bool) -> WasherFactory: + # TODO + pass + + +def main() -> None: + factory = get_washer_factory(can_wash_fragile_things=True) + + wash_things( + factory, + [ + ThingsToWash.PLATE, + ThingsToWash.SHIRT, + ThingsToWash.SPOON, + ThingsToWash.PANTS, + ThingsToWash.FORK, + ], + ) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/exercise/05-observer-bad-case.py b/topics/sw_concepts/code/exercise/05-observer-bad-case.py new file mode 100644 index 00000000..460952fe --- /dev/null +++ b/topics/sw_concepts/code/exercise/05-observer-bad-case.py @@ -0,0 +1,80 @@ +class Theme: + def __init__(self) -> None: + self._is_dark_theme: bool = False + + @property + def is_dark_theme(self) -> bool: + return self._is_dark_theme + + @is_dark_theme.setter + def is_dark_theme(self, value: bool) -> None: + self._is_dark_theme = value + theme_name = "Dark" if value else "Light" + print(f"[Theme] Theme changed to {theme_name}") + + +class DarkThemeButton: + def __init__(self, theme: Theme) -> None: + self.theme: Theme = theme + self.light_theme_button: LightThemeButton | None = None + self.enabled: bool = True + + def on_action(self) -> None: + if not self.enabled: + return + + print("Dark theme button clicked") + self.theme.is_dark_theme = True + self.set_enable(False) + if self.light_theme_button: + self.light_theme_button.set_enable(True) + + def set_enable(self, enable: bool) -> None: + self.enabled = enable + status = "enabled" if enable else "disabled" + print(f" Dark theme button is now {status}") + + +class LightThemeButton: + def __init__(self, theme: Theme) -> None: + self.theme: Theme = theme + self.dark_theme_button: DarkThemeButton | None = None + self.enabled: bool = False + + def on_action(self) -> None: + if not self.enabled: + return + + print("Light theme button clicked") + self.theme.is_dark_theme = False + self.set_enable(False) + if self.dark_theme_button: + self.dark_theme_button.set_enable(True) + + def set_enable(self, enable: bool) -> None: + self.enabled = enable + status = "enabled" if enable else "disabled" + print(f" Light theme button is now {status}") + + +def main() -> None: + theme: Theme = Theme() + + dark_button: DarkThemeButton = DarkThemeButton(theme) + light_button: LightThemeButton = LightThemeButton(theme) + + dark_button.light_theme_button = light_button + light_button.dark_theme_button = dark_button + + print("User clicks dark theme button:") + dark_button.on_action() + + print("\nUser clicks light theme button:") + light_button.on_action() + + print("\nUser clicks dark theme button again:") + dark_button.on_action() + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/pattern-examples/.gitignore b/topics/sw_concepts/code/pattern-examples/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/topics/sw_concepts/code/pattern-examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/topics/sw_concepts/code/pattern-examples/pmd-config.xml b/topics/sw_concepts/code/pattern-examples/pmd-config.xml deleted file mode 100644 index 10bc782e..00000000 --- a/topics/sw_concepts/code/pattern-examples/pmd-config.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - RuleSet for jumpstart. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/topics/sw_concepts/code/pattern-examples/pom.xml b/topics/sw_concepts/code/pattern-examples/pom.xml deleted file mode 100644 index c3d93dd1..00000000 --- a/topics/sw_concepts/code/pattern-examples/pom.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - 4.0.0 - - ch.scs.jumpstart - pattern-examples - 1.0-SNAPSHOT - - - - 21 - 21 - UTF-8 - UTF-8 - - - - - - org.junit - junit-bom - 5.14.3 - pom - import - - - org.mockito - mockito-bom - 5.22.0 - pom - import - - - - - - - org.assertj - assertj-core - 3.27.7 - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter - test - - - org.mockito - mockito-core - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.15.0 - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.5 - - - - com.diffplug.spotless - spotless-maven-plugin - 2.46.1 - - - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.28.0 - - false - true - - pmd-config.xml - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.14 - - - - prepare-agent - - - - report - test - - report - - - - - - - - diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java deleted file mode 100644 index 5122f7bf..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java +++ /dev/null @@ -1,134 +0,0 @@ -package ch.scs.jumpstart.pattern.examples; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.TimeUnit; - -public class Abstractions { - - private static final int INPUT_MIN = 0; - private static final int INPUT_MAX_TSL2550 = 544; - private static final int INPUT_THRESHOLD = 11; - - private static final double OUTPUT_MIN_FACTOR = 0.2; - private static final double OUTPUT_CHANGE_MAX_FACTOR = 0.005; - - private static final int[] SENSOR_VALUE_LUX_APPROX_MAP = { - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 18, 20, 22, 24, 26, 28, 30, - 32, 34, 36, 38, 40, 42, 44, 46, - 49, 53, 57, 61, 65, 69, 73, 77, - 81, 85, 89, 93, 97, 101, 105, 109, - 115, 123, 131, 139, 147, 155, 163, 171, - 179, 187, 195, 203, 211, 219, 227, 235, - 247, 263, 279, 295, 311, 327, 343, 359, - 375, 391, 407, 423, 439, 455, 471, 487, - 511, 543, 575, 607, 639, 671, 703, 735, - 767, 799, 831, 863, 895, 927, 959, 991, - 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, - 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, - 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, - 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 - }; - - @SuppressWarnings({ - "PMD.CognitiveComplexity", - "PMD.CyclomaticComplexity", - "PMD.SystemPrintln", - "PMD.AvoidPrintStackTrace" - }) - public static void main(String[] args) throws IOException { - if (args.length != 3) { - throw new IllegalArgumentException("Wrong number of arguments"); - } - - String path = args[0]; - String inputPath = args[1]; - String outputPath = args[2]; - boolean opt3001 = - Files.isSymbolicLink(Paths.get(inputPath)) - && Files.readSymbolicLink(Paths.get(inputPath)) - .toString() - .contains("in_illuminance_input"); - - int inputMax = INPUT_MAX_TSL2550; - int inputLastValue = INPUT_MIN - INPUT_THRESHOLD; - Integer outputLastValue = null; - - try (BufferedReader reader = new BufferedReader(new FileReader(path))) { - int outputMax = Integer.parseInt(reader.readLine().trim()); - int outputMin = (int) Math.ceil(outputMax * OUTPUT_MIN_FACTOR); - int outputChangeMax = (int) Math.ceil(outputMax * OUTPUT_CHANGE_MAX_FACTOR); - - while (true) { - try { - int inputValue; - if (opt3001) { - try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { - inputValue = (int) Double.parseDouble(inputReader.readLine().trim()); - } catch (IOException e) { - inputValue = INPUT_MIN; - } - } else { - try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { - inputValue = Integer.parseInt(inputReader.readLine().trim()); - } - if (0 <= inputValue && inputValue < SENSOR_VALUE_LUX_APPROX_MAP.length) { - inputValue = SENSOR_VALUE_LUX_APPROX_MAP[inputValue]; - } else { - inputValue = inputLastValue; - } - } - - inputValue = Math.min(inputValue, inputMax); - - if (Math.abs(inputValue - inputLastValue) < INPUT_THRESHOLD) { - inputValue = inputLastValue; - } - - double a = (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN); - int value1 = (int) (a * (outputMax - outputMin) + outputMin); - int outputValue = Math.min(value1, outputMax); - - if (outputLastValue == null) { - outputValue = outputValue; - } else if (outputValue >= outputLastValue) { - outputValue = Math.min(outputValue, outputLastValue + outputChangeMax); - } else { - outputValue = Math.max(outputValue, outputLastValue - outputChangeMax); - } - int dimmedValue = outputValue; - - if (!Objects.equals(outputValue, outputLastValue)) { - System.out.printf( - "input: %4d (%4.1f%%), output: %4d (%4.1f%%), dimmed: %4d (%4.1f%%)%n", - inputValue, - 100 * (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN), - outputValue, - 100 * (outputValue - outputMin) / (double) (outputMax - outputMin), - dimmedValue, - 100 * (dimmedValue - outputMin) / (double) (outputMax - outputMin)); - System.out.flush(); - } - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) { - outputLastValue = outputValue; - writer.write(String.valueOf(outputValue)); - } - - inputLastValue = inputValue; - } catch (IOException e) { - if (!(e instanceof FileNotFoundException)) { - throw e; - } - } - - TimeUnit.MILLISECONDS.sleep(10); - } - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - } -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java deleted file mode 100644 index b028bc61..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java +++ /dev/null @@ -1,177 +0,0 @@ -package ch.scs.jumpstart.pattern.examples; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.concurrent.TimeUnit; - -public class AbstractionsSolution { - private static final int INPUT_MAX_TSL2550 = 544; - private static final double OUTPUT_MIN_FACTOR = 0.2; - private static final double OUTPUT_CHANGE_MAX_FACTOR = 0.005; - - @SuppressWarnings({"PMD.AvoidPrintStackTrace"}) - public static void main(String[] args) throws IOException { - if (args.length != 3) { - throw new IllegalArgumentException("Wrong number of arguments"); - } - - String path = args[0]; - String inputPath = args[1]; - String outputPath = args[2]; - boolean opt3001 = - Files.isSymbolicLink(Paths.get(inputPath)) - && Files.readSymbolicLink(Paths.get(inputPath)) - .toString() - .contains("in_illuminance_input"); - - try (BufferedReader reader = new BufferedReader(new FileReader(path))) { - var brightnessControl = createBrightnessControl(reader, opt3001, inputPath); - - while (true) { - try { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) { - writer.write(String.valueOf(brightnessControl.update())); - } - } catch (IOException e) { - if (!(e instanceof FileNotFoundException)) { - throw e; - } - } - - TimeUnit.MILLISECONDS.sleep(10); - } - } catch (IOException | InterruptedException e) { - e.printStackTrace(); - } - } - - private static BrightnessControl createBrightnessControl( - BufferedReader reader, boolean opt3001, String inputPath) throws IOException { - int outputMax = Integer.parseInt(reader.readLine().trim()); - int outputMin = (int) Math.ceil(outputMax * OUTPUT_MIN_FACTOR); - int outputChangeMax = (int) Math.ceil(outputMax * OUTPUT_CHANGE_MAX_FACTOR); - - return new BrightnessControl( - opt3001, INPUT_MAX_TSL2550, inputPath, outputMin, outputMax, outputChangeMax); - } -} - -class BrightnessControl { - private static final int INPUT_MIN = 0; - private static final int INPUT_THRESHOLD = 11; - - private static final int[] SENSOR_VALUE_LUX_APPROX_MAP = { - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 18, 20, 22, 24, 26, 28, 30, - 32, 34, 36, 38, 40, 42, 44, 46, - 49, 53, 57, 61, 65, 69, 73, 77, - 81, 85, 89, 93, 97, 101, 105, 109, - 115, 123, 131, 139, 147, 155, 163, 171, - 179, 187, 195, 203, 211, 219, 227, 235, - 247, 263, 279, 295, 311, 327, 343, 359, - 375, 391, 407, 423, 439, 455, 471, 487, - 511, 543, 575, 607, 639, 671, 703, 735, - 767, 799, 831, 863, 895, 927, 959, 991, - 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, - 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, - 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, - 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 - }; - - private final boolean opt3001; - private final int inputMax; - private final String inputPath; - - private final int outputMax; - private final int outputMin; - private final int outputChangeMax; - - private int inputLastValue; - private Integer outputLastValue = null; - - public BrightnessControl( - boolean opt3001, - int inputMax, - String inputPath, - int outputMin, - int outputMax, - int outputChangeMax) { - this.opt3001 = opt3001; - this.inputMax = inputMax; - this.inputPath = inputPath; - this.outputMax = outputMax; - this.outputMin = outputMin; - this.outputChangeMax = outputChangeMax; - - this.inputLastValue = INPUT_MIN - INPUT_THRESHOLD; - } - - int update() throws IOException { - var inputValue = getInputValue(); - var outputValue = calculateOutput(inputValue); - var dimmedValue = getDimmedValue(outputValue); - logValues(inputValue, outputValue, dimmedValue); - inputLastValue = inputValue; - outputLastValue = dimmedValue; - return dimmedValue; - } - - private int getInputValue() throws IOException { - int inputValue; - if (opt3001) { - try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { - inputValue = (int) Double.parseDouble(inputReader.readLine().trim()); - } catch (IOException e) { - inputValue = INPUT_MIN; - } - } else { - try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { - inputValue = Integer.parseInt(inputReader.readLine().trim()); - } - if (0 <= inputValue && inputValue < SENSOR_VALUE_LUX_APPROX_MAP.length) { - inputValue = SENSOR_VALUE_LUX_APPROX_MAP[inputValue]; - } else { - inputValue = inputLastValue; - } - } - - inputValue = Math.min(inputValue, inputMax); - - if (Math.abs(inputValue - inputLastValue) < INPUT_THRESHOLD) { - inputValue = inputLastValue; - } - return inputValue; - } - - private int calculateOutput(int inputValue) { - double a = (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN); - int value1 = (int) (a * (outputMax - outputMin) + outputMin); - return Math.min(value1, outputMax); - } - - private int getDimmedValue(int outputValue) { - if (outputLastValue == null) { - return outputValue; - } - if (outputValue >= outputLastValue) { - return Math.min(outputValue, outputLastValue + outputChangeMax); - } else { - return Math.max(outputValue, outputLastValue - outputChangeMax); - } - } - - @SuppressWarnings({"PMD.SystemPrintln"}) - private void logValues(int inputValue, int outputValue, int dimmedValue) { - System.out.printf( - "input: %4d (%4.1f%%), output: %4d (%4.1f%%), dimmed: %4d (%4.1f%%)%n", - inputValue, - 100 * (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN), - outputValue, - 100 * (outputValue - outputMin) / (double) (outputMax - outputMin), - dimmedValue, - 100 * (dimmedValue - outputMin) / (double) (outputMax - outputMin)); - System.out.flush(); - } -} 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 deleted file mode 100644 index 8446f7db..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogic.java +++ /dev/null @@ -1,127 +0,0 @@ -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 deleted file mode 100644 index b741d19c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/Main.java +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 61f0c6c0..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutor.java +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index f3a62aab..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 6b266558..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/WinCondition.java +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index 3d2ccef7..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardCoordinates.java +++ /dev/null @@ -1,52 +0,0 @@ -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 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 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 deleted file mode 100644 index 73be190c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardObserver.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 2cb6afe8..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/JumpGambleResult.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 2f208134..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Move.java +++ /dev/null @@ -1,108 +0,0 @@ -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 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( - 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 deleted file mode 100644 index c2d3cb30..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Piece.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index ff48a421..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/Player.java +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 74d4949f..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Board.java +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index e4ad021b..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Command.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 879fbd19..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpGambleLostMove.java +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index aad2cc78..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/JumpMove.java +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index caf6a7f9..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/SimpleMove.java +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 13622633..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/Store.java +++ /dev/null @@ -1,120 +0,0 @@ -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 { - Map 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 { - Map 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 deleted file mode 100644 index bdbae8e6..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonal.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 68c790b3..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsForwardIfNotKing.java +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index 3380c0fc..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLength.java +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 42dc6b24..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveValidator.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 6db87cf1..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossible.java +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index 99e77f82..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJump.java +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 87847a1c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValid.java +++ /dev/null @@ -1,16 +0,0 @@ -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(); - Optional startPiece = board.getPieceAt(start); - return startPiece.filter(piece -> piece.owner() == move.player()).isPresent(); - } -} 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 deleted file mode 100644 index ae41a8d9..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmpty.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index e8742c04..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinter.java +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 2eadbb6c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosser.java +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index a047b3e2..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Console.java +++ /dev/null @@ -1,30 +0,0 @@ -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 deleted file mode 100644 index 260f71f8..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculator.java +++ /dev/null @@ -1,49 +0,0 @@ -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; - } - } - } - } - } - Map 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 deleted file mode 100644 index 11c1fb3e..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/checkers/util/Tuple.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 4ba1e8d0..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/Button.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 63da870a..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/ButtonFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 64580b89..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButton.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index d1cf12bc..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/FlatButtonFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 00751dec..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButton.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index cc33a6d0..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/gui/RoundedButtonFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -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/organisation/ConfigurationOrganisationSupplier.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/ConfigurationOrganisationSupplier.java deleted file mode 100644 index bad0ec56..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/ConfigurationOrganisationSupplier.java +++ /dev/null @@ -1,26 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation; - -import ch.scs.jumpstart.pattern.examples.organisation.infra.Configuration; -import ch.scs.jumpstart.pattern.examples.organisation.infra.OrganisationRepository; -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Organisation; - -@SuppressWarnings("unused") -public class ConfigurationOrganisationSupplier implements OrganisationSupplier { - - private final Configuration config; - private final OrganisationRepository organisationRepository; - - public ConfigurationOrganisationSupplier( - Configuration config, OrganisationRepository organisationRepository) { - this.config = config; - this.organisationRepository = organisationRepository; - } - - @Override - public Organisation getOrganisation() { - return config - .getOrganisationId() - .map(organisationRepository::getById) - .orElseThrow(() -> new RuntimeException("Organisation ID not found")); - } -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/LocationOrganisationProvider.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/LocationOrganisationProvider.java deleted file mode 100644 index 1e8f8f15..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/LocationOrganisationProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation; - -import ch.scs.jumpstart.pattern.examples.organisation.infra.Configuration; -import ch.scs.jumpstart.pattern.examples.organisation.infra.LocationRepository; -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Location; -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Organisation; - -@SuppressWarnings("unused") -public class LocationOrganisationProvider implements OrganisationSupplier { - - private final Configuration config; - private final LocationRepository locationRepository; - - public LocationOrganisationProvider(Configuration config, LocationRepository locationRepository) { - this.config = config; - this.locationRepository = locationRepository; - } - - @Override - public Organisation getOrganisation() { - return config - .getOrganisationId() - .map(locationRepository::getById) - .map(Location::organisation) - .orElseThrow(() -> new RuntimeException("Location not found")); - } -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/OrganisationSupplier.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/OrganisationSupplier.java deleted file mode 100644 index bbea8d3e..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/OrganisationSupplier.java +++ /dev/null @@ -1,8 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation; - -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Organisation; - -@SuppressWarnings("unused") -public interface OrganisationSupplier { - Organisation getOrganisation(); -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/Configuration.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/Configuration.java deleted file mode 100644 index 060bb45e..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/Configuration.java +++ /dev/null @@ -1,14 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation.infra; - -import java.util.Optional; - -public class Configuration { - public Optional getOrganisationId() { - // as example, this would read it from some kind of configuration. - return Optional.of(0L); - } - - public Optional getLocationId() { - return Optional.of(0L); - } -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/LocationRepository.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/LocationRepository.java deleted file mode 100644 index e32db2d1..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/LocationRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation.infra; - -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Location; - -public interface LocationRepository { - Location getById(long id); -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/OrganisationRepository.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/OrganisationRepository.java deleted file mode 100644 index 06d2300f..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/OrganisationRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation.infra; - -import ch.scs.jumpstart.pattern.examples.organisation.infra.domain.Organisation; - -public interface OrganisationRepository { - Organisation getById(long id); -} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Location.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Location.java deleted file mode 100644 index 1f3e9db8..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Location.java +++ /dev/null @@ -1,3 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation.infra.domain; - -public record Location(long id, String name, Organisation organisation) {} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Organisation.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Organisation.java deleted file mode 100644 index d9f75b36..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/organisation/infra/domain/Organisation.java +++ /dev/null @@ -1,3 +0,0 @@ -package ch.scs.jumpstart.pattern.examples.organisation.infra.domain; - -public record Organisation(long id, String name) {} 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 deleted file mode 100644 index 930e22bd..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Customer1ReceiptFactory.java +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 76dc0f4b..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/Receipt.java +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 6d3e9f4b..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/receipts/ReceiptFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -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/BrightnessControlTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java deleted file mode 100644 index 7bd55afb..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package ch.scs.jumpstart.pattern.examples; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class BrightnessControlTest { - public static final int INPUT_MAX = 4015; - public static final int OUTPUT_MAX = 1000; - public static final int OUTPUT_MIN = 0; - public static final int OUTPUT_CHANGE_MAX = 10; - - @TempDir private Path tempDir; - - private Path input; - - @BeforeEach - void setup() { - input = tempDir.resolve("input"); - } - - @Test - void climbs_to_target_value_in_steps() throws IOException { - var brightnessControl = - new BrightnessControl( - false, INPUT_MAX, input.toString(), OUTPUT_MIN, OUTPUT_MAX, OUTPUT_CHANGE_MAX); - Files.writeString(input, "0"); - brightnessControl.update(); - - Files.writeString(input, "60"); - - var outputValues = new ArrayList<>(); - for (int __ : IntStream.range(0, 10).toArray()) { - int update = brightnessControl.update(); - outputValues.add(update); - } - - assertThat(outputValues).isEqualTo(List.of(10, 20, 30, 40, 50, 52, 52, 52, 52, 52)); - } -} 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 deleted file mode 100644 index 41d6d15a..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/GameLogicTest.java +++ /dev/null @@ -1,409 +0,0 @@ -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 deleted file mode 100644 index 8bde14da..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/MoveExecutorTest.java +++ /dev/null @@ -1,115 +0,0 @@ -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 deleted file mode 100644 index 150b532c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/WinConditionTest.java +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index 3d07b8c8..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/BoardTest.java +++ /dev/null @@ -1,180 +0,0 @@ -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 deleted file mode 100644 index eabf88d8..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/MoveTest.java +++ /dev/null @@ -1,168 +0,0 @@ -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 deleted file mode 100644 index 37ab7f83..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/dom/board/StoreTest.java +++ /dev/null @@ -1,62 +0,0 @@ -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 deleted file mode 100644 index 3a2cc5cc..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveIsDiagonalTest.java +++ /dev/null @@ -1,97 +0,0 @@ -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 deleted file mode 100644 index 2b8cfe49..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/MoveLengthTest.java +++ /dev/null @@ -1,109 +0,0 @@ -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 deleted file mode 100644 index c599197d..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/NoOtherMoveToJumpPossibleTest.java +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index f6511577..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/OpponentPieceBetweenJumpTest.java +++ /dev/null @@ -1,86 +0,0 @@ -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 deleted file mode 100644 index a3dea968..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/StartPieceValidTest.java +++ /dev/null @@ -1,84 +0,0 @@ -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 deleted file mode 100644 index e290b25c..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/movevalidator/TargetFieldEmptyTest.java +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 55db415d..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/BoardPrinterTest.java +++ /dev/null @@ -1,71 +0,0 @@ -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 deleted file mode 100644 index d054c26a..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/CoinTosserTest.java +++ /dev/null @@ -1,88 +0,0 @@ -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 deleted file mode 100644 index a6cbc08a..00000000 --- a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/checkers/util/PointsCalculatorTest.java +++ /dev/null @@ -1,46 +0,0 @@ -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/code/slides/abstract-factory/abstract-factory-bad-case.py b/topics/sw_concepts/code/slides/abstract-factory/abstract-factory-bad-case.py new file mode 100644 index 00000000..144085f2 --- /dev/null +++ b/topics/sw_concepts/code/slides/abstract-factory/abstract-factory-bad-case.py @@ -0,0 +1,78 @@ +from abc import ABC, abstractmethod + + +class Label(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class Button(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class LightLabel(Label): + def render(self) -> str: + return "Light Label" + + +class DarkLabel(Label): + def render(self) -> str: + return "Dark Label" + + +class LightButton(Button): + def render(self) -> str: + return "Light Button" + + +class DarkButton(Button): + def render(self) -> str: + return "Dark Button" + + +class Header: + def __init__(self, darkmode: bool) -> None: + if darkmode: + self.label: Label = DarkLabel() + else: + self.label = LightLabel() + + def display(self) -> str: + return f"Header: {self.label.render()}" + + +class MainView: + def __init__(self, darkmode: bool) -> None: + if darkmode: + self.label: Label = DarkLabel() + self.button: Button = DarkButton() + else: + self.label = LightLabel() + self.button = LightButton() + + def display(self) -> str: + return f"MainView: {self.label.render()}, {self.button.render()}" + + +class Bootstrap: + def __init__(self, darkmode: bool) -> None: + self.darkmode = darkmode + self.header = Header(darkmode) + self.main_view = MainView(darkmode) + + def run(self) -> None: + print(self.header.display()) + print(self.main_view.display()) + + +if __name__ == "__main__": + print("Light mode:") + light_app = Bootstrap(darkmode=False) + light_app.run() + + print("\nDark mode:") + dark_app = Bootstrap(darkmode=True) + dark_app.run() diff --git a/topics/sw_concepts/code/slides/abstract-factory/abstract-factory-good-case.py b/topics/sw_concepts/code/slides/abstract-factory/abstract-factory-good-case.py new file mode 100644 index 00000000..bd2888c0 --- /dev/null +++ b/topics/sw_concepts/code/slides/abstract-factory/abstract-factory-good-case.py @@ -0,0 +1,101 @@ +from abc import ABC, abstractmethod + + +class Label(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class Button(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class LightLabel(Label): + def render(self) -> str: + return "Light Label" + + +class DarkLabel(Label): + def render(self) -> str: + return "Dark Label" + + +class LightButton(Button): + def render(self) -> str: + return "Light Button" + + +class DarkButton(Button): + def render(self) -> str: + return "Dark Button" + + +class ComponentFactory(ABC): + @abstractmethod + def create_label(self) -> Label: + pass + + @abstractmethod + def create_button(self) -> Button: + pass + + +class LightComponentFactory(ComponentFactory): + def create_label(self) -> Label: + return LightLabel() + + def create_button(self) -> Button: + return LightButton() + + +class DarkComponentFactory(ComponentFactory): + def create_label(self) -> Label: + return DarkLabel() + + def create_button(self) -> Button: + return DarkButton() + + +class Header: + def __init__(self, factory: ComponentFactory) -> None: + self.label: Label = factory.create_label() + + def display(self) -> str: + return f"Header: {self.label.render()}" + + +class MainView: + def __init__(self, factory: ComponentFactory) -> None: + self.label: Label = factory.create_label() + self.button: Button = factory.create_button() + + def display(self) -> str: + return f"MainView: {self.label.render()}, {self.button.render()}" + + +class Bootstrap: + def __init__(self, darkmode: bool) -> None: + if darkmode: + factory: ComponentFactory = DarkComponentFactory() + else: + factory = LightComponentFactory() + + self.header = Header(factory) + self.main_view = MainView(factory) + + def run(self) -> None: + print(self.header.display()) + print(self.main_view.display()) + + +if __name__ == "__main__": + print("Light mode:") + light_app = Bootstrap(darkmode=False) + light_app.run() + + print("\nDark mode:") + dark_app = Bootstrap(darkmode=True) + dark_app.run() diff --git a/topics/sw_concepts/code/slides/command/command-bad-case.py b/topics/sw_concepts/code/slides/command/command-bad-case.py new file mode 100644 index 00000000..46510357 --- /dev/null +++ b/topics/sw_concepts/code/slides/command/command-bad-case.py @@ -0,0 +1,72 @@ +from abc import ABC, abstractmethod + + +class ThemeObservable: + def __init__(self) -> None: + self._is_dark_theme: bool = False + + def set_is_dark_theme(self, value: bool) -> None: + self._is_dark_theme = value + theme_name = "Dark" if value else "Light" + print(f"Theme changed to {theme_name}") + + def is_dark_theme(self) -> bool: + return self._is_dark_theme + + +class DarkThemeButton(ABC): + def __init__(self, theme: ThemeObservable) -> None: + self.theme = theme + + @abstractmethod + def on_action(self) -> None: + pass + + +class DarkDarkThemeButton(DarkThemeButton): + def on_action(self) -> None: + print("Dark theme button clicked (in dark UI)") + self.theme.set_is_dark_theme(True) + + +class LightDarkThemeButton(DarkThemeButton): + def on_action(self) -> None: + print("Dark theme button clicked (in light UI)") + self.theme.set_is_dark_theme(True) + + +class LightThemeButton(ABC): + def __init__(self, theme: ThemeObservable) -> None: + self.theme = theme + + @abstractmethod + def on_action(self) -> None: + pass + + +class DarkLightThemeButton(LightThemeButton): + def on_action(self) -> None: + print("Light theme button clicked (in dark UI)") + self.theme.set_is_dark_theme(False) + + +class LightLightThemeButton(LightThemeButton): + def on_action(self) -> None: + print("Light theme button clicked (in light UI)") + self.theme.set_is_dark_theme(False) + + +if __name__ == "__main__": + theme = ThemeObservable() + + print("Starting in light mode:") + dark_btn = LightDarkThemeButton(theme) # Dark button for light UI + light_btn = LightLightThemeButton(theme) # Light button for light UI + + dark_btn.on_action() + + print("\nNow in dark mode, need different button instances:") + dark_btn_2 = DarkDarkThemeButton(theme) # Dark button for dark UI + light_btn_2 = DarkLightThemeButton(theme) # Light button for dark UI + + light_btn_2.on_action() diff --git a/topics/sw_concepts/code/slides/command/command-good-case.py b/topics/sw_concepts/code/slides/command/command-good-case.py new file mode 100644 index 00000000..37b94704 --- /dev/null +++ b/topics/sw_concepts/code/slides/command/command-good-case.py @@ -0,0 +1,87 @@ +from abc import ABC, abstractmethod + + +class ThemeObservable: + def __init__(self) -> None: + self._is_dark_theme: bool = False + + def set_is_dark_theme(self, value: bool) -> None: + self._is_dark_theme = value + theme_name = "Dark" if value else "Light" + print(f"[Theme] Theme changed to {theme_name}") + + def is_dark_theme(self) -> bool: + return self._is_dark_theme + + +class Command(ABC): + @abstractmethod + def execute(self) -> None: + pass + + +class SetDarkThemeCommand(Command): + def __init__(self, theme: ThemeObservable) -> None: + self.theme = theme + + def execute(self) -> None: + print("Executing: Set Dark Theme") + self.theme.set_is_dark_theme(True) + + +class SetLightThemeCommand(Command): + def __init__(self, theme: ThemeObservable) -> None: + self.theme = theme + + def execute(self) -> None: + print("Executing: Set Light Theme") + self.theme.set_is_dark_theme(False) + + +class DarkButton: + def __init__(self) -> None: + self._command: Command | None = None + + def set_command(self, command: Command) -> None: + self._command = command + + def on_action(self) -> None: + if self._command: + print("Dark button clicked") + self._command.execute() + else: + print("Dark button has no command set") + + +class LightButton: + def __init__(self) -> None: + self._command: Command | None = None + + def set_command(self, command: Command) -> None: + self._command = command + + def on_action(self) -> None: + if self._command: + print("Light button clicked") + self._command.execute() + else: + print("Light button has no command set") + + +if __name__ == "__main__": + theme = ThemeObservable() + + dark_button = DarkButton() + dark_button.set_command(SetLightThemeCommand(theme)) + + light_button = LightButton() + light_button.set_command(SetDarkThemeCommand(theme)) + + print("Starting in light mode:") + dark_button.on_action() + + print("\nNow in dark mode:") + light_button.on_action() + + print("\nBack to dark mode:") + dark_button.on_action() diff --git a/topics/sw_concepts/code/slides/dependency-inversion/no-dependency-inversion.py b/topics/sw_concepts/code/slides/dependency-inversion/no-dependency-inversion.py new file mode 100644 index 00000000..a9f21277 --- /dev/null +++ b/topics/sw_concepts/code/slides/dependency-inversion/no-dependency-inversion.py @@ -0,0 +1,51 @@ +from datetime import datetime + + +class Transaction: + def __init__(self, transaction_id: str, amount: float, timestamp: datetime) -> None: + self.transaction_id = transaction_id + self.amount = amount + self.timestamp = timestamp + + def __repr__(self) -> str: + return f"Transaction({self.transaction_id}, ${self.amount:.2f}, {self.timestamp})" + + +class DBTransactionRepository: + def __init__(self) -> None: + self._transactions: dict[str, Transaction] = {} + + def save(self, transaction: Transaction) -> None: + print(f"[DB] Saving transaction to database: {transaction.transaction_id}") + self._transactions[transaction.transaction_id] = transaction + + def find_by_id(self, transaction_id: str) -> Transaction | None: + print(f"[DB] Querying database for transaction: {transaction_id}") + return self._transactions.get(transaction_id) + + +class TransactionService: + def __init__(self) -> None: + self.repository = DBTransactionRepository() + + def create_transaction(self, transaction_id: str, amount: float) -> Transaction: + """Create and save a new transaction""" + transaction = Transaction(transaction_id, amount, datetime.now()) + self.repository.save(transaction) + return transaction + + def get_transaction(self, transaction_id: str) -> Transaction | None: + """Retrieve a transaction by ID""" + return self.repository.find_by_id(transaction_id) + + +if __name__ == "__main__": + service = TransactionService() + + print("Creating transaction:") + txn = service.create_transaction("TXN-001", 99.99) + print(f"Created: {txn}") + + print("\nRetrieving transaction:") + retrieved = service.get_transaction("TXN-001") + print(f"Retrieved: {retrieved}") diff --git a/topics/sw_concepts/code/slides/dependency-inversion/with-dependency-inversion.py b/topics/sw_concepts/code/slides/dependency-inversion/with-dependency-inversion.py new file mode 100644 index 00000000..8f45e572 --- /dev/null +++ b/topics/sw_concepts/code/slides/dependency-inversion/with-dependency-inversion.py @@ -0,0 +1,80 @@ +from abc import ABC, abstractmethod +from datetime import datetime + + +class Transaction: + def __init__(self, transaction_id: str, amount: float, timestamp: datetime) -> None: + self.transaction_id = transaction_id + self.amount = amount + self.timestamp = timestamp + + def __repr__(self) -> str: + return f"Transaction({self.transaction_id}, ${self.amount:.2f}, {self.timestamp})" + + +class TransactionRepository(ABC): + @abstractmethod + def save(self, transaction: Transaction) -> None: + pass + + @abstractmethod + def find_by_id(self, transaction_id: str) -> Transaction | None: + pass + + +class TransactionService: + def __init__(self, repository: TransactionRepository) -> None: + self.repository = repository + + def create_transaction(self, transaction_id: str, amount: float) -> Transaction: + transaction = Transaction(transaction_id, amount, datetime.now()) + self.repository.save(transaction) + return transaction + + def get_transaction(self, transaction_id: str) -> Transaction | None: + return self.repository.find_by_id(transaction_id) + + +class DBTransactionRepository(TransactionRepository): + def __init__(self) -> None: + self._transactions: dict[str, Transaction] = {} + + def save(self, transaction: Transaction) -> None: + print(f"[DB] Saving transaction to database: {transaction.transaction_id}") + self._transactions[transaction.transaction_id] = transaction + + def find_by_id(self, transaction_id: str) -> Transaction | None: + print(f"[DB] Querying database for transaction: {transaction_id}") + return self._transactions.get(transaction_id) + + +class InMemoryTransactionRepository(TransactionRepository): + def __init__(self) -> None: + self._transactions: dict[str, Transaction] = {} + + def save(self, transaction: Transaction) -> None: + print(f"[Memory] Saving transaction to memory: {transaction.transaction_id}") + self._transactions[transaction.transaction_id] = transaction + + def find_by_id(self, transaction_id: str) -> Transaction | None: + print(f"[Memory] Retrieving transaction from memory: {transaction_id}") + return self._transactions.get(transaction_id) + + +if __name__ == "__main__": + print("Using DB Repository:") + db_repo = DBTransactionRepository() + service = TransactionService(db_repo) + + txn = service.create_transaction("TXN-001", 99.99) + print(f"Created: {txn}") + + retrieved = service.get_transaction("TXN-001") + print(f"Retrieved: {retrieved}") + + print("\nUsing In-Memory Repository:") + memory_repo = InMemoryTransactionRepository() + service2 = TransactionService(memory_repo) + + txn2 = service2.create_transaction("TXN-002", 49.99) + print(f"Created: {txn2}") diff --git a/topics/sw_concepts/code/slides/factory/factory-bad-case.py b/topics/sw_concepts/code/slides/factory/factory-bad-case.py new file mode 100644 index 00000000..3fef7be5 --- /dev/null +++ b/topics/sw_concepts/code/slides/factory/factory-bad-case.py @@ -0,0 +1,53 @@ +from abc import ABC, abstractmethod + + +class Label(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class LightLabel(Label): + def render(self) -> str: + return "[Light Label]" + + +class DarkLabel(Label): + def render(self) -> str: + return "[Dark Label]" + + +class Header: + def __init__(self, is_dark_theme: bool) -> None: + if is_dark_theme: + self.title_label: Label = DarkLabel() + else: + self.title_label = LightLabel() + + def display(self) -> str: + return f"Header: {self.title_label.render()}" + + +class MainView: + def __init__(self, is_dark_theme: bool) -> None: + if is_dark_theme: + self.content_label: Label = DarkLabel() + else: + self.content_label = LightLabel() + + def display(self) -> str: + return f"MainView: {self.content_label.render()}" + + +if __name__ == "__main__": + print("Light theme:") + header_light = Header(is_dark_theme=False) + main_light = MainView(is_dark_theme=False) + print(header_light.display()) + print(main_light.display()) + + print("\nDark theme:") + header_dark = Header(is_dark_theme=True) + main_dark = MainView(is_dark_theme=True) + print(header_dark.display()) + print(main_dark.display()) diff --git a/topics/sw_concepts/code/slides/factory/factory-good-case.py b/topics/sw_concepts/code/slides/factory/factory-good-case.py new file mode 100644 index 00000000..c93ed089 --- /dev/null +++ b/topics/sw_concepts/code/slides/factory/factory-good-case.py @@ -0,0 +1,59 @@ +from abc import ABC, abstractmethod + + +class Label(ABC): + @abstractmethod + def render(self) -> str: + pass + + +class LightLabel(Label): + def render(self) -> str: + return "[Light Label]" + + +class DarkLabel(Label): + def render(self) -> str: + return "[Dark Label]" + + +class LabelFactory: + def __init__(self, is_dark_theme: bool) -> None: + self.is_dark_theme = is_dark_theme + + def create_label(self) -> Label: + if self.is_dark_theme: + return DarkLabel() + return LightLabel() + + +class Header: + def __init__(self, factory: LabelFactory) -> None: + self.title_label: Label = factory.create_label() + + def display(self) -> str: + return f"Header: {self.title_label.render()}" + + +class MainView: + def __init__(self, factory: LabelFactory) -> None: + self.content_label: Label = factory.create_label() + + def display(self) -> str: + return f"MainView: {self.content_label.render()}" + + +if __name__ == "__main__": + print("Light theme:") + light_factory = LabelFactory(is_dark_theme=False) + header_light = Header(light_factory) + main_light = MainView(light_factory) + print(header_light.display()) + print(main_light.display()) + + print("\nDark theme:") + dark_factory = LabelFactory(is_dark_theme=True) + header_dark = Header(dark_factory) + main_dark = MainView(dark_factory) + print(header_dark.display()) + print(main_dark.display()) diff --git a/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-bad-case.py b/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-bad-case.py new file mode 100644 index 00000000..d5c6fae0 --- /dev/null +++ b/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-bad-case.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod + + +class Washer(ABC): + @abstractmethod + def wash_clothes(self) -> None: + pass + + @abstractmethod + def wash_dishes(self) -> None: + pass + + +class HandWasher(Washer): + def wash_clothes(self) -> None: + print("Hand washing clothes in basin") + + def wash_dishes(self) -> None: + print("Hand washing dishes in sink") + + +class DishWashingMachine(Washer): + def wash_dishes(self) -> None: + print("Machine washing dishes with hot water and detergent") + + def wash_clothes(self) -> None: + raise NotImplementedError( + "DishWashingMachine cannot wash clothes! This method should not exist for this class." + ) + + +if __name__ == "__main__": + print("HandWasher (flexible, can do both):") + hand_washer = HandWasher() + hand_washer.wash_clothes() + hand_washer.wash_dishes() + + print("\nDishWashingMachine (specialized):") + dish_machine = DishWashingMachine() + dish_machine.wash_dishes() + + print("\nTrying to wash clothes with dish machine:") + try: + dish_machine.wash_clothes() + except NotImplementedError as e: + print(f"Error: {e}") diff --git a/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-good-case.py b/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-good-case.py new file mode 100644 index 00000000..c487a3c5 --- /dev/null +++ b/topics/sw_concepts/code/slides/interface-segregation/interface-segregation-good-case.py @@ -0,0 +1,53 @@ +from abc import ABC, abstractmethod + + +class ClothesWasher(ABC): + @abstractmethod + def wash_clothes(self) -> None: + pass + + +class DishWasher(ABC): + @abstractmethod + def wash_dishes(self) -> None: + pass + + +class HandWasher(ClothesWasher, DishWasher): + def wash_clothes(self) -> None: + print("Hand washing clothes in basin") + + def wash_dishes(self) -> None: + print("Hand washing dishes in sink") + + +class DishWashingMachine(DishWasher): + def wash_dishes(self) -> None: + print("Machine washing dishes with hot water and detergent") + + +def clean_dishes(washer: DishWasher) -> None: + washer.wash_dishes() + + +def clean_clothes(washer: ClothesWasher) -> None: + washer.wash_clothes() + + +if __name__ == "__main__": + print("HandWasher (flexible, implements both interfaces):") + hand_washer = HandWasher() + hand_washer.wash_clothes() + hand_washer.wash_dishes() + + print("\nDishWashingMachine (specialized, implements only DishWasher):") + dish_machine = DishWashingMachine() + dish_machine.wash_dishes() + + print("\nUsing polymorphism with DishWasher interface:") + clean_dishes(hand_washer) + clean_dishes(dish_machine) + + print("\nUsing ClothesWasher interface:") + clean_clothes(hand_washer) + # dish_machine cannot be passed here - it doesn't implement ClothesWasher! diff --git a/topics/sw_concepts/code/slides/strategy/strategy-bad-case.py b/topics/sw_concepts/code/slides/strategy/strategy-bad-case.py new file mode 100644 index 00000000..8ad8c07c --- /dev/null +++ b/topics/sw_concepts/code/slides/strategy/strategy-bad-case.py @@ -0,0 +1,55 @@ +from abc import ABC, abstractmethod + + +class Label(ABC): + @abstractmethod + def paint_background(self) -> None: + pass + + +class LightLabel(Label): + def paint_background(self) -> None: + print(" Painting LIGHT background for label") + print(" - Setting background color: #FFFFFF") + + +class DarkLabel(Label): + def paint_background(self) -> None: + print(" Painting DARK background for label") + print(" - Setting background color: #1E1E1E") + + +class Button(ABC): + @abstractmethod + def paint_background(self) -> None: + pass + + +class LightButton(Button): + def paint_background(self) -> None: + print(" Painting LIGHT background for button") + print(" - Setting background color: #FFFFFF") + + +class DarkButton(Button): + def paint_background(self) -> None: + print(" Painting DARK background for button") + print(" - Setting background color: #1E1E1E") + + +if __name__ == "__main__": + print("Light theme components:") + light_label = LightLabel() + light_button = LightButton() + print("Label:") + light_label.paint_background() + print("Button:") + light_button.paint_background() + + print("\nDark theme components:") + dark_label = DarkLabel() + dark_button = DarkButton() + print("Label:") + dark_label.paint_background() + print("Button:") + dark_button.paint_background() diff --git a/topics/sw_concepts/code/slides/strategy/strategy-good-case.py b/topics/sw_concepts/code/slides/strategy/strategy-good-case.py new file mode 100644 index 00000000..a167ef59 --- /dev/null +++ b/topics/sw_concepts/code/slides/strategy/strategy-good-case.py @@ -0,0 +1,77 @@ +from abc import ABC, abstractmethod + + +class PaintBackgroundStrategy(ABC): + @abstractmethod + def execute(self) -> None: + pass + + +class LightBackgroundStrategy(PaintBackgroundStrategy): + def execute(self) -> None: + print(" Painting LIGHT background") + print(" - Setting background color: #FFFFFF") + + +class DarkBackgroundStrategy(PaintBackgroundStrategy): + def execute(self) -> None: + print(" Painting DARK background") + print(" - Setting background color: #1E1E1E") + + +class Label: + def __init__(self, background_strategy: PaintBackgroundStrategy) -> None: + self.background_strategy = background_strategy + + def paint_background(self) -> None: + self.background_strategy.execute() + + +class LightLabel(Label): + pass + + +class DarkLabel(Label): + pass + + +class Button: + def __init__(self, background_strategy: PaintBackgroundStrategy) -> None: + self.background_strategy = background_strategy + + def paint_background(self) -> None: + self.background_strategy.execute() + + +class LightButton(Button): + pass + + +class DarkButton(Button): + pass + + +if __name__ == "__main__": + light_strategy = LightBackgroundStrategy() + dark_strategy = DarkBackgroundStrategy() + + print("Light theme components (using shared strategy):") + light_label = LightLabel(light_strategy) + light_button = LightButton(light_strategy) + print("Label:") + light_label.paint_background() + print("Button:") + light_button.paint_background() + + print("\nDark theme components (using shared strategy):") + dark_label = DarkLabel(dark_strategy) + dark_button = DarkButton(dark_strategy) + print("Label:") + dark_label.paint_background() + print("Button:") + dark_button.paint_background() + + print("\nChanging label strategy at runtime:") + light_label.background_strategy = dark_strategy + print("Light label with dark background:") + light_label.paint_background() diff --git a/topics/sw_concepts/code/slides/uml/class-diagram-parts.py b/topics/sw_concepts/code/slides/uml/class-diagram-parts.py new file mode 100644 index 00000000..fcf1df22 --- /dev/null +++ b/topics/sw_concepts/code/slides/uml/class-diagram-parts.py @@ -0,0 +1,37 @@ +from abc import ABC, abstractmethod + + +class Bucket: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + + def __repr__(self) -> str: + return f"Bucket(capacity={self.capacity}L)" + + +class Washer(ABC): + @abstractmethod + def wash_clothes(self) -> None: + pass + + @abstractmethod + def wash_dishes(self) -> None: + pass + + +class HandWasher(Washer): + def __init__(self, bucket: Bucket) -> None: + self.bucket = bucket + + def wash_clothes(self) -> None: + print(f"Washing clothes using {self.bucket}") + + def wash_dishes(self) -> None: + print(f"Washing dishes using {self.bucket}") + + +if __name__ == "__main__": + bucket = Bucket(capacity=10) + hand_washer = HandWasher(bucket) + hand_washer.wash_clothes() + hand_washer.wash_dishes() diff --git a/topics/sw_concepts/code/solution/01-intro-refactored.py b/topics/sw_concepts/code/solution/01-intro-refactored.py new file mode 100644 index 00000000..471788f9 --- /dev/null +++ b/topics/sw_concepts/code/solution/01-intro-refactored.py @@ -0,0 +1,95 @@ +import math +from dataclasses import dataclass + +TAX_RATE = 0.19 +DISCOUNT_THRESHOLD = 50.0 +DISCOUNT_RATE = 0.10 + +EXCHANGE_RATES: dict[str, float] = { + "CHF": 1.0, + "USD": 1.08, +} + + +@dataclass +class Product: + name: str + price: float + + +@dataclass +class LineItem: + product: Product + quantity: int + + @property + def subtotal(self) -> float: + return self.quantity * self.product.price + + +class Order: + def __init__(self, customer: str, items: list[LineItem], currency: str = "CHF") -> None: + self.customer = customer + self.items = items + self.currency = currency + + @property + def subtotal(self) -> float: + return sum(item.subtotal for item in self.items) + + @property + def discount(self) -> float: + if self.subtotal > DISCOUNT_THRESHOLD: + return self.subtotal * DISCOUNT_RATE + return 0.0 + + @property + def taxable_amount(self) -> float: + return self.subtotal - self.discount + + @property + def tax(self) -> float: + return self.taxable_amount * TAX_RATE + + @property + def total(self) -> float: + raw = self.taxable_amount + self.tax + return _floor_cents(raw * EXCHANGE_RATES[self.currency]) + + +def _floor_cents(value: float) -> float: + return math.floor(value * 100) / 100 + + +def print_receipt(order: Order) -> None: + print("=" * 40) + print("RECEIPT") + print("=" * 40) + for item in order.items: + print(f" {item.quantity}x {item.product.name} @ {item.product.price} = {item.subtotal}") + if order.discount > 0: + print(f" Discount ({DISCOUNT_RATE:.0%}): -{order.discount:.2f}") + print(f" Tax ({TAX_RATE:.0%}): {order.tax:.2f}") + print(f" TOTAL: {order.total} {order.currency}") + print("=" * 40) + + +def main() -> None: + widget = Product("Widget", 9.99) + gadget = Product("Gadget", 24.99) + + orders = [ + Order("Alice", [LineItem(widget, 3), LineItem(gadget, 1)]), + Order("Bob", [LineItem(widget, 5)]), + Order("Charlie", [LineItem(widget, 4), LineItem(gadget, 2)], currency="USD"), + ] + + for i, order in enumerate(orders, start=1): + print(f"Order {i} ({order.customer}): {order.total} {order.currency}") + + print() + print_receipt(orders[0]) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/solution/02-interfaces.py b/topics/sw_concepts/code/solution/02-interfaces.py new file mode 100644 index 00000000..dd2d4109 --- /dev/null +++ b/topics/sw_concepts/code/solution/02-interfaces.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod +from enum import StrEnum, auto + + +class Washer(ABC): + @abstractmethod + def wash_clothes(self, item: str): + pass + + @abstractmethod + def wash_dishes(self, item: str): + pass + + +class MachineWasher: + def wash_clothes(self, item: str): + print(f"Machine washing {item}.") + + def wash_dishes(self, item: str): + print(f"Machine washing {item}.") + + +class HandWasher: + def wash_clothes(self, item: str): + print(f"Hand washing {item}.") + + def wash_dishes(self, item: str): + print(f"Hand washing {item}.") + + +class ThingsToWash(StrEnum): + PANTS = auto() + SHIRT = auto() + SPOON = auto() + PLATE = auto() + FORK = auto() + + +def wash_things(washer: Washer, things: list[ThingsToWash]) -> None: + for thing in things: + if thing in (ThingsToWash.PANTS, ThingsToWash.SHIRT): + washer.wash_clothes(str(thing)) + elif thing in (ThingsToWash.SPOON, ThingsToWash.PLATE, ThingsToWash.FORK): + washer.wash_dishes(str(thing)) + + +def main() -> None: + machine_washer = MachineWasher() + hand_washer = HandWasher() + + wash_things(machine_washer, [ThingsToWash.PLATE, ThingsToWash.SHIRT, ThingsToWash.SPOON]) + wash_things(hand_washer, [ThingsToWash.PANTS, ThingsToWash.FORK]) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/solution/03-factory.py b/topics/sw_concepts/code/solution/03-factory.py new file mode 100644 index 00000000..3e84b74b --- /dev/null +++ b/topics/sw_concepts/code/solution/03-factory.py @@ -0,0 +1,69 @@ +from abc import ABC, abstractmethod +from enum import StrEnum, auto + + +class Washer(ABC): + @abstractmethod + def wash_clothes(self, item: str): + pass + + @abstractmethod + def wash_dishes(self, item: str): + pass + + +class MachineWasher: + def wash_clothes(self, item: str): + print(f"Machine washing {item}.") + + def wash_dishes(self, item: str): + print(f"Machine washing {item}.") + + +class HandWasher: + def wash_clothes(self, item: str): + print(f"Hand washing {item}.") + + def wash_dishes(self, item: str): + print(f"Hand washing {item}.") + + +class ThingsToWash(StrEnum): + PANTS = auto() + SHIRT = auto() + SPOON = auto() + PLATE = auto() + FORK = auto() + + +def wash_things(washer: Washer, things: list[ThingsToWash]) -> None: + for thing in things: + if thing in (ThingsToWash.PANTS, ThingsToWash.SHIRT): + washer.wash_clothes(str(thing)) + elif thing in (ThingsToWash.SPOON, ThingsToWash.PLATE, ThingsToWash.FORK): + washer.wash_dishes(str(thing)) + + +def washer_factory(can_wash_fragile_things: bool) -> Washer: + if can_wash_fragile_things: + return HandWasher() + return MachineWasher() + + +def main() -> None: + washer = washer_factory(can_wash_fragile_things=True) + + wash_things( + washer, + [ + ThingsToWash.PLATE, + ThingsToWash.SHIRT, + ThingsToWash.SPOON, + ThingsToWash.PANTS, + ThingsToWash.FORK, + ], + ) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/solution/04-abstract-factory.py b/topics/sw_concepts/code/solution/04-abstract-factory.py new file mode 100644 index 00000000..4d791d0e --- /dev/null +++ b/topics/sw_concepts/code/solution/04-abstract-factory.py @@ -0,0 +1,104 @@ +from abc import ABC, abstractmethod +from enum import StrEnum, auto + + +class ClothesWasher(ABC): + @abstractmethod + def wash(self, item: str) -> None: + pass + + +class DishWasher(ABC): + @abstractmethod + def wash(self, item: str) -> None: + pass + + +class MachineClothesWasher(ClothesWasher): + def wash(self, item: str) -> None: + print(f"Machine washing clothes: {item}.") + + +class MachineDishWasher(DishWasher): + def wash(self, item: str) -> None: + print(f"Machine washing dishes: {item}.") + + +class HandClothesWasher(ClothesWasher): + def wash(self, item: str) -> None: + print(f"Hand washing clothes: {item}.") + + +class HandDishWasher(DishWasher): + def wash(self, item: str) -> None: + print(f"Hand washing dishes: {item}.") + + +class WasherFactory(ABC): + @abstractmethod + def create_clothes_washer(self) -> ClothesWasher: + pass + + @abstractmethod + def create_dish_washer(self) -> DishWasher: + pass + + +class MachineWasherFactory(WasherFactory): + def create_clothes_washer(self) -> ClothesWasher: + return MachineClothesWasher() + + def create_dish_washer(self) -> DishWasher: + return MachineDishWasher() + + +class HandWasherFactory(WasherFactory): + def create_clothes_washer(self) -> ClothesWasher: + return HandClothesWasher() + + def create_dish_washer(self) -> DishWasher: + return HandDishWasher() + + +class ThingsToWash(StrEnum): + PANTS = auto() + SHIRT = auto() + SPOON = auto() + PLATE = auto() + FORK = auto() + + +def wash_things(factory: WasherFactory, things: list[ThingsToWash]) -> None: + clothes_washer = factory.create_clothes_washer() + dish_washer = factory.create_dish_washer() + + for thing in things: + if thing in (ThingsToWash.PANTS, ThingsToWash.SHIRT): + clothes_washer.wash(str(thing)) + elif thing in (ThingsToWash.SPOON, ThingsToWash.PLATE, ThingsToWash.FORK): + dish_washer.wash(str(thing)) + + +def get_washer_factory(can_wash_fragile_things: bool) -> WasherFactory: + if can_wash_fragile_things: + return HandWasherFactory() + return MachineWasherFactory() + + +def main() -> None: + factory = get_washer_factory(can_wash_fragile_things=True) + + wash_things( + factory, + [ + ThingsToWash.PLATE, + ThingsToWash.SHIRT, + ThingsToWash.SPOON, + ThingsToWash.PANTS, + ThingsToWash.FORK, + ], + ) + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/code/solution/05-observer-good-case.py b/topics/sw_concepts/code/solution/05-observer-good-case.py new file mode 100644 index 00000000..83bb4369 --- /dev/null +++ b/topics/sw_concepts/code/solution/05-observer-good-case.py @@ -0,0 +1,94 @@ +from abc import ABC, abstractmethod + + +class ThemeObserver(ABC): + @abstractmethod + def on_theme_changed(self, is_dark_theme: bool) -> None: + pass + + +class ThemeObservable: + def __init__(self) -> None: + self._is_dark_theme: bool = False + self._observers: list[ThemeObserver] = [] + + def add_observer(self, observer: ThemeObserver) -> None: + if observer not in self._observers: + self._observers.append(observer) + + def set_is_dark_theme(self, value: bool) -> None: + if self._is_dark_theme != value: + self._is_dark_theme = value + theme_name = "Dark" if value else "Light" + print(f"[Theme] Theme changed to {theme_name}") + self._notify_observers() + + def _notify_observers(self) -> None: + for observer in self._observers: + observer.on_theme_changed(self._is_dark_theme) + + +class DarkThemeButton(ThemeObserver): + def __init__(self, theme_observable: ThemeObservable) -> None: + self.theme_observable: ThemeObservable = theme_observable + self.enabled: bool = True + + def on_action(self) -> None: + if not self.enabled: + return + + print("Dark theme button clicked") + self.theme_observable.set_is_dark_theme(True) + + def on_theme_changed(self, is_dark_theme: bool) -> None: + self.set_enable(not is_dark_theme) + + def set_enable(self, enable: bool) -> None: + self.enabled = enable + status = "enabled" if enable else "disabled" + print(f" Dark theme button is now {status}") + + +class LightThemeButton(ThemeObserver): + def __init__(self, theme_observable: ThemeObservable) -> None: + self.theme_observable: ThemeObservable = theme_observable + self.enabled: bool = False + + def on_action(self) -> None: + if not self.enabled: + return + + print("Light theme button clicked") + self.theme_observable.set_is_dark_theme(False) + + def on_theme_changed(self, is_dark_theme: bool) -> None: + self.set_enable(is_dark_theme) + + def set_enable(self, enable: bool) -> None: + self.enabled = enable + status = "enabled" if enable else "disabled" + print(f" Light theme button is now {status}") + + +def main() -> None: + theme: ThemeObservable = ThemeObservable() + + dark_button: DarkThemeButton = DarkThemeButton(theme) + light_button: LightThemeButton = LightThemeButton(theme) + + theme.add_observer(dark_button) + theme.add_observer(light_button) + + # Simulate user interactions + print("User clicks dark theme button:") + dark_button.on_action() + + print("\nUser clicks light theme button:") + light_button.on_action() + + print("\nUser clicks dark theme button again:") + dark_button.on_action() + + +if __name__ == "__main__": + main() diff --git a/topics/sw_concepts/sw_concept_slides.md b/topics/sw_concepts/sw_concept_slides.md index 20798fb3..c72ac055 100644 --- a/topics/sw_concepts/sw_concept_slides.md +++ b/topics/sw_concepts/sw_concept_slides.md @@ -5,16 +5,21 @@ title: "SW Konzepte" date: \today --- -Was ist hier falsch? +Was ist hier schlecht? ------- -Code Beispiel: [Abstractions.java](code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java) +Code Beispiel: [01-intro.py](code/exercise/01-intro.py) -Abstractions.java Zusammenfassung +intro.py Zusammenfassung ------- -* Fast keine Strukturierung durch Methoden/Funktionen +* Fast keine Strukturierung durch Methoden/Funktionen/Datenmodelle * Code duplication +* Magic numbers +* Business Logik und Präsentation vermischt + +-> Besser: [01-intro-refactored.py](code/solution/01-intro-refactored.py) + Abstraktion: Nachteile ------- @@ -24,33 +29,6 @@ Abstraktion: Nachteile * Die Abstraktion am falschen Ort (Vertikal) * Die Abstraktion versteckt wichtige Features -Abstraktion am falschen Ort ------- - -```java -public void initialize() { - carouselDisposable.dispose(); - carouselDisposable = contactlessPMod.getCarouselProvider() - .addCarouselObserver( - imagePath -> JfxThreadingUtils.invokeOnFx(() -> setContactlessLogo(imagePath)) - ); -} - -private void setContactlessLogo(String imagePath) { - if (imagePath == null || getAcceptance() != ACCEPTED) { - final String imageFileName = getImageFileName() - final String imageUrl = IMAGES_PATH + imageFileName; - contactlessLogo.setImage(new Image(imageUrl)); - } else { - if (contactlessPMod.isCarouselEnabled()) { - contactlessLogo.setImage(new Image(imagePath)); - } else { - contactlessLogo.setImage(new Image(IMAGES_PATH + "static_carousel.png")); - } - } -} -``` - Agenda ------- @@ -96,21 +74,14 @@ KISS (Keep it simple stupid) ```python -# num % (modulo) 2 with a bitwise operator: -num & 1 -# it is simpler and more readable -# to use the % operator. -num % 2 -``` - -Erklärung Modulo 2 mit Bit operator -------- - -```md -5 = 00000101 & -1 = 00000001 --------------- - 00000001 -> ungerade +# Good example (KISS) +result = sum(numbers) / len(numbers) + +# Bad example (unnecessarily complex) +result = 0 +for num in numbers: + result += num +result = result / len(numbers) ``` YAGNI @@ -136,7 +107,19 @@ NIH NIH Beispiel 1 ------- - +```python +def custom_sort(numbers): + for i in range(len(numbers)): + for j in range(i + 1, len(numbers)): + if numbers[i] > numbers[j]: + numbers[i], numbers[j] = numbers[j], numbers[i] + return numbers + +sorted_list = custom_sort([3, 1, 4, 1, 5]) + +# Besser: built-in Funktion verwenden +sorted_list = sorted([3, 1, 4, 1, 5]) +``` Clean Code ------- @@ -236,7 +219,24 @@ class HandWasher Kurze Übung Interface ------- -Überlegt euch zu zweit ein Beispiel für ein Interface mit 2 Methoden und 2 Implementationen. +Implementiert das Interface `Washer` für die Klasse `MashineWasher` und `HandWasher` in Python. +(Verwendet simple print statements für die Implementierung der Methoden.) + +Das Interface in Python könnte so aussehen (siehe auch [exercise/02-03-interfaces.py](code/exercise/02-03-interfaces.py)): + +```python +from abc import ABC, abstractmethod + +class Washer(ABC): + @abstractmethod + def wash_clothes(self, item: str): + pass + + @abstractmethod + def wash_dishes(self, item: str): + pass +``` + UML ------- @@ -252,7 +252,14 @@ von [UML: ISO/IEC 19501](https://www.omg.org/spec/UML/ISO/19501/PDF) UML Klassendiagramm ------- -![UML Klassendiagramm: verwendete Komponenten](images/uml/class-diagram-parts.png){width=70%} +\colBegin{0.8} +![UML Klassendiagramm: verwendete Komponenten](images/uml/class-diagram-parts.png){width=90%} +\colNext{0.2} +\small + +[class-diagram-parts.py](code/slides/uml/class-diagram-parts.py) + +\colEnd Single Responsibility ------- @@ -299,7 +306,6 @@ public SessionFactoryBuilder withUserAndPassword(String username, String passwor this.password = password; return this; } - ``` Open Closed @@ -317,8 +323,9 @@ Open Closed Beispiel: vorher \colNext{0.2} \small +* [abstract-factory-bad-case.py](code/slides/abstract-factory/abstract-factory-bad-case.py) * Das Modul `views` ist `closed for extension`. -Für GreyLabel und GreyButton müssen alle if statements geändert werden. +* Für GreyLabel und GreyButton müssen alle if statements geändert werden. \colEnd @@ -330,6 +337,7 @@ Open Closed Beispiel: nachher \colNext{0.2} \small +* [abstract-factory-good-case.py](code/slides/abstract-factory/abstract-factory-good-case.py) * Das Modul `views` ist jetzt `open` für weitere Implementationen von ComponentFactory. \colEnd @@ -347,6 +355,7 @@ Beispiel einer Verletzung des Liskov substitution principle ![Liskov substitution principle bad case](images/interface-segregation/interface-segregation-bad-case.png) + Das Liskov Substitution Principle ist hier verletzt, da die `DishWashingMachine::washClothes` Methode sich nicht so verhält, wie das Interface verlangt.\ `DishWashingMachine` kann nicht überall dort eingesetzt werden, so das Interface `Washer` verlangt wird. @@ -365,12 +374,16 @@ Interface Segregation Beispiel vorher ![Interface Segregation bad case](images/interface-segregation/interface-segregation-bad-case.png) +[interface-segregation-bad-case.py](code/slides/interface-segregation/interface-segregation-bad-case.py) + Interface Segregation Beispiel nachher ------ ![Interface Segregation good case](images/interface-segregation/interface-segregation-good-case.png) +[interface-segregation-bad-case.py](code/slides/interface-segregation/interface-segregation-bad-case.py) + Hier lösen wir die Verletzung des Liskov Substitution Principles mit Interface Segregation. Dependency Inversion @@ -386,10 +399,14 @@ Dependency Inversion Beispiel 1 ![No dependency inversion](images/dependency-inversion/no-dependency-inversion.png){width=65%} +[no-dependency-inversion.py](code/slides/dependency-inversion/no-dependency-inversion.py) + Dependency Inversion Beispiel 2 ----- -![Dependency inversion](images/dependency-inversion/with-dependency-inversion.png){height=95%} +![Dependency inversion](images/dependency-inversion/with-dependency-inversion.png){height=80%} + +[with-dependency-inversion.py](code/slides/dependency-inversion/with-dependency-inversion.py) Design Patterns ------- @@ -416,11 +433,27 @@ Factory Beispiel: vorher ![Factory Beispiel: vorher](images/factory/factory-bad-case.png){width=78%} +[factory-bad-case.py](code/slides/factory/factory-bad-case.py) + Factory Beispiel: nachher ------ ![Factory Beispiel: nachher](images/factory/factory-good-case.png) +[factory-good-case.py](code/slides/factory/factory-good-case.py) + +Factory Übung +------ + +Erweitert die Übung [exercise/02-03-interfaces.py](code/exercise/02-03-interfaces.py) um eine Factory, +die `Washer`-Klasse erstellt basierend darauf, ob die Items empfindlich (fragile) sind oder nicht. +Die Factory könnte so aussehen (muss nicht zwangsläufig eine klasse sein): + +```python +def washer_factory(can_wash_fragile_things: bool) -> Washer: + pass +``` + AbstractFactory ------- @@ -439,6 +472,9 @@ AbstractFactory Beispiel: vorher \colNext{0.2} \colEnd +[abstract-factory-bad-case.py](code/slides/abstract-factory/abstract-factory-bad-case.py) + + AbstractFactory Beispiel: nachher ------ @@ -447,6 +483,7 @@ AbstractFactory Beispiel: nachher \colNext{0.2} \small +* [abstract-factory-good-case.py](code/slides/abstract-factory/abstract-factory-good-case.py) * Die Anzahl "if darkmode" statements ist reduziert. * Es ist jetzt viel einfacher, ein "HighContrast"-Theme einzubauen. @@ -458,6 +495,24 @@ AbstractFactory Beispiel 2 ![footer_m](images/abstract-factory/footer_m.png) ![footer_c](images/abstract-factory/footer_c.png) +AbstractFactory Übung +------ + +Wir haben die vorherige Übung etwas erweitert (Interface Segregation). +Nutzt [04-abstract-factory.py](code/exercise/04-abstract-factory.py) als Ausgangspunkt um eine Abstract Factory zu implementieren. + +```python +class WasherFactory(ABC): + @abstractmethod + def create_clothes_washer(self) -> ClothesWasher: + pass + + @abstractmethod + def create_dish_washer(self) -> DishWasher: + pass + +``` + Strategy ------- @@ -468,18 +523,21 @@ Strategy * Beispiele: * Unterschiedliche Algorithmen für ein Ziel (z.b. Suchalgorithmen, Sortieralgorithmen) * Daten können von verschiedenen Quellen kommen (RemoteConfigSourceStrategy, FileConfigSourceStrategy) - Strategy Beispiel: vorher ------- -![Observer bad case](images/strategy/strategy-bad-case.png) +![Strategy bad case](images/strategy/strategy-bad-case.png) + +[strategy-bad-case.py](code/slides/strategy/strategy-bad-case.py) Strategy Beispiel: nachher ------- -![Observer good case](images/strategy/strategy-good-case.png) +![Strategy good case](images/strategy/strategy-good-case.png) + +[strategy-good-case.py](code/slides/strategy/strategy-good-case.py) Observer ------- @@ -494,11 +552,27 @@ Observer Beispiel: vorher ![Observer bad case](images/observer/observer-bad-case.png) +[05-observer-bad-case.py](code/exercises/05-observer-bad-case.py) + Observer Beispiel: nachher ------- ![Observer good case](images/observer/observer-good-case.png){height=90%} +Observer Übung +------- + +Implementiert das Observer Pattern für die folgende Situation. +Nehmt dafür +[05-observer-bad-case.py](code/exercises/05-observer-bad-case.py) als Ausgangspunkt. + +![Observer good case](images/observer/observer-good-case.png){height=75%} + +Observer Lösung +------- + +Lösung: [05-observer-good-case.py](code/solutions/05-observer-good-case.py) + Command ------- @@ -509,12 +583,16 @@ Command Command Beispiel: vorher ------- -![Observer bad case](images/command/command-bad-case.png) +![Command bad case](images/command/command-bad-case.png) + +[command-bad-case.py](code/slides/command/command-bad-case.py) Command Beispiel: nachher ------- -![Observer good case](images/command/command-good-case.png){height=90%} +![Command good case](images/command/command-good-case.png){height=85%} + +[command-good-case.py](code/slides/command/command-good-case.py) Zusammenfassung Abstraktion ------ diff --git a/topics/tools_and_varia/slides/links.md b/topics/tools_and_varia/slides/links.md index ed45dcfb..5a95eb0a 100644 --- a/topics/tools_and_varia/slides/links.md +++ b/topics/tools_and_varia/slides/links.md @@ -96,6 +96,7 @@ Links [active_forks]: https://github.com/techgaun/active-forks [vulnerabilities_in_code_examples]: https://arxiv.org/ftp/arxiv/papers/1910/1910.01321.pdf [quality_chatgpt_as_code_help]: https://www.golem.de/news/ki-im-kreuzfeuer-der-kritik-massive-qualitaetsmaengel-bei-chatgpt-als-programmierhilfe-2405-185442.html +[scs-copilot-infos]: https://supercomputingsystems.atlassian.net/wiki/x/6oMhIQ * [wiki_signal_slot] * [nano_signal_slot] @@ -107,5 +108,6 @@ Links * [active_forks] * [vulnerabilities_in_code_examples] * [quality_chatgpt_as_code_help] +* [scs-copilot-infos] \colEnd diff --git a/topics/tools_and_varia/slides/problem_solving.md b/topics/tools_and_varia/slides/problem_solving.md index a554e7f5..dfbbcbd0 100644 --- a/topics/tools_and_varia/slides/problem_solving.md +++ b/topics/tools_and_varia/slides/problem_solving.md @@ -105,6 +105,18 @@ Verwendung von Libraries aus dem Internet * Gibt es bessere Forks? [active_forks] +AI Coding Assistants +-------------------- + +Die meisten IDEs unterstützen mittlerweile AI Coding Assistants, die den Programmierprozess unterstützen können. +In der SCS verwenden wir z.B. GitHub Copilot (siehe [scs-copilot-infos] für weitere Infos). + +* Copilot über die SCS beziehen: [scs-copilot-infos] +* Immer in Absprache mit dem PL und Team, ob und wie AI-Tools eingesetzt werden dürfen. +* Wie immer mit AI-Tools: *Kritisch hinterfragen, was vorgeschlagen wird und nicht blind übernehmen!* +* Das Tool kann durchaus nützlich sein — man sollte etwas Erfahrung sammeln, wie es am besten eingesetzt werden kann. + + Verwendung von Code aus dem Internet ------------------------------------