Skip to content

Commit 5912ac6

Browse files
authored
Merge pull request #14 from Daenyth/cats
Convert scalaz to cats
2 parents aa6c64e + e854ef8 commit 5912ac6

File tree

15 files changed

+178
-154
lines changed

15 files changed

+178
-154
lines changed

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ A scala library for the [Tak](http://cheapass.com/tak/) board game
77

88
```scala
99
import com.github.daenyth.taklib._
10-
import scalaz.\/
1110
```
1211

1312
```scala
14-
val invalid: String \/ Game = Game.fromTps("invalid")
15-
// invalid: scalaz.\/[String,com.github.daenyth.taklib.Game] = -\/(`[' expected but `i' found)
13+
val invalid: Either[String, Game] = Game.fromTps("invalid")
14+
// invalid: Either[String,com.github.daenyth.taklib.Game] = Left(`[' expected but `i' found)
1615
val game = Game.fromTps("[ 1,2,1,2,1/2,1,2,1,2/1,2,1,2,1/2,1,2,1,2/1,2,1,2,1 12 2 ]").getOrElse(throw new Exception)
1716
// game: com.github.daenyth.taklib.Game = com.github.daenyth.taklib.Game@78c4cfdd
1817
val winner = game.winner
@@ -90,15 +89,15 @@ Create a RuleSet to make new games with
9089

9190
```
9291
scala> Game.ofSize(7, DefaultRules)
93-
res0: scalaz.\/[String,com.github.daenyth.taklib.Game] = -\/(Bad game size: 7)
92+
res0: Either[String,com.github.daenyth.taklib.Game] = scala.Left(Bad game size: 7)
9493
9594
// A new variant with a size-7 board that has 40 flatstones and 7 capstones per player!
9695
scala> Game.ofSize(7, new RuleSet {
9796
| val rules = DefaultRules.rules
9897
| val expectedStoneColor = DefaultRules.expectedStoneColor
9998
| val stoneCounts = DefaultRules.stoneCounts + ((7, (40, 7)))
10099
| })
101-
res1: scalaz.\/[String,com.github.daenyth.taklib.Game] = \/-(com.github.daenyth.taklib.Game@517564bf)
100+
res1: Either[String,com.github.daenyth.taklib.Game] = scala.Right(com.github.daenyth.taklib.Game@517564bf)
102101
```
103102

104103
## Goals
@@ -111,7 +110,7 @@ res1: scalaz.\/[String,com.github.daenyth.taklib.Game] = \/-(com.github.daenyth.
111110
- Not aiming to be the fastest runtime - I'm not benchmarking anything until the project is much more stable.
112111
- Stable API - for now. This is a new library, and so the api can change without notice as I find better ways to do things.
113112
- Supporting scala.js - for now. It should be possible with little effort but it's not a priority. Patches welcome
114-
- Cats support. Taklib will use scalaz only for the near future
113+
- Scalaz usage. Taklib will only support cats
115114

116115
## Testing
117116

build.sbt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,23 @@ scalacOptions in (opentak, Compile, console) ~= (_.filterNot(Set("-Xfatal-warnin
4141

4242
resolvers += Resolver.sonatypeRepo("releases")
4343

44-
val scalazVersion = "7.2.8"
44+
val catsVersion = "0.9.0"
4545
val parserCombinators = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.5"
4646
val dependencies = Seq(
47-
"org.scalaz" %% "scalaz-core" % scalazVersion,
47+
"org.typelevel" %% "cats" % catsVersion,
4848
parserCombinators,
4949
"org.scala-graph" %% "graph-core" % "1.11.4"
5050
)
5151
val testDependencies = Seq(
5252
"org.scalatest" %% "scalatest" % "3.0.1" % "test",
5353
"org.scalacheck" %% "scalacheck" % "1.13.4" % "test",
54-
"org.typelevel" %% "scalaz-scalatest" % "1.1.1" % "test",
55-
"org.scalaz" %% "scalaz-scalacheck-binding" % scalazVersion % "test"
54+
"com.ironcorelabs" %% "cats-scalatest" % "2.2.0" % "test"
5655
)
5756

5857
libraryDependencies in taklib ++= dependencies
5958
libraryDependencies in taklib ++= testDependencies
6059
libraryDependencies in takcli ++= Seq(
61-
"org.scalaz" %% "scalaz-concurrent" % scalazVersion
60+
"org.typelevel" %% "cats-effect" % "0.2"
6261
)
6362

6463
resolvers in tpsserver += Resolver.sonatypeRepo("snapshots")

opentak/src/main/scala/com/github/daenyth/opentak/protocol/PlaytakCodec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ object PlaytakCodec {
1616
def encode(outgoing: Playtak.Outgoing): String =
1717
Outgoing.encode(outgoing)
1818
def decode(input: String): Either[String, Playtak.Incoming] =
19-
Incoming.parseEither(Incoming.incoming, input).toEither
19+
Incoming.parseEither(Incoming.incoming, input)
2020

2121
/* Abandon hope all ye who scroll below here */
2222

2323
object Incoming extends RegexParsers with RichParsing {
2424
def decode(input: String): Either[String, Playtak.Incoming] =
25-
parseEither(incoming, input).toEither
25+
parseEither(incoming, input)
2626

2727
import Playtak.Incoming._
2828

opentak/src/test/scala/com/github/daenyth/opentak/protocol/PlaytakCodecTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class PlaytakCodecTest extends FlatSpec with Matchers with EitherValues {
99
import Playtak.Incoming._
1010

1111
def parse[A](parser: PlaytakCodec.Incoming.Parser[A], s: String): Either[String, A] =
12-
PlaytakCodec.Incoming.parseEither(parser, s).toEither
12+
PlaytakCodec.Incoming.parseEither(parser, s)
1313

1414
"client" should "parse" in {
1515
parse(client, "Client Foop") shouldBe Right(Client("Foop"))

takcli/src/main/scala/com/github/daenyth/takcli/Main.scala

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,49 @@
11
package com.github.daenyth.takcli
22

3+
import cats.effect.IO
34
import com.github.daenyth.taklib._
45

56
import scala.io.StdIn
67
import scala.util.control.NoStackTrace
7-
import scalaz.concurrent.Task
8-
import scalaz.syntax.monad._
9-
import scalaz.{-\/, \/-}
8+
import cats.syntax.flatMap._
9+
import cats.syntax.applicativeError._
1010

1111
object Main {
12+
1213
def main(args: Array[String]): Unit =
13-
mainT.handleWith {
14-
case CleanExit => Task.now(println("Exiting"))
15-
}.unsafePerformSync
14+
mainT
15+
.recoverWith {
16+
case CleanExit => IO(println("Exiting"))
17+
}
18+
.unsafeRunSync()
1619

17-
def mainT: Task[Unit] = printStartup *> getInitialGame >>= runGameLoop
20+
def mainT: IO[Unit] = printStartup >> getInitialGame >>= runGameLoop
1821

19-
def getInitialGame: Task[Game] = promptSize.flatMap {
22+
def getInitialGame: IO[Game] = promptSize.flatMap {
2023
Game.ofSize(_) match {
21-
case -\/(err) => Task.now(println(err)) *> getInitialGame
22-
case \/-(game) => Task.now(game)
24+
case scala.Left(err) => IO(println(err)) >> getInitialGame
25+
case scala.Right(game) => IO(game)
2326
}
2427
}
2528

26-
def runGameLoop(g: Game): Task[Unit] = runGameTurn(g).flatMap {
29+
def runGameLoop(g: Game): IO[Unit] = runGameTurn(g).flatMap {
2730
case OkMove(nextState) =>
2831
runGameLoop(nextState)
2932
case InvalidMove(reason) =>
30-
Task.now(println(s"Bad move: $reason")) *> runGameLoop(g)
33+
IO(println(s"Bad move: $reason")) >> runGameLoop(g)
3134
case GameOver(result, finalState) =>
32-
printGame(finalState) *> endGame(result)
35+
printGame(finalState) >> endGame(result)
3336
}
3437

35-
def endGame(end: GameEndResult): Task[Unit] = Task {
38+
def endGame(end: GameEndResult): IO[Unit] = IO {
3639
println("Game over!")
3740
println(end)
3841
}
3942

40-
def runGameTurn(g: Game): Task[MoveResult[Game]] =
41-
printGame(g) *> promptAction.map(g.takeTurn)
43+
def runGameTurn(g: Game): IO[MoveResult[Game]] =
44+
printGame(g) >> promptAction.map(g.takeTurn)
4245

43-
def printGame(g: Game) = Task {
46+
def printGame(g: Game) = IO {
4447
val nextPlayInfo = g.turnNumber match {
4548
case 1 => "White to play (Black stone)"
4649
case 2 => "Black to play (White stone)"
@@ -52,35 +55,36 @@ object Main {
5255
println()
5356
}
5457

55-
def printStartup = Task {
56-
println("TakCLI")
57-
}
58+
def printStartup = IO(println("TakCLI"))
5859

59-
def promptSize: Task[Int] =
60-
Task {
60+
def promptSize: IO[Int] =
61+
IO {
6162
print("Game size?\n > ")
6263
StdIn.readInt()
63-
}.handleWith {
64-
case n: NumberFormatException => Task(println(s"Bad size: $n")) *> promptSize
64+
}.recoverWith {
65+
case n: NumberFormatException => IO(println(s"Bad size: $n")) >> promptSize
6566
}
6667

67-
def pretty(g: Game): String = {
68-
g.currentBoard.boardPositions.map(_.reverse).transpose.map(_.map(_.toTps).mkString("\t")).mkString("\n")
69-
}
68+
def pretty(g: Game): String =
69+
g.currentBoard.boardPositions
70+
.map(_.reverse)
71+
.transpose
72+
.map(_.map(_.toTps).mkString("\t"))
73+
.mkString("\n")
7074

71-
def promptAction: Task[TurnAction] =
72-
Task(StdIn.readLine("Your Move?\n > "))
75+
def promptAction: IO[TurnAction] =
76+
IO(StdIn.readLine("Your Move?\n > "))
7377
.flatMap { input =>
7478
if (input == null) { throw CleanExit } else
7579
PtnParser
7680
.parseEither(PtnParser.turnAction, input)
7781
.fold(
78-
err => Task.fail(PtnParseError(err)),
79-
Task.now
82+
err => IO.raiseError(PtnParseError(err)),
83+
ta => IO.pure(ta)
8084
)
8185
}
82-
.handleWith {
83-
case PtnParseError(err) => Task(println(s"Bad move: $err")) *> promptAction
86+
.recoverWith {
87+
case PtnParseError(err) => IO(println(s"Bad move: $err")) >> promptAction
8488
}
8589
}
8690

taklib/src/main/scala/com/github/daenyth/taklib/Board.scala

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ import com.github.daenyth.taklib.Stone._
66

77
import scala.annotation.tailrec
88
import scala.collection.immutable.IndexedSeq
9-
import scalaz.std.vector._
10-
import scalaz.syntax.either._
11-
import scalaz.syntax.monoid._
12-
import scalaz.{Equal, \/}
9+
import cats.syntax.either._
10+
import cats.syntax.monoid._
11+
import cats.instances.vector._
12+
import cats.{Eq => Equal}
13+
14+
import scala.util.Try
1315

1416
object Board {
17+
type \/[A, B] = Either[A, B]
1518

1619
type BoardLayout = Vector[Vector[Stack]]
1720

18-
/** Build a board from Tak Positional System; -\/ if tps is invalid */
19-
def fromTps(tps: String): String \/ Board = TpsParser.parse(TpsParser.tps, tps) match {
20-
case TpsParser.Success((board, _, _), _) => board.right
21-
case err: TpsParser.NoSuccess => err.msg.left
21+
/** Build a board from Tak Positional System; scala.Left if tps is invalid */
22+
def fromTps(tps: String): Either[String, Board] = TpsParser.parse(TpsParser.tps, tps) match {
23+
case TpsParser.Success((board, _, _), _) => board.asRight
24+
case err: TpsParser.NoSuccess => err.msg.asLeft
2225
}
2326

2427
def ofSize(size: Int): Board =
@@ -34,7 +37,7 @@ object Board {
3437
index: BoardIndex,
3538
stack: Stack): MoveResult[BoardLayout] = {
3639
val (i, j) = (index.file - 1, index.rank - 1)
37-
val stackAtIdx: MoveResult[Stack] = \/.fromTryCatchNonFatal(positions(i)(j))
40+
val stackAtIdx: MoveResult[Stack] = Either.fromTry(Try(positions(i)(j)))
3841
.fold(_ => InvalidMove(s"$index is not on the board"), OkMove.apply)
3942
val newStack: MoveResult[Stack] = stackAtIdx.flatMap {
4043
case Stack(Vector()) => OkMove(stack)
@@ -151,7 +154,7 @@ case class Board(size: Int, boardPositions: BoardLayout) {
151154
}
152155

153156
def stackAt(index: BoardIndex): MoveResult[Stack] =
154-
\/.fromTryCatchNonFatal(boardPositions(index.file - 1)(index.rank - 1)).fold(
157+
Either.fromTry(Try(boardPositions(index.file - 1)(index.rank - 1))).fold(
155158
_ => InvalidMove(s"$index is not on the board"),
156159
OkMove(_)
157160
)
@@ -224,7 +227,7 @@ case class Stack(pieces: Vector[Stone]) {
224227

225228
object Player {
226229
implicit val playerInstance: Equal[Player] = new Equal[Player] {
227-
override def equal(a1: Player, a2: Player): Boolean = a1 == a2
230+
override def eqv(a1: Player, a2: Player): Boolean = a1 == a2
228231
}
229232
}
230233
sealed trait Player {

taklib/src/main/scala/com/github/daenyth/taklib/Game.scala

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import com.github.daenyth.taklib.RuleSet.GameRule
77
import scala.annotation.tailrec
88
import scalax.collection.GraphEdge.UnDiEdge
99
import scalax.collection.immutable.Graph
10-
import scalaz.Ordering.{EQ, GT, LT}
11-
import scalaz.std.anyVal.intInstance
12-
import scalaz.std.option._
13-
import scalaz.std.vector._
14-
import scalaz.syntax.either._
15-
import scalaz.syntax.foldable._
16-
import scalaz.syntax.order._
17-
import scalaz.syntax.semigroup._
18-
import scalaz.{Equal, NonEmptyList, Semigroup, \/}
10+
import cats.instances.int._
11+
import cats.instances.option._
12+
import cats.syntax.either._
13+
import cats.syntax.order._
14+
import cats.syntax.list._
15+
import cats.syntax.semigroup._
16+
import cats.{Semigroup, Eq => Equal}
17+
import cats.data.NonEmptyList
18+
import cats.kernel.Comparison.{EqualTo, GreaterThan, LessThan}
1919

2020
sealed trait GameEndResult
2121
object GameEndResult {
2222
implicit val gerInstance: Semigroup[GameEndResult] with Equal[GameEndResult] =
2323
new Semigroup[GameEndResult] with Equal[GameEndResult] {
24-
override def append(f1: GameEndResult, f2: => GameEndResult) = (f1, f2) match {
24+
override def combine(f1: GameEndResult, f2: GameEndResult): GameEndResult = (f1, f2) match {
2525
case (DoubleRoad, _) => DoubleRoad
2626
case (_, DoubleRoad) => DoubleRoad
2727
case (Draw, r: RoadWin) => r
@@ -36,7 +36,7 @@ object GameEndResult {
3636
case (_, w: WinByResignation) => w
3737
}
3838

39-
override def equal(a1: GameEndResult, a2: GameEndResult): Boolean = (a1, a2) match {
39+
override def eqv(a1: GameEndResult, a2: GameEndResult): Boolean = (a1, a2) match {
4040
case (DoubleRoad, DoubleRoad) => true
4141
case (Draw, Draw) => true
4242
case (RoadWin(p1), RoadWin(p2)) => p1 == p2
@@ -151,15 +151,15 @@ object DefaultRules extends RuleSet {
151151

152152
object Game {
153153

154-
def ofSize(size: Int): String \/ Game = ofSize(size, DefaultRules)
154+
def ofSize(size: Int): Either[String, Game] = ofSize(size, DefaultRules)
155155

156-
def ofSize(size: Int, rules: RuleSet): String \/ Game =
156+
def ofSize(size: Int, rules: RuleSet): Either[String, Game] =
157157
rules.stoneCounts.keySet
158158
.contains(size)
159159
.guard(s"Bad game size: $size")
160160
.map { _ =>
161161
val b = Board.ofSize(size)
162-
new Game(size, 1, rules, NonEmptyList((StartGameWithBoard(b), b)))
162+
new Game(size, 1, rules, NonEmptyList((StartGameWithBoard(b), b), Nil))
163163
}
164164

165165
// Start at turn 3 to make the "play opponent's stone" rule easier
@@ -168,19 +168,19 @@ object Game {
168168
board.size,
169169
turnNumber,
170170
DefaultRules,
171-
NonEmptyList((StartGameWithBoard(board), board))
171+
NonEmptyList((StartGameWithBoard(board), board), Nil)
172172
)
173173

174-
def fromPtn(ptn: String): String \/ MoveResult[Game] =
174+
def fromPtn(ptn: String): Either[String, MoveResult[Game]] =
175175
PtnParser.parseEither(PtnParser.ptn(DefaultRules), ptn).map(_._2)
176176

177-
def fromTps(tps: String): String \/ Game =
177+
def fromTps(tps: String): Either[String, Game] =
178178
TpsParser.parse(TpsParser.tps, tps) match {
179179
case TpsParser.Success((board, turn, nextPlayer), _) =>
180180
// We use one turn for each player's action, Tps uses turn as a move for both players with a move counter between them
181181
val turnNumber = (2 * turn) + nextPlayer.fold(1, 0)
182-
Game.fromBoard(board, turnNumber).right
183-
case err: TpsParser.NoSuccess => err.msg.left
182+
Game.fromBoard(board, turnNumber).asRight
183+
case err: TpsParser.NoSuccess => err.msg.asLeft
184184
}
185185

186186
}
@@ -208,7 +208,7 @@ class Game private (val size: Int,
208208
def takeTurn(action: TurnAction): MoveResult[Game] =
209209
rules.check(this, action).getOrElse {
210210
currentBoard.applyAction(nextPlayer, action).flatMap { nextState =>
211-
val newHistory = (action, nextState) <:: history
211+
val newHistory = (action, nextState) :: history
212212
val game = new Game(size, turnNumber + 1, rules, newHistory)
213213
game.winner match {
214214
case Some(gameEnd) => GameOver(gameEnd, game)
@@ -235,7 +235,7 @@ class Game private (val size: Int,
235235
}
236236

237237
def winner: Option[GameEndResult] =
238-
(roads: Vector[GameEndResult]).suml1Opt |+| flatWin
238+
Semigroup[GameEndResult].combineAllOption(roads) |+| flatWin
239239

240240
private def roads: Vector[RoadWin] = {
241241
def mkGraph(xs: Set[BoardIndex]): Graph[BoardIndex, UnDiEdge] = {
@@ -297,10 +297,10 @@ class Game private (val size: Int,
297297
if (!emptySpaceAvailable
298298
|| whiteCount == reserve
299299
|| blackCount == reserve) {
300-
Some(whiteFlats cmp blackFlats match {
301-
case LT => FlatWin(Black)
302-
case EQ => Draw
303-
case GT => FlatWin(White)
300+
Some(whiteFlats comparison blackFlats match {
301+
case LessThan => FlatWin(Black)
302+
case EqualTo => Draw
303+
case GreaterThan => FlatWin(White)
304304
})
305305
} else None
306306
case stack :: rest =>

0 commit comments

Comments
 (0)