Created
October 2, 2013 17:08
-
-
Save lamdor/6796988 to your computer and use it in GitHub Desktop.
tdd-with-quickcheck (scala version) from http://primitive-automaton.logdown.com/posts/142511/tdd-with-quickcheck
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import org.scalacheck._ | |
| object TicTackToePrintArbGame extends App { | |
| import TicTacToeArbitraries._ | |
| import Arbitrary._ | |
| import TicTacToe._ | |
| val Some(Game9(game)) = arbitrary[Game9].apply(Gen.Params()) | |
| def show(game: Game): String = { // really ugly way | |
| val arr = Array.ofDim[String](3,3) | |
| def updateColumnOfRow(row: Array[String], col: Fin3, player: Player) = col match { | |
| case I1 => row(0) = player.toString | |
| case I2 => row(1) = player.toString | |
| case I3 => row(2) = player.toString | |
| } | |
| game.moves.foreach { case Move((row, col), player) => | |
| row match { | |
| case I1 => updateColumnOfRow(arr(0), col, player) | |
| case I2 => updateColumnOfRow(arr(1), col, player) | |
| case I3 => updateColumnOfRow(arr(2), col, player) | |
| } | |
| } | |
| arr.map(_.mkString).mkString("\n") | |
| } | |
| println(show(game)) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| object TicTacToe { | |
| sealed trait Player | |
| case object X extends Player | |
| case object O extends Player | |
| sealed trait Fin3 | |
| case object I1 extends Fin3 | |
| case object I2 extends Fin3 | |
| case object I3 extends Fin3 | |
| type Position = (Fin3, Fin3) | |
| case class Move(position: Position, player: Player) | |
| case class Game(moves: Seq[Move]) { | |
| lazy val takenPositions = moves.map(_.position) | |
| } | |
| @annotation.tailrec | |
| def isValidGame(game: Game): Boolean = game match { | |
| case Game(head :: moves) => | |
| val previousGame = Game(moves) | |
| isValidMove(previousGame, head.position) && | |
| previousMoveIsNotSamePlayer(head.player, moves.headOption) && | |
| isValidGame(previousGame) | |
| case Game(Nil) => true | |
| } | |
| def isValidMove(game: Game, position: Position): Boolean = | |
| game.moves.size != 9 && !game.takenPositions.contains(position) | |
| def previousMoveIsNotSamePlayer(player: Player, previousMove: Option[Move]) = | |
| previousMove.map(_.player != player) getOrElse true | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import org.scalacheck._ | |
| import org.scalacheck.Prop.forAll | |
| object TicTacToeSpecification extends Properties("TicTacToe") { | |
| import TicTacToe._ | |
| import TicTacToeArbitraries._ | |
| property("game of 9 moves, has 9 moves") = | |
| forAll { game9: Game9 => | |
| game9.game.moves.size == 9 | |
| } | |
| property("game of 9 moves already, has no valid moves") = | |
| forAll { (g9: Game9, p: Position) => | |
| !isValidMove(g9.game, p) | |
| } | |
| property("for an empty game, every position is valid") = | |
| forAll { p: Position => | |
| isValidMove(Game(Nil), p) | |
| } | |
| property("for an empty game, every move added makes a valid game") = | |
| forAll { m: Move => | |
| isValidGame(Game(m :: Nil)) | |
| } | |
| property("for different moves, every move added still is a valid game") = | |
| forAll { dm: DifferingMoves => | |
| isValidGame(Game(dm.m1 :: dm.m2 :: Nil)) | |
| } | |
| property("the same move twice, is not a valid game") = | |
| forAll { m: Move => | |
| !isValidGame(Game(m :: m :: Nil)) | |
| } | |
| property("a game of 9 valid moves") = | |
| forAll((game: Game9) => isValidGame(game.game)) | |
| property("empty game is a valid game") = isValidGame(Game(Nil)) | |
| property("a move by the same player twice, is not a valid game") = | |
| forAll { dmbsp: DifferingMovesBySamePlayer => | |
| !isValidGame(Game(dmbsp.m1 :: dmbsp.m2 :: Nil)) | |
| } | |
| } | |
| object TicTacToeArbitraries { | |
| import TicTacToe._ | |
| import Arbitrary._ | |
| implicit val arbPlayer: Arbitrary[Player] = Arbitrary { | |
| Gen.oneOf(Seq(X,O)) | |
| } | |
| implicit val fin3Arb: Arbitrary[Fin3] = Arbitrary { | |
| Gen.oneOf(Seq(I1,I2,I3)) | |
| } | |
| implicit val arbMove: Arbitrary[Move] = Arbitrary { | |
| arbitrary[Position].map2(arbitrary[Player])(Move.apply) | |
| } | |
| implicit val arbGame: Arbitrary[Game] = Arbitrary { | |
| for { | |
| moves <- arbitrary[List[Move]] | |
| game = Game(moves) | |
| if isValidGame(game) | |
| } yield game | |
| } | |
| val allPositions: Set[Position] = | |
| Seq(I1,I1,I2,I2,I3,I3).permutations.foldLeft(Set.empty[Position]) { (s, pos) => | |
| s ++ pos.combinations(2).map { | |
| case p1 :: p2 :: Nil => (p1, p2) | |
| } | |
| } | |
| def genNValidMoves(n: Int): Gen[List[Move]] = { | |
| def go(left: Int, player: Player, | |
| availablePositions: Set[Position] = allPositions): Gen[List[Move]] = { | |
| if (left > 0) { | |
| for { | |
| nextPosition <- Gen.oneOf(availablePositions.toSeq) | |
| nextMove = Move(nextPosition, player) | |
| nextPlayer = if (player == X) O else X | |
| nextAvailablePositions = availablePositions - nextPosition | |
| nextMoves <- go(left - 1, nextPlayer, nextAvailablePositions) | |
| } yield (nextMoves :+ nextMove) | |
| } else Gen.value(Nil) | |
| } | |
| arbitrary[Player].flatMap(player => go(n, player)) | |
| } | |
| case class Game9(game: Game) | |
| implicit val arbGame9: Arbitrary[Game9] = Arbitrary { | |
| for { | |
| moves <- genNValidMoves(9) | |
| game = Game(moves) | |
| } yield Game9(game) | |
| } | |
| case class DifferingMoves(m1: Move, m2: Move) | |
| implicit val arbDifferingMoves: Arbitrary[DifferingMoves] = Arbitrary { | |
| for { | |
| m1 <- arbitrary[Move] | |
| m2 <- arbitrary[Move] | |
| if m1.position != m2.position | |
| if m1.player != m2.player | |
| } yield DifferingMoves(m1, m2) | |
| } | |
| case class DifferingMovesBySamePlayer(m1: Move, m2: Move) | |
| implicit val arbDifferingMovesBySamePlayer: Arbitrary[DifferingMovesBySamePlayer] = Arbitrary { | |
| for { | |
| p <- arbitrary[Player] | |
| p1 <- arbitrary[Position] | |
| p2 <- arbitrary[Position] | |
| if p1 != p2 | |
| } yield DifferingMovesBySamePlayer(Move(p1, p), Move(p2, p)) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment