Testing the Ludo corner cases
TL;DR
We add examples to test all the special cases of the Ludo game play.
For each case, we have a before and an m after example.
Testing corner cases
Basic play seems to work, but we haven't tested all the possible odd cases.
For each we will have two examples: one that leads to the setup, and another that tests the special case.
1. Entering play when there is a token of another player on the start square. (Send other token to start state.)
Before: token A is on B's start square:
bToPlayWithAonStartSquare "Setup for 1. Entering play when there is a token of another player on the start square. (Send other token to start state.)" <gtExample> | game | game := self playerAentersTokenA. game roll: 6. game moveTokenNamed: 'A'. game roll: 4. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 11. self assert: game currentPlayer name equals: 'B'. ^ game
After: B enters play, sending A back to the start square.
bEntersPlayWithAonStartSquare "Test for 1. Entering play when there is a token of another player on the start square. (Send other token to start state.)" <gtExample> | game | game := self bToPlayWithAonStartSquare. self assert: (game tokenNamed: 'A') isInPlay. game roll: 6. game moveTokenNamed: 'B'. self assert: (game positionOfTokenNamed: 'B') equals: 11. self assert: game currentPlayer name equals: 'B'. self assert: (game tokenNamed: 'A') isInStartState. ^ game
2. Entering play when there is a token of the same player on the start square (ie after rolling a 6 twice).
Before: token A is on A's start square.
playerAentersTokenA "Setup for 2. Entering play when there is a token of the same player on the start square (ie after rolling a 6 twice)." <gtExample> | game | game := self playerArolls6. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 1. self assert: game currentPlayer name equals: 'A'. self assert: game playerToRoll. self assert: game playerToMove not. self assert: (game tokensToMove collect: #name) asSet equals: Set new. ^ game
After: A enters token "a", landing on token A, and ending up on the next free square.
playerAentersAndLandsOnTokenA "Example for 2. Entering play when there is a token of the same player on the start square (ie after rolling a 6 twice)." <gtExample> | game | game := self playerAentersTokenA. self assert: (game positionOfTokenNamed: 'A') equals: 1. game roll: 6. game moveTokenNamed: 'a'. self assert: (game positionOfTokenNamed: 'A') equals: 1. self assert: (game positionOfTokenNamed: 'a') equals: 2. self assert: game currentPlayer name equals: 'A'. ^ game
3. Landing on a square with the same player's token. (Land on next square)
Before: the outcome from above, with tokens A and a in positions 1 and 2.
After: token A moves 6 and token a moves 5, landing on A, and ending up on the next free square.
playerAmovesTwiceAndLandsOnA "Example for: 3. Landing on a square with the same player's token. (Land on next square.)" <gtExample> | game | game := self playerAentersAndLandsOnTokenA. self assert: game currentPlayer name equals: 'A'. game roll: 6. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'a') equals: 2. self assert: (game positionOfTokenNamed: 'A') equals: 7. game roll: 5. game moveTokenNamed: 'a'. self assert: (game positionOfTokenNamed: 'a') equals: 8. self assert: game currentPlayer name equals: 'B'. ^ game
4. Landing on another player's token. (Send other token to start)
Before: token A is in position 12
playerBentersWithTokenAahead "Setup for 4. Landing on another player's token. (Send other token to start.)" <gtExample> | game | game := self playerAentersAndLandsOnTokenA. game roll: 6. game moveTokenNamed: 'A'. game roll: 5. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 12. self assert: game currentPlayer name equals: 'B'. ^ game
After: B lands on position 12, sending A back to the start
bEntersAndPlaysWithAahead "Example for 4. Landing on another player's token. (Send other token to start.)" <gtExample> | game | game := self playerBentersWithTokenAahead. game roll: 6. game moveTokenNamed: 'B'. game roll: 1. game moveTokenNamed: 'B'. self assert: (game positionOfTokenNamed: 'B') equals: 12. self assert: game currentPlayer name equals: 'C'. self assert: (game tokenNamed: 'A') isInStartState. ^ game
5. Landing on a square with the same play's token, followed by a square with a token of another player. (Land on next square, sending the token there to start)
Before: token B is in position 17 and token A in position 18.
playerBtoPlayWithTokensBandAahead "Setup for 5. Landing on a square with the same play's token, followed by a square with a token of another player. (Land on next square, sending the token there to start.)" <gtExample> | game | game := self playerAentersTokenA. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 5. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 18. self assert: game currentPlayer name equals: 'B'. game roll: 6. game moveTokenNamed: 'B'. game roll: 6. game moveTokenNamed: 'b'. game roll: 6. game moveTokenNamed: 'B'. self assert: (game positionOfTokenNamed: 'B') equals: 17. ^ game
After: token b (position 12) lands on position 17, and ends up on square 18, sending A back to the start
playerBplaysWithTokensBandAahead "Example for 5. Landing on a square with the same play's token, followed by a square with a token of another player. (Land on next square, sending the token there to start.)" <gtExample> | game | game := self playerBtoPlayWithTokensBandAahead. self assert: game currentPlayer name equals: 'B'. self assert: (game positionOfTokenNamed: 'b') equals: 12. self assert: (game positionOfTokenNamed: 'B') equals: 17. self assert: (game positionOfTokenNamed: 'A') equals: 18. game roll: 5. game moveTokenNamed: 'b'. self assert: (game positionOfTokenNamed: 'B') equals: 17. self assert: (game positionOfTokenNamed: 'b') equals: 18. self assert: (game tokenNamed: 'A') isInStartState. ^ game
6. Landing on the first goal square, unoccupied.
Before: token a is in position 38, close to the end of the route.
playerAarrivesCloseToGoal "Setup for 6. Landing on the first goal square, unoccupied and 7. Landing on the unoccupied second goal square." <gtExample> | game | game := self playerAentersAndLandsOnTokenA. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 1. self assert: (game positionOfTokenNamed: 'a') equals: 2. game roll: 6. game moveTokenNamed: 'a'. game roll: 6. game moveTokenNamed: 'a'. game roll: 6. game moveTokenNamed: 'a'. game roll: 6. game moveTokenNamed: 'a'. game roll: 6. game moveTokenNamed: 'a'. game roll: 6. game moveTokenNamed: 'a'. self assert: (game positionOfTokenNamed: 'a') equals: 38. ^ game
After: a rolls a 3, landing on position 41, the first goal state.
playerAlandsOnFirstGoalSquare "Example for 6. Landing on the first goal square, unoccupied." <gtExample> | game | game := self playerAarrivesCloseToGoal. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'a') equals: 38. game roll: 3. game moveTokenNamed: 'a'. self assert: (game tokenNamed: 'a') isInGoalState. ^ game
7. Landing on the unoccupied second goal square.
Before: as above, token a is in position 38
After: a rolls a 4, landing on the second goal square
playerAlandsOnSecondGoalSquare "Example for 7. Landing on the unoccupied second goal square." <gtExample> | game | game := self playerAarrivesCloseToGoal. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'a') equals: 38. game roll: 4. game moveTokenNamed: 'a'. self assert: (game tokenNamed: 'a') isInGoalState. ^ game
8. Overshooting the goal sqares. (Fail to move)
Before: as above, token a is in position 38
After: a rolls a 5, overshooting the goal squares, and remaining in position 38 (an impossible move)
playerAovershootsGoal "Example for 8. Overshooting the goal sqares. (Fail to move.)" <gtExample> | game | game := self playerAarrivesCloseToGoal. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'a') equals: 38. game roll: 5. game moveTokenNamed: 'a'. self assert: (game tokenNamed: 'a') isInPlay. self assert: (game positionOfTokenNamed: 'a') equals: 38. ^ game
9. Landing on the first occupied goal square. (Land on next goal square)
Before: token a is on square 37 and A is on the first goal square
playerAsecondTokenCloseToGoal "Setup for 9. Landing on the first occupied goal square. (Land on next goal square.)" <gtExample> | game | game := self playerAlandsOnFirstGoalSquare. self assert: game currentPlayer name equals: 'B'. game roll: 1. game roll: 1. game roll: 1. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 1. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 37. ^ game
After: A rolls a 4, landing on token a, and ending up on the second goal square
playerAlandsOnOccupiedGoalSquare "Example for 9. Landing on the first occupied goal square. (Land on next goal square.)" <gtExample> | game | game := self playerAsecondTokenCloseToGoal. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 37. game roll: 4. game moveTokenNamed: 'A'. self assert: (game tokenNamed: 'A') isInGoalState. ^ game
10. Landing on the occupied second goal square. (Fail to move)
Before: token a is on the second goal square and A is on square 37
playerAsecondTokenCloseToSecondOccupiedGoal "Setup for 10. Landing on the occupied second goal square. (Fail to move.)" <gtExample> | game | game := self playerAlandsOnSecondGoalSquare. self assert: game currentPlayer name equals: 'B'. game roll: 1. game roll: 1. game roll: 1. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 1. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. game roll: 6. game moveTokenNamed: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 37. ^ game
After: A rolls a 5 and lands on token a, ending up back where it started (impossible move)
playerAlandsOnOccupiedSecondGoalSquare "Example for 10. Landing on the occupied second goal square. (Fail to move.)" <gtExample> | game | game := self playerAsecondTokenCloseToSecondOccupiedGoal. self assert: game currentPlayer name equals: 'A'. self assert: (game positionOfTokenNamed: 'A') equals: 37. game roll: 5. game moveTokenNamed: 'A'. self assert: (game tokenNamed: 'A') isInPlay. self assert: (game positionOfTokenNamed: 'A') equals: 37. ^ game
We can run all the examples and test them:
examples := GtExplicitExampleGroup withAll: GtLudoGame package gtExamplesAllContained. examples runAll