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