From 35f4217fb5f450c2235dcb76b14f1045e1e9adca Mon Sep 17 00:00:00 2001 From: matimiodosky Date: Sat, 18 May 2024 11:22:14 -0300 Subject: [PATCH] added undo-redo tests added default impl updated version --- test-framework/build.gradle | 2 +- .../edu/austral/dissis/chess/test/TestGame.kt | 4 +- .../dissis/chess/test/game/GameInput.kt | 13 +++++ .../dissis/chess/test/game/GameTester.kt | 43 ++++++++++------- .../dissis/chess/test/game/TestGameRunner.kt | 8 ++++ .../chess/test/gameParser/GameBoardParser.kt | 30 ++++++++---- .../main/resources/test_cases/four_redos.md | 47 +++++++++++++++++++ .../test_cases/redo_pawn_movement.md | 39 +++++++++++++++ .../test_cases/undo_pawn_movement.md | 38 +++++++++++++++ .../undo_pawn_movement_still_black_turn.md | 40 ++++++++++++++++ .../undo_pawn_movement_still_white_turn.md | 39 +++++++++++++++ .../src/test/kotlin/RunnersHistory.kt | 30 ++++++++++++ .../src/test/kotlin/TestGameRunnerImpl.kt | 22 +++++++-- 13 files changed, 324 insertions(+), 31 deletions(-) create mode 100644 test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameInput.kt create mode 100644 test-framework/src/main/resources/test_cases/four_redos.md create mode 100644 test-framework/src/main/resources/test_cases/redo_pawn_movement.md create mode 100644 test-framework/src/main/resources/test_cases/undo_pawn_movement.md create mode 100644 test-framework/src/main/resources/test_cases/undo_pawn_movement_still_black_turn.md create mode 100644 test-framework/src/main/resources/test_cases/undo_pawn_movement_still_white_turn.md create mode 100644 test-framework/src/test/kotlin/RunnersHistory.kt diff --git a/test-framework/build.gradle b/test-framework/build.gradle index a36bbf1..23d05c6 100644 --- a/test-framework/build.gradle +++ b/test-framework/build.gradle @@ -16,7 +16,7 @@ plugins { group = "edu.austral.dissis.chess" -version = "1.3.0" +version = "1.4.0" repositories { mavenLocal() diff --git a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/TestGame.kt b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/TestGame.kt index d3302a8..142c5ae 100644 --- a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/TestGame.kt +++ b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/TestGame.kt @@ -1,9 +1,11 @@ package edu.austral.dissis.chess.test +import edu.austral.dissis.chess.test.game.TestInput + class TestGame( val title: String, val initialBoard: TestBoard, - val movements: List>, + val inputs: List, val testResult: TestGameResult, val finalBoard: TestBoard ) \ No newline at end of file diff --git a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameInput.kt b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameInput.kt new file mode 100644 index 0000000..e77f19d --- /dev/null +++ b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameInput.kt @@ -0,0 +1,13 @@ +package edu.austral.dissis.chess.test.game + +import edu.austral.dissis.chess.test.TestPosition + +sealed interface TestInput { +} + +data class TestMove(val from: TestPosition, val to: TestPosition) : TestInput + +data object Undo : TestInput + +data object Redo : TestInput + diff --git a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameTester.kt b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameTester.kt index 476565d..2cc0a8c 100644 --- a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameTester.kt +++ b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/GameTester.kt @@ -41,28 +41,33 @@ class GameTester(private val runner: TestGameRunner) { private fun runMoves( title: String, runner: TestGameRunner, - moves: List> + moves: List ): TestGameRunner { - return moves.fold(runner) { currentRunner, (from, to) -> - when (val result = currentRunner.executeMove(from, to)) { + return moves.fold(runner) { currentRunner, input -> + val result = when (input) { + is Undo -> currentRunner.undo() + is Redo -> currentRunner.redo() + is TestMove -> currentRunner.executeMove(input.from, input.to) + } + when (result) { is TestMoveSuccess -> result.testGameRunner - is FinalTestMoveResult -> fail(failedMoveMessage(title, from, to, result)) + is FinalTestMoveResult -> fail(failedMoveMessage(title, input, result)) } } } private fun assertAllMovesValid(testGame: TestGame) { val initialRunner = runner.withBoard(testGame.initialBoard) - val resultingRunner = runMoves(testGame.title, initialRunner, testGame.movements) + val resultingRunner = runMoves(testGame.title, initialRunner, testGame.inputs) checkFinalBoardMatches(resultingRunner.getBoard(), testGame.finalBoard) } private fun assertLastMove(testGame: TestGame, checkResult: (FinalTestMoveResult) -> Boolean) { val initialRunner = runner.withBoard(testGame.initialBoard) - val preparatoryMoves = testGame.movements.dropLast(1) - val lastMove = testGame.movements.last() + val preparatoryMoves = testGame.inputs.dropLast(1) + val lastMove = testGame.inputs.last() as TestMove val finalRunner = runMoves(testGame.title, initialRunner, preparatoryMoves) - when (val result = finalRunner.executeMove(lastMove.first, lastMove.second)) { + when (val result = finalRunner.executeMove(lastMove.from, lastMove.to)) { is TestMoveSuccess -> fail("${testGame.title} failed, last move should result in game end but did not") is FinalTestMoveResult -> { if (!checkResult(result)) { @@ -116,21 +121,25 @@ class GameTester(private val runner: TestGameRunner) { private fun failedMoveMessage( title: String, - from: TestPosition, - to: TestPosition, + input: TestInput, result: FinalTestMoveResult ): String { - // take 1-based int and return a string with the char. a for 1, b for 2, etc. - val fromFile = ('a'.code + from.col -1 ).toChar() - val fromRank = from.row + return when (input) { + is Undo -> "$title failed, undo should not result in game end" + is Redo -> "$title failed, redo should not result in game end" + is TestMove -> { + val fromFile = ('a'.code + input.from.col - 1).toChar() + val fromRank = input.from.row - val toFile = ('a'.code + to.col -1 ).toChar() - val toRank = to.row + val toFile = ('a'.code + input.to.col - 1).toChar() + val toRank = input.to.row - val pieceType = result.finalBoard.pieces[from].toString() + val pieceType = result.finalBoard.pieces[input.from].toString() - return "$title failed, moving $pieceType from $fromFile$fromRank to $toFile$toRank" + "$title failed, moving $pieceType from $fromFile$fromRank to $toFile$toRank" + } + } } diff --git a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/TestGameRunner.kt b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/TestGameRunner.kt index 0ba581f..ea87a55 100644 --- a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/TestGameRunner.kt +++ b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/game/TestGameRunner.kt @@ -20,5 +20,13 @@ interface TestGameRunner { fun executeMove(from: TestPosition, to: TestPosition): TestMoveResult + fun undo(): TestMoveResult { + TODO("not implemented") + } + + fun redo(): TestMoveResult { + TODO("not implemented") + } + fun getBoard(): TestBoard } \ No newline at end of file diff --git a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/gameParser/GameBoardParser.kt b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/gameParser/GameBoardParser.kt index ae6234e..4c259ba 100644 --- a/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/gameParser/GameBoardParser.kt +++ b/test-framework/src/main/kotlin/edu/austral/dissis/chess/test/gameParser/GameBoardParser.kt @@ -1,6 +1,10 @@ package edu.austral.dissis.chess.test.gameParser import edu.austral.dissis.chess.test.* +import edu.austral.dissis.chess.test.game.TestInput +import edu.austral.dissis.chess.test.game.TestMove +import edu.austral.dissis.chess.test.game.Redo +import edu.austral.dissis.chess.test.game.Undo import edu.austral.dissis.chess.test.parserUtils.MatrixParser class GameBoardParser { @@ -14,14 +18,14 @@ class GameBoardParser { val title = parseTitle(lines) val size = parseSize(lines) val board = parseStartingBoard(lines, size) - val movements = parseMoves(lines) + val inputs = parseInputs(lines) val result = parseResult(lines) val finalBoard = parseFinalBoard(lines, size) return TestGame( title, board, - movements, + inputs, result, finalBoard ) @@ -102,14 +106,16 @@ class GameBoardParser { return createBoard(size, pieces) } - private fun parseMoves(lines: List): List> { + private fun parseInputs(lines: List): List { /* * Format is: # Moves - 1. e2 e4 - 2. e7 e5 - 3. g1 f3 + 1. e2-e4 + 2. e7-e5 + 3. UNDO + 4. REDO + 5. g1-f3 * */ val headerLine = lines.indexOfFirst { it.startsWith("# Moves") } @@ -119,8 +125,16 @@ class GameBoardParser { val moves = lines.subList(headerLine + 1, headerLine + nextEmptyLine) return moves .map { it.split(" ")[1] } - .map { it.split("-") } - .map { Pair(TestPosition.fromAlgebraic(it[0]), TestPosition.fromAlgebraic(it[1])) } + .map { + when (it) { + "UNDO" -> Undo + "REDO" -> Redo + else -> { + val parts = it.split("-") + TestMove(TestPosition.fromAlgebraic(parts[0]), TestPosition.fromAlgebraic(parts[1])) + } + } + } } private fun parseResult(lines: List): TestGameResult { diff --git a/test-framework/src/main/resources/test_cases/four_redos.md b/test-framework/src/main/resources/test_cases/four_redos.md new file mode 100644 index 0000000..6a3c21a --- /dev/null +++ b/test-framework/src/main/resources/test_cases/four_redos.md @@ -0,0 +1,47 @@ +# 4 redos + +# Size +width = 8 +height = 8 + +# Starting board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | |BP| | | | +8 | | | | |BK| | | | +``` +# Moves +1. e2-e3 +2. e7-e6 +3. e3-e4 +4. e6-e5 +5. UNDO +6. UNDO +7. UNDO +8. UNDO +9. REDO +10. REDO +11. REDO +12. REDO + +# Result +`ALL_MOVES_VALID` + +# Final board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | | | | | | +3 | | | | | | | | | +4 | | | | |WP| | | | +5 | | | | |BP| | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` diff --git a/test-framework/src/main/resources/test_cases/redo_pawn_movement.md b/test-framework/src/main/resources/test_cases/redo_pawn_movement.md new file mode 100644 index 0000000..df44833 --- /dev/null +++ b/test-framework/src/main/resources/test_cases/redo_pawn_movement.md @@ -0,0 +1,39 @@ +# Redo Pawn Movement + +# Size +width = 8 +height = 8 + +# Starting board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` +# Moves +1. e2-e4 +2. UNDO +3. REDO + + +# Result +`ALL_MOVES_VALID` + +# Final board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | | | | | | +3 | | | | | | | | | +4 | | | | |WP| | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` diff --git a/test-framework/src/main/resources/test_cases/undo_pawn_movement.md b/test-framework/src/main/resources/test_cases/undo_pawn_movement.md new file mode 100644 index 0000000..28e6dae --- /dev/null +++ b/test-framework/src/main/resources/test_cases/undo_pawn_movement.md @@ -0,0 +1,38 @@ +# Undo Pawn Movement + +# Size +width = 8 +height = 8 + +# Starting board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` +# Moves +1. e2-e4 +2. UNDO + + +# Result +`ALL_MOVES_VALID` + +# Final board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` diff --git a/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_black_turn.md b/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_black_turn.md new file mode 100644 index 0000000..84a7f46 --- /dev/null +++ b/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_black_turn.md @@ -0,0 +1,40 @@ +# Undo pawn movement still white turn + +# Size +width = 8 +height = 8 + +# Starting board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | |BP| | | | +8 | | | | |BK| | | | +``` +# Moves +1. e2-e3 +2. e7-e6 +3. UNDO +4. e7-e5 + + +# Result +`ALL_MOVES_VALID` + +# Final board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | | | | | | +3 | | | | |WP| | | | +4 | | | | | | | | | +5 | | | | |BP| | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` diff --git a/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_white_turn.md b/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_white_turn.md new file mode 100644 index 0000000..f61cd10 --- /dev/null +++ b/test-framework/src/main/resources/test_cases/undo_pawn_movement_still_white_turn.md @@ -0,0 +1,39 @@ +# Undo pawn movement still white turn + +# Size +width = 8 +height = 8 + +# Starting board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | |WP| | | | +3 | | | | | | | | | +4 | | | | | | | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` +# Moves +1. e2-e3 +2. UNDO +3. e2-e4 + + +# Result +`ALL_MOVES_VALID` + +# Final board +``` + a b c d e f g h +1 | | | | |WK| | | | +2 | | | | | | | | | +3 | | | | | | | | | +4 | | | | |WP| | | | +5 | | | | | | | | | +6 | | | | | | | | | +7 | | | | | | | | | +8 | | | | |BK| | | | +``` diff --git a/test-framework/src/test/kotlin/RunnersHistory.kt b/test-framework/src/test/kotlin/RunnersHistory.kt new file mode 100644 index 0000000..8277331 --- /dev/null +++ b/test-framework/src/test/kotlin/RunnersHistory.kt @@ -0,0 +1,30 @@ +import edu.austral.dissis.chess.test.game.TestGameRunner + +data class RunnersHistory( + val history: List = emptyList(), + val undone: List = emptyList() +) { + + fun add(runner: TestGameRunnerImpl): RunnersHistory { + return RunnersHistory(history + runner, emptyList()) + } + + fun undo(current: TestGameRunnerImpl): TestGameRunner { + return if (history.isNotEmpty()) { + val last = history.last() + last.withHistory(RunnersHistory(history.dropLast(1), undone + current)) + } else { + throw IllegalStateException("No moves to undo") + } + } + + fun redo(): TestGameRunner { + if (undone.isNotEmpty()) { + val lastUndone = undone.last() + val newUndone = undone.dropLast(1) + return lastUndone.withHistory(RunnersHistory(history + lastUndone, newUndone)) + } else { + throw IllegalStateException("No moves to undo") + } + } +} \ No newline at end of file diff --git a/test-framework/src/test/kotlin/TestGameRunnerImpl.kt b/test-framework/src/test/kotlin/TestGameRunnerImpl.kt index 7bd5e71..f6d7c2b 100644 --- a/test-framework/src/test/kotlin/TestGameRunnerImpl.kt +++ b/test-framework/src/test/kotlin/TestGameRunnerImpl.kt @@ -9,6 +9,7 @@ class TestGameRunnerImpl : TestGameRunner { private val adapter = ModelAdapter() private var gameBoard = Board() + private var history = RunnersHistory() constructor() { val newBoard = Board() @@ -16,11 +17,16 @@ class TestGameRunnerImpl : TestGameRunner { this.gameBoard = newBoard } - private constructor(gameBoard: Board) : this() { + private constructor(gameBoard: Board, history: RunnersHistory) : this() { this.gameBoard = gameBoard + this.history = history } - override fun withBoard(board: TestBoard): TestGameRunner { + fun withHistory(history: RunnersHistory): TestGameRunnerImpl { + return TestGameRunnerImpl(gameBoard, history) + } + + override fun withBoard(board: TestBoard): edu.austral.dissis.chess.test.game.TestGameRunner { val newBoard = Board() newBoard.clear() board @@ -32,7 +38,7 @@ class TestGameRunnerImpl : TestGameRunner { adapter.testPositionToSquare(position) ) } - return TestGameRunnerImpl(newBoard) + return TestGameRunnerImpl(newBoard, RunnersHistory()) } override fun executeMove(from: TestPosition, to: TestPosition): TestMoveResult { @@ -47,12 +53,20 @@ class TestGameRunnerImpl : TestGameRunner { WhiteCheckMate(adapter.boardToTestBoard(newBoard)) } } - TestMoveSuccess(TestGameRunnerImpl(newBoard)) + TestMoveSuccess(TestGameRunnerImpl(newBoard, history.add(this))) } else { TestMoveFailure(adapter.boardToTestBoard(newBoard)) } } + override fun undo(): TestMoveResult { + return TestMoveSuccess(history.undo(this)) + } + + override fun redo(): TestMoveResult { + return TestMoveSuccess(history.redo()) + } + override fun getBoard(): TestBoard { return adapter.boardToTestBoard(gameBoard) }