Skip to content

Commit 26462ec

Browse files
committed
Remove TurnAction.player
Close #9
1 parent 5470aaf commit 26462ec

8 files changed

Lines changed: 75 additions & 107 deletions

File tree

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ object Main {
3838
}
3939

4040
def runGameTurn(g: Game): Task[MoveResult[Game]] =
41-
printGame(g) *> runAction(g.nextPlayer).map(g.takeTurn)
42-
43-
def runAction(nextPlayer: Player): Task[TurnAction] = promptAction.map(_(nextPlayer))
41+
printGame(g) *> promptAction.map(g.takeTurn)
4442

4543
def printGame(g: Game) = Task {
4644
val nextPlayInfo = g.turnNumber match {
@@ -70,15 +68,15 @@ object Main {
7068
g.currentBoard.boardPositions.map(_.reverse).transpose.map(_.map(_.toTps).mkString("\t")).mkString("\n")
7169
}
7270

73-
def promptAction: Task[Player => TurnAction] =
71+
def promptAction: Task[TurnAction] =
7472
Task(StdIn.readLine("Your Move?\n > "))
7573
.flatMap { input =>
7674
if (input == null) { throw CleanExit } else
7775
PtnParser
7876
.parseEither(PtnParser.turnAction, input)
7977
.fold(
8078
err => Task.fail(PtnParseError(err)),
81-
toAction => Task.now(toAction)
79+
Task.now
8280
)
8381
}
8482
.handleWith {

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,35 +62,37 @@ case class Board(size: Int, boardPositions: BoardLayout) {
6262
BoardIndex(rank + 1, file + 1) -> boardPositions(rank)(file)
6363
}.toVector
6464

65-
def applyAction(action: TurnAction): MoveResult[Board] = action match {
65+
def applyAction(actingPlayer: Player, action: TurnAction): MoveResult[Board] = action match {
6666
case PlayStone(at, stone) =>
6767
stackAt(at).flatMap {
6868
case s if s.nonEmpty => InvalidMove(s"A stack already exists at ${at.name}")
6969
case _ =>
70-
val stack = Stack.of(stone)
70+
val stack = Stack.of(stone(actingPlayer))
7171
val newPositions = setStackAt(boardPositions, at, stack)
7272
OkMove(Board(size, newPositions))
7373
}
7474
case m: Move => doMoveAction(m)
7575
}
7676

77-
def applyActions(actions: Seq[TurnAction]): MoveResult[Board] =
77+
def applyActions(actions: Seq[(Player, TurnAction)]): MoveResult[Board] =
7878
actions.headOption.fold[MoveResult[Board]](InvalidMove("Tried to apply an empty seq of actions")) {
7979
a => applyActions(a, actions.tail: _*)
8080
}
8181

8282
@tailrec
83-
final def applyActions(a: TurnAction, as: TurnAction*): MoveResult[Board] =
83+
final def applyActions(pa: (Player, TurnAction), pas: (Player, TurnAction)*): MoveResult[Board] = {
84+
val (actingPlayer, a) = pa
8485
// Explicit match instead of map/flatmap to appease @tailrec
85-
applyAction(a) match {
86+
applyAction(actingPlayer, a) match {
8687
case i: InvalidMove => i
8788
case o: GameOver => o
88-
case s @ OkMove(newState) =>
89-
as.toList match {
89+
case s@OkMove(newState) =>
90+
pas.toList match {
9091
case Nil => s
9192
case nextMove :: moreMoves => newState.applyActions(nextMove, moreMoves: _*)
9293
}
9394
}
95+
}
9496

9597
private[taklib] def doMoveAction(m: Move): MoveResult[Board] = {
9698
@tailrec

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,11 @@ object DefaultRules extends RuleSet {
112112
case None =>
113113
Option(InvalidMove(s"Cannot move empty Stack at ${m.from}"))
114114
case Some(controller) =>
115-
(controller === action.player)
115+
val player = game.nextPlayer
116+
(controller === player)
116117
.orElse(
117118
InvalidMove(
118-
s"${action.player} cannot move stack controlled by $controller at ${m.from}"
119+
s"$player cannot move stack controlled by $controller at ${m.from}"
119120
)
120121
)
121122
}
@@ -124,15 +125,9 @@ object DefaultRules extends RuleSet {
124125
}
125126
}
126127

127-
val playerOwnsStone: GameRule = { (game, action) =>
128-
(action.player == game.nextPlayer)
129-
.orElse(InvalidMove(s"${action.player} is not the correct color for this turn"))
130-
}
131-
132128
override val rules: List[GameRule] = List(
133129
actionIndexIsValid,
134-
actingPlayerControlsStack,
135-
playerOwnsStone
130+
actingPlayerControlsStack
136131
)
137132

138133
override val stoneCounts: Map[Int, (Int, Int)] = Map(
@@ -196,7 +191,9 @@ class Game private (val size: Int,
196191
override def toString = {
197192
def pretty(ga: GameAction) = ga match {
198193
case _: StartGameWithBoard => "{New Game}"
199-
case t: TurnAction => s"${t.player} ${t.ptn}"
194+
case t: TurnAction =>
195+
val lastPlayer = nextPlayer.fold(White, Black) // Take the opposite player
196+
s"$lastPlayer ${t.ptn}"
200197
}
201198
s"<Game ${size}x$size lastMove=[${pretty(history.head._1)}] turn=$turnNumber>"
202199
}
@@ -207,7 +204,7 @@ class Game private (val size: Int,
207204

208205
def takeTurn(action: TurnAction): MoveResult[Game] =
209206
rules.check(this, action).getOrElse {
210-
currentBoard.applyAction(action).flatMap { nextState =>
207+
currentBoard.applyAction(nextPlayer, action).flatMap { nextState =>
211208
val newHistory = (action, nextState) <:: history
212209
val game = new Game(size, turnNumber + 1, rules, newHistory)
213210
game.winner match {

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

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import scalaz.Monad
77
sealed trait GameAction
88
case class StartGameWithBoard(board: Board) extends GameAction
99
sealed trait TurnAction extends GameAction {
10-
def player: Player // TODO maybe get rid of this - makes ptn parsing hard
11-
1210
def ptn: String = this match {
13-
case PlayFlat(_, at) => at.name
14-
case PlayStanding(_, at) => s"S${at.name}"
15-
case PlayCapstone(_, at) => s"C${at.name}"
16-
case Move(_, from, direction, count, drops) =>
11+
case PlayFlat(at) => at.name
12+
case PlayStanding(at) => s"S${at.name}"
13+
case PlayCapstone(at) => s"C${at.name}"
14+
case Move(from, direction, count, drops) =>
1715
// Omit count+drops if moving whole stack or one piece
1816
val num =
1917
drops match {
@@ -29,24 +27,23 @@ sealed trait TurnAction extends GameAction {
2927
}
3028
}
3129
object PlayStone {
32-
def unapply(p: PlayStone): Option[(BoardIndex, Stone)] =
30+
def unapply(p: PlayStone): Option[(BoardIndex, Player => Stone)] =
3331
Some((p.at, p.stone))
3432
}
3533
sealed trait PlayStone extends TurnAction {
3634
def at: BoardIndex
37-
def stone: Stone
35+
def stone: Player => Stone
3836
}
39-
case class PlayFlat(player: Player, at: BoardIndex) extends PlayStone {
40-
val stone = FlatStone(player)
37+
case class PlayFlat(at: BoardIndex) extends PlayStone {
38+
val stone = FlatStone.apply _
4139
}
42-
case class PlayStanding(player: Player, at: BoardIndex) extends PlayStone {
43-
val stone = StandingStone(player)
40+
case class PlayStanding(at: BoardIndex) extends PlayStone {
41+
val stone = StandingStone.apply _
4442
}
45-
case class PlayCapstone(player: Player, at: BoardIndex) extends PlayStone {
46-
val stone = Capstone(player)
43+
case class PlayCapstone(at: BoardIndex) extends PlayStone {
44+
val stone = Capstone.apply _
4745
}
48-
case class Move(player: Player,
49-
from: BoardIndex,
46+
case class Move(from: BoardIndex,
5047
direction: MoveDirection,
5148
count: Option[Int],
5249
drops: Option[Vector[Int]])

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

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,10 @@ object PtnParser extends RegexParsers with RichParsing {
1919
val file = str.charAt(1).toString.toInt
2020
BoardIndex(rank, file)
2121
}
22-
val playFlat: Parser[Player => PlayFlat] =
23-
boardIndex ^^ { idx => player =>
24-
PlayFlat(player, idx)
25-
}
26-
val playStanding: Parser[Player => PlayStanding] =
27-
"S".r ~ boardIndex ^^ {
28-
case (_ ~ idx) =>
29-
player =>
30-
PlayStanding(player, idx)
31-
}
32-
val playCapstone: Parser[Player => PlayCapstone] =
33-
"C".r ~ boardIndex ^^ {
34-
case (_ ~ idx) =>
35-
player =>
36-
PlayCapstone(player, idx)
37-
}
38-
val playStone: Parser[Player => PlayStone] =
39-
playFlat | playStanding | playCapstone
22+
val playFlat: Parser[PlayFlat] = boardIndex ^^ { idx => PlayFlat(idx) }
23+
val playStanding: Parser[PlayStanding] = "S".r ~ boardIndex ^^ { case _ ~ idx => PlayStanding(idx) }
24+
val playCapstone: Parser[PlayCapstone] = "C".r ~ boardIndex ^^ { case _ ~ idx => PlayCapstone(idx) }
25+
val playStone: Parser[PlayStone] = playFlat | playStanding | playCapstone
4026

4127
val moveDirection: Parser[MoveDirection] = "[-+<>]".r ^^ {
4228
case "-" => Down
@@ -45,22 +31,21 @@ object PtnParser extends RegexParsers with RichParsing {
4531
case ">" => Right
4632
}
4733

48-
val moveStones: Parser[Player => Move] = {
34+
val moveStones: Parser[Move] = {
4935
val count = "[12345678]".r ^^ { _.toInt }
5036
val drops = "[12345678]+".r ^^ { _.toVector.map(_.toString.toInt) }
5137
(count.? ~ boardIndex ~ moveDirection ~ drops.?) ^^ {
5238
case (count: Option[Int]) ~
5339
(idx: BoardIndex) ~
5440
(direction: MoveDirection) ~
5541
(drops: Option[Vector[Int]]) =>
56-
player =>
57-
Move(player, idx, direction, count, drops)
42+
Move(idx, direction, count, drops)
5843
}
5944
}
6045

6146
val infoMark: Parser[String] = "'{1,2}".r | "[!?]{1,2}".r
6247

63-
val turnAction: Parser[Player => TurnAction] = (moveStones | playStone) ~ infoMark.? ^^ {
48+
val turnAction: Parser[TurnAction] = (moveStones | playStone) ~ infoMark.? ^^ {
6449
case action ~ _ => action
6550
}
6651

@@ -73,24 +58,24 @@ object PtnParser extends RegexParsers with RichParsing {
7358

7459
val comment: Parser[String] = """(?s)\{(.*?)\}""".r
7560

76-
val fullTurnLine: Parser[(Int, Player => TurnAction, Player => TurnAction)] =
61+
val fullTurnLine: Parser[(Int, TurnAction, TurnAction)] =
7762
"""\d+\.""".r ~ turnAction ~ turnAction ~ comment.? ^^ {
7863
case turnNumber ~ whiteAction ~ blackAction ~ _comment =>
7964
(turnNumber.dropRight(1).toInt, whiteAction, blackAction)
8065
}
8166

82-
val lastTurnLine: Parser[(Int, Player => TurnAction, Option[Player => TurnAction])] =
67+
val lastTurnLine: Parser[(Int, TurnAction, Option[TurnAction])] =
8368
"""\d+\.""".r ~ turnAction ~ turnAction.? ~ comment.? ^^ {
8469
case turnNumber ~ whiteAction ~ blackAction ~ _comment =>
8570
(turnNumber.dropRight(1).toInt, whiteAction, blackAction)
8671
}
8772

88-
def gameHistory(startingTurn: Int, skipFirst: Boolean): Parser[Vector[Player => TurnAction]] =
73+
def gameHistory(startingTurn: Int, skipFirst: Boolean): Parser[Vector[TurnAction]] =
8974
rep(fullTurnLine) ~ lastTurnLine.? ^^? {
9075
case fullturns ~ lastTurn =>
9176
var nextTurnNumber = startingTurn
9277
val iter = fullturns.iterator
93-
val history = new VectorBuilder[Player => TurnAction]
78+
val history = new VectorBuilder[TurnAction]
9479
\/.fromTryCatchNonFatal {
9580
while (iter.hasNext) {
9681
val (turnNumber, whiteAction, blackAction) = iter.next()
@@ -185,7 +170,6 @@ object PtnParser extends RegexParsers with RichParsing {
185170
initialGame <- getInitialGame(size, ruleSet)
186171
} yield {
187172
val finalGame = history.zipWithIndex
188-
.map { case (a, i) => (a(ruleSet.expectedStoneColor(i + 1)), i) }
189173
.foldLeftM[MoveResult, Game](initialGame) {
190174
case (game, (action, actionIdx)) =>
191175
game.takeTurn(action).noteInvalid(r => s"(Move #${actionIdx + 1}) $r")

taklib/src/test/scala/com/github/daenyth/taklib/GameTest.scala

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ class GameTest
5252

5353
"A board with 5 stones in a row" should "have a road win" in {
5454
val board = Board.ofSize(5)
55-
val roadBoard = board.applyActions((1 to 5).map(n => PlayFlat(White, BoardIndex(1, n))))
55+
val roadBoard = board.applyActions((1 to 5).map(n => White -> PlayFlat(BoardIndex(1, n))))
5656
val game = Game.fromBoard(roadBoard.value)
5757
game.winner.value shouldBe RoadWin(White)
5858
}
5959

6060
"Four flats and a capstone" should "have a road win" in {
6161
val board = Board.ofSize(5)
62-
val moves = (1 to 4).map(n => PlayFlat(Black, BoardIndex(1, n))) ++ Vector(
63-
PlayCapstone(Black, BoardIndex(1, 5))
62+
val moves = (1 to 4).map(n => Black -> PlayFlat(BoardIndex(1, n))) ++ Vector(
63+
Black -> PlayCapstone(BoardIndex(1, 5))
6464
)
6565
val roadBoard = board.applyActions(moves)
6666
val game = Game.fromBoard(roadBoard.value)
@@ -69,8 +69,8 @@ class GameTest
6969

7070
"Four flats and a standing stone" should "not be a win" in {
7171
val board = Board.ofSize(5)
72-
val moves = (1 to 4).map(n => PlayFlat(Black, BoardIndex(1, n))) ++ Vector(
73-
PlayStanding(Black, BoardIndex(1, 5))
72+
val moves = (1 to 4).map(n => Black -> PlayFlat(BoardIndex(1, n))) ++ Vector(
73+
Black -> PlayStanding(BoardIndex(1, 5))
7474
)
7575
val roadBoard = board.applyActions(moves)
7676
val game = Game.fromBoard(roadBoard.value)
@@ -79,21 +79,21 @@ class GameTest
7979

8080
"A player" should "be able to move a stack they control" in {
8181
val i = BoardIndex(1, 1)
82-
val board = Board.ofSize(5).applyAction(PlayFlat(White, i)).value
82+
val board = Board.ofSize(5).applyAction(White, PlayFlat(i)).value
8383
val game = Game.fromBoard(board)
84-
DefaultRules.actingPlayerControlsStack(game, Move(White, i, Right, None, None)) shouldBe None
84+
DefaultRules.actingPlayerControlsStack(game, Move(i, Right, None, None)) shouldBe None
8585
}
8686

8787
"A player" should "not be able to move a stack they don't control" in {
8888
val i = BoardIndex(1, 1)
89-
val board = Board.ofSize(5).applyAction(PlayFlat(Black, i)).value
89+
val board = Board.ofSize(5).applyAction(Black, PlayFlat(i)).value
9090
val game = Game.fromBoard(board)
91-
DefaultRules.actingPlayerControlsStack(game, Move(White, i, Right, None, None)) shouldBe 'nonEmpty
91+
DefaultRules.actingPlayerControlsStack(game, Move(i, Right, None, None)) shouldBe 'nonEmpty
9292
}
9393

9494
"The first move" should "be taken with a black flatstone" in {
9595
val game = Game.ofSize(5).value
96-
val result = game.takeTurn(PlayFlat(Black, BoardIndex(1, 1)))
96+
val result = game.takeTurn(PlayFlat(BoardIndex(1, 1)))
9797
result shouldBe an[OkMove[_]]
9898
}
9999

@@ -105,8 +105,8 @@ class GameTest
105105

106106
"A game's tps" should "round trip to the same game" in {
107107
val game1 = (for {
108-
a <- Game.ofSize(5).value.takeTurn(PlayFlat(Black, BoardIndex(1, 1)))
109-
b <- a.takeTurn(PlayFlat(White, BoardIndex(5, 1)))
108+
a <- Game.ofSize(5).value.takeTurn(PlayFlat(BoardIndex(1, 1)))
109+
b <- a.takeTurn(PlayFlat(BoardIndex(5, 1)))
110110
} yield b).value
111111
val tps = game1.toTps
112112
val game2 = Game.fromTps(tps).value
@@ -117,7 +117,7 @@ class GameTest
117117

118118
"A long game" should "be playable without a problem" in {
119119
// https://www.playtak.com/games/153358/view
120-
val maybeMoves: Vector[String \/ (Player => TurnAction)] = Vector(
120+
val maybeMoves: Vector[String \/ TurnAction] = Vector(
121121
"a6",
122122
"a1",
123123
"c3",
@@ -214,13 +214,7 @@ class GameTest
214214
"5c1>14",
215215
"e3"
216216
).map { PtnParser.parseEither(PtnParser.turnAction, _) }
217-
val toActions: Vector[Player => TurnAction] = maybeMoves.sequenceU.value
218-
val actions = toActions.zipWithIndex.map {
219-
case (action, 0) => action(Black)
220-
case (action, 1) => action(White)
221-
case (action, n) if n % 2 == 0 => action(White)
222-
case (action, _) => action(Black)
223-
}
217+
val actions: Vector[TurnAction] = maybeMoves.sequenceU.value
224218
val game = actions.foldLeftM[MoveResult, Game](Game.ofSize(6).value) { (game, action) => game.takeTurn(action) }
225219
game should matchPattern { case GameOver(FlatWin(White), _) => () }
226220
}

0 commit comments

Comments
 (0)