From fe50c593e8863a289e11891006cac5b2304f5a6e Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Fri, 7 Mar 2025 18:15:33 +0000 Subject: [PATCH 1/3] Add stub dameo gamelogic --- src/main/scala/Action.scala | 10 + src/main/scala/Actor.scala | 7 + src/main/scala/Board.scala | 63 + src/main/scala/CubeAction.scala | 2 + src/main/scala/DiceRoll.scala | 2 + src/main/scala/Drop.scala | 4 + src/main/scala/EndTurn.scala | 1 + src/main/scala/Game.scala | 160 +++ src/main/scala/GameCollection.scala | 59 +- src/main/scala/Hash.scala | 10 + src/main/scala/History.scala | 18 + src/main/scala/Lift.scala | 1 + src/main/scala/Move.scala | 60 + src/main/scala/Pass.scala | 1 + src/main/scala/Piece.scala | 17 + src/main/scala/Pos.scala | 14 +- src/main/scala/Replay.scala | 66 + src/main/scala/Role.scala | 74 ++ src/main/scala/SelectSquares.scala | 1 + src/main/scala/Situation.scala | 164 +++ src/main/scala/Undo.scala | 1 + src/main/scala/dameo/Action.scala | 25 + src/main/scala/dameo/Actor.scala | 55 + src/main/scala/dameo/Board.scala | 82 ++ src/main/scala/dameo/Divider.scala | 11 + src/main/scala/dameo/File.scala | 42 + src/main/scala/dameo/Game.scala | 103 ++ src/main/scala/dameo/Hash.scala | 1071 ++++++++++++++++ src/main/scala/dameo/History.scala | 85 ++ src/main/scala/dameo/Move.scala | 48 + src/main/scala/dameo/Piece.scala | 35 + src/main/scala/dameo/Pos.scala | 317 +++++ src/main/scala/dameo/Rank.scala | 39 + src/main/scala/dameo/Replay.scala | 290 +++++ src/main/scala/dameo/Role.scala | 141 +++ src/main/scala/dameo/Setup.scala | 6 + src/main/scala/dameo/Situation.scala | 82 ++ src/main/scala/dameo/StartingPosition.scala | 1101 +++++++++++++++++ src/main/scala/dameo/format/FEN.scala | 31 + src/main/scala/dameo/format/Forsyth.scala | 171 +++ src/main/scala/dameo/format/Uci.scala | 120 ++ src/main/scala/dameo/format/UciCharPair.scala | 42 + src/main/scala/dameo/format/UciDump.scala | 28 + src/main/scala/dameo/format/pdn/Binary.scala | 124 ++ src/main/scala/dameo/format/pdn/Dumper.scala | 21 + src/main/scala/dameo/format/pdn/Parser.scala | 266 ++++ src/main/scala/dameo/format/pdn/Reader.scala | 100 ++ src/main/scala/dameo/format/pdn/package.scala | 171 +++ .../scala/dameo/format/pdn/parsingModel.scala | 58 + src/main/scala/dameo/opening/Ecopening.scala | 70 ++ .../scala/dameo/opening/EcopeningDB.scala | 19 + .../scala/dameo/opening/FullOpening.scala | 19 + .../scala/dameo/opening/FullOpeningDB.scala | 18 + .../dameo/opening/FullOpeningPartA.scala | 6 + .../dameo/opening/FullOpeningPartB.scala | 6 + src/main/scala/dameo/package.scala | 16 + src/main/scala/dameo/project/build.properties | 1 + src/main/scala/dameo/variant/Dameo.scala | 27 + src/main/scala/dameo/variant/Variant.scala | 134 ++ src/main/scala/format/FEN.scala | 42 + src/main/scala/format/Forsyth.scala | 24 + src/main/scala/format/Uci.scala | 103 ++ src/main/scala/format/UciCharPair.scala | 1 + src/main/scala/format/UciDump.scala | 4 + src/main/scala/format/pgn/Binary.scala | 4 + src/main/scala/format/pgn/Dumper.scala | 2 + src/main/scala/format/pgn/Reader.scala | 21 + src/main/scala/format/pgn/tagModel.scala | 12 + src/main/scala/opening/Ecopening.scala | 22 + src/main/scala/opening/EcopeningDB.scala | 3 + src/main/scala/opening/FullOpening.scala | 11 + src/main/scala/opening/FullOpeningDB.scala | 24 + src/main/scala/variant/Variant.scala | 131 +- 73 files changed, 6115 insertions(+), 5 deletions(-) create mode 100644 src/main/scala/dameo/Action.scala create mode 100644 src/main/scala/dameo/Actor.scala create mode 100644 src/main/scala/dameo/Board.scala create mode 100644 src/main/scala/dameo/Divider.scala create mode 100644 src/main/scala/dameo/File.scala create mode 100644 src/main/scala/dameo/Game.scala create mode 100644 src/main/scala/dameo/Hash.scala create mode 100644 src/main/scala/dameo/History.scala create mode 100644 src/main/scala/dameo/Move.scala create mode 100644 src/main/scala/dameo/Piece.scala create mode 100644 src/main/scala/dameo/Pos.scala create mode 100644 src/main/scala/dameo/Rank.scala create mode 100644 src/main/scala/dameo/Replay.scala create mode 100644 src/main/scala/dameo/Role.scala create mode 100644 src/main/scala/dameo/Setup.scala create mode 100644 src/main/scala/dameo/Situation.scala create mode 100644 src/main/scala/dameo/StartingPosition.scala create mode 100644 src/main/scala/dameo/format/FEN.scala create mode 100644 src/main/scala/dameo/format/Forsyth.scala create mode 100644 src/main/scala/dameo/format/Uci.scala create mode 100644 src/main/scala/dameo/format/UciCharPair.scala create mode 100644 src/main/scala/dameo/format/UciDump.scala create mode 100644 src/main/scala/dameo/format/pdn/Binary.scala create mode 100644 src/main/scala/dameo/format/pdn/Dumper.scala create mode 100644 src/main/scala/dameo/format/pdn/Parser.scala create mode 100644 src/main/scala/dameo/format/pdn/Reader.scala create mode 100644 src/main/scala/dameo/format/pdn/package.scala create mode 100644 src/main/scala/dameo/format/pdn/parsingModel.scala create mode 100644 src/main/scala/dameo/opening/Ecopening.scala create mode 100644 src/main/scala/dameo/opening/EcopeningDB.scala create mode 100644 src/main/scala/dameo/opening/FullOpening.scala create mode 100644 src/main/scala/dameo/opening/FullOpeningDB.scala create mode 100644 src/main/scala/dameo/opening/FullOpeningPartA.scala create mode 100644 src/main/scala/dameo/opening/FullOpeningPartB.scala create mode 100644 src/main/scala/dameo/package.scala create mode 100644 src/main/scala/dameo/project/build.properties create mode 100644 src/main/scala/dameo/variant/Dameo.scala create mode 100644 src/main/scala/dameo/variant/Variant.scala diff --git a/src/main/scala/Action.scala b/src/main/scala/Action.scala index 6c7435906..37ede741c 100644 --- a/src/main/scala/Action.scala +++ b/src/main/scala/Action.scala @@ -20,6 +20,7 @@ abstract class Action( def toGo: go.Action def toBackgammon: backgammon.Action def toAbalone: abalone.Action + def toDameo: dameo.Action } object Action { @@ -63,6 +64,10 @@ object Action { case m: abalone.Move => Move.Abalone(m) } + def wrap(action: dameo.Action): Action = action match { + case m: dameo.Move => Move.Dameo(m) + } + def toChess(action: Action): chess.Action = action match { case Move.Chess(m) => m case Drop.Chess(d) => d @@ -111,4 +116,9 @@ object Action { case _ => sys.error("Expecting a abalone action e.g. move") } + def toDameo(action: Action): dameo.Move = action match { + case Move.Dameo(m) => m + case _ => sys.error("Expecting a dameo action e.g. move") + } + } diff --git a/src/main/scala/Actor.scala b/src/main/scala/Actor.scala index 4029bca49..34a3ed5f4 100644 --- a/src/main/scala/Actor.scala +++ b/src/main/scala/Actor.scala @@ -71,4 +71,11 @@ object Actor { Board.Abalone(a.board) ) {} + final case class Dameo(a: dameo.Actor) + extends Actor( + Piece.Dameo(a.piece), + Pos.Dameo(a.pos), + Board.Dameo(a.board) + ) {} + } diff --git a/src/main/scala/Board.scala b/src/main/scala/Board.scala index 6a78b679d..977e3da55 100644 --- a/src/main/scala/Board.scala +++ b/src/main/scala/Board.scala @@ -40,6 +40,7 @@ sealed abstract class Board( def toGo: go.Board def toBackgammon: backgammon.Board def toAbalone: abalone.Board + def toDameo: dameo.Board } object Board { @@ -83,6 +84,7 @@ object Board { def toGo = sys.error("Can't make a go board from a chess board") def toBackgammon = sys.error("Can't make a backgammon board from a chess board") def toAbalone = sys.error("Can't make a abalone board from a chess board") + def toDameo = sys.error("Can't make a dameo board from a chess board") } @@ -124,6 +126,7 @@ object Board { def toGo = sys.error("Can't make a go board from a draughts board") def toBackgammon = sys.error("Can't make a backgammon board from a draughts board") def toAbalone = sys.error("Can't make a abalone board from a draughts board") + def toDameo = sys.error("Can't make a dameo board from a draughts board") } @@ -166,6 +169,7 @@ object Board { def toGo = sys.error("Can't make a go board from a fairysf board") def toBackgammon = sys.error("Can't make a backgammon board from a fairysf board") def toAbalone = sys.error("Can't make a abalone board from a fairysf board") + def toDameo = sys.error("Can't make a dameo board from a fairysf board") } @@ -207,6 +211,7 @@ object Board { def toGo = sys.error("Can't make a go board from a samurai board") def toBackgammon = sys.error("Can't make a backgammon board from a samurai board") def toAbalone = sys.error("Can't make a abalone board from a samurai board") + def toDameo = sys.error("Can't make a dameo board from a samurai board") } @@ -250,6 +255,7 @@ object Board { def toGo = sys.error("Can't make a go board from a togyzkumalak board") def toBackgammon = sys.error("Can't make a backgammon board from a togyzkumalak board") def toAbalone = sys.error("Can't make a abalone board from a togyzkumalak board") + def toDameo = sys.error("Can't make a dameo board from a togyzkumalak board") } @@ -292,6 +298,7 @@ object Board { def toGo = b def toBackgammon = sys.error("Can't make a backgammon board from a go board") def toAbalone = sys.error("Can't make a abalone board from a go board") + def toDameo = sys.error("Can't make a dameo board from a go board") } @@ -338,6 +345,7 @@ object Board { def toGo = sys.error("Can't make a go board from a backgammon board") def toBackgammon = b def toAbalone = sys.error("Can't make a abalone board from a backgammon board") + def toDameo = sys.error("Can't make a dameo board from a backgammon board") } @@ -379,6 +387,49 @@ object Board { def toGo = sys.error("Can't make a go board from a abalone board") def toBackgammon = sys.error("Can't make a backgammon board from a abalone board") def toAbalone = b + def toDameo = sys.error("Can't make a dameo board from a abalone board") + + } + + case class Dameo(b: dameo.Board) + extends Board( + b.pieces.map { case (pos, piece) => (Pos.Dameo(pos), (Piece.Dameo(piece), 1)) }, + History.Dameo(b.history), + Variant.Dameo(b.variant) + ) { + + def withHistory(h: History): Board = h match { + case History.Dameo(h) => Dameo(b.withHistory(h)) + case _ => sys.error("Not passed dameo objects") + } + + def usedDice: List[Int] = List.empty + + def situationOf(player: Player): Situation = Situation.Dameo(b.situationOf(player)) + + def materialImbalance: Int = b.materialImbalance + + override def toString: String = b.toString + + def copy(history: History, variant: Variant): Board = (history, variant) match { + case (History.Dameo(history), Variant.Dameo(variant)) => + Dameo(b.copy(history = history, variant = variant)) + case _ => sys.error("Unable to copy a dameo board with non-dameo arguments") + } + def copy(history: History): Board = history match { + case History.Dameo(history) => Dameo(b.copy(history = history)) + case _ => sys.error("Unable to copy a dameo board with non-dameo arguments") + } + + def toFairySF = sys.error("Can't make a fairysf board from a dameo board") + def toChess = sys.error("Can't make a chess board from a dameo board") + def toDraughts = sys.error("Can't make a draughts board from a dameo board") + def toSamurai = sys.error("Can't make a samurai board from a dameo board") + def toTogyzkumalak = sys.error("Can't make a togyzkumalak board from a dameo board") + def toGo = sys.error("Can't make a go board from a dameo board") + def toBackgammon = sys.error("Can't make a backgammon board from a dameo board") + def toAbalone = sys.error("Can't make a abalone board from a dameo board") + def toDameo = b } @@ -466,6 +517,16 @@ object Board { variant ) ) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + Dameo( + dameo.Board.apply( + pieces.flatMap { + case (Pos.Dameo(pos), (Piece.Dameo(piece), _)) => Some((pos, piece)) + case _ => None + }, + variant + ) + ) case _ => sys.error("Mismatched gamelogic types 27") } @@ -477,6 +538,7 @@ object Board { implicit def goBoard(b: go.Board) = Board.Go(b) implicit def backgammonBoard(b: backgammon.Board) = Board.Backgammon(b) implicit def abaloneBoard(b: abalone.Board) = Board.Abalone(b) + implicit def dameoBoard(b: dameo.Board) = Board.Dameo(b) def init(lib: GameLogic, variant: Variant): Board = (lib, variant) match { case (GameLogic.Draughts(), Variant.Draughts(variant)) => Draughts(draughts.Board.init(variant)) @@ -488,6 +550,7 @@ object Board { case (GameLogic.Go(), Variant.Go(variant)) => Go(go.Board.init(variant)) case (GameLogic.Backgammon(), Variant.Backgammon(variant)) => Backgammon(backgammon.Board.init(variant)) case (GameLogic.Abalone(), Variant.Abalone(variant)) => Abalone(abalone.Board.init(variant)) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => Dameo(dameo.Board.init(variant)) case _ => sys.error("Mismatched gamelogic types 28") } diff --git a/src/main/scala/CubeAction.scala b/src/main/scala/CubeAction.scala index 14456ea72..daf708175 100644 --- a/src/main/scala/CubeAction.scala +++ b/src/main/scala/CubeAction.scala @@ -50,6 +50,7 @@ object CubeAction { def toGo = sys.error("Can't make a go cubeaction from a backgammon cubeaction") def toBackgammon = c def toAbalone = sys.error("Can't make a abalone cubeaction from a backgammon cubeaction") + def toDameo = sys.error("Can't make a dameo cubeaction from a backgammon cubeaction") } @@ -83,6 +84,7 @@ object CubeInteraction { def toGo = sys.error("Can't make a go cubeinteraction from a backgammon cubeinteraction") def toBackgammon = c def toAbalone = sys.error("Can't make a abalone cubeinteraction from a backgammon cubeinteraction") + def toDameo = sys.error("Can't make a dameo cubeinteraction from a backgammon cubeinteraction") } diff --git a/src/main/scala/DiceRoll.scala b/src/main/scala/DiceRoll.scala index 726c8db7d..891f0151e 100644 --- a/src/main/scala/DiceRoll.scala +++ b/src/main/scala/DiceRoll.scala @@ -51,6 +51,7 @@ object DiceRoll { def toGo = sys.error("Can't make a go DiceRoll from a chess DiceRoll") def toBackgammon = sys.error("Can't make a backgammon DiceRoll from a chess DiceRoll") def toAbalone = sys.error("Can't make a abalone DiceRoll from a chess DiceRoll") + def toDameo = sys.error("Can't make a dameo DiceRoll from a chess DiceRoll") } @@ -78,6 +79,7 @@ object DiceRoll { def toGo = sys.error("Can't make a go DiceRoll from a backgammon DiceRoll") def toBackgammon = dr def toAbalone = sys.error("Can't make a abalone DiceRoll from a backgammon DiceRoll") + def toDameo = sys.error("Can't make a dameo DiceRoll from a backgammon DiceRoll") } diff --git a/src/main/scala/Drop.scala b/src/main/scala/Drop.scala index 9f4b48340..b93882f5a 100644 --- a/src/main/scala/Drop.scala +++ b/src/main/scala/Drop.scala @@ -54,6 +54,7 @@ object Drop { def toGo = sys.error("Can't make a go drop from a chess drop") def toBackgammon = sys.error("Can't make a backgammon drop from a chess drop") def toAbalone = sys.error("Can't make a abalone drop from a chess drop") + def toDameo = sys.error("Can't make a dameo drop from a chess drop") } @@ -81,6 +82,7 @@ object Drop { def toGo = sys.error("Can't make a go drop from a fairysf drop") def toBackgammon = sys.error("Can't make a backgammon drop from a fairysf drop") def toAbalone = sys.error("Can't make a abalone drop from a fairysf drop") + def toDameo = sys.error("Can't make a dameo drop from a fairysf drop") } @@ -108,6 +110,7 @@ object Drop { def toGo = d def toBackgammon = sys.error("Can't make a backgammon drop from a go drop") def toAbalone = sys.error("Can't make a abalone drop from a go drop") + def toDameo = sys.error("Can't make a dameo drop from a go drop") } @@ -135,6 +138,7 @@ object Drop { def toGo = sys.error("Can't make a go drop from a backgammon drop") def toBackgammon = d def toAbalone = sys.error("Can't make a abalone drop from a backgammon drop") + def toDameo = sys.error("Can't make a dameo drop from a backgammon drop") } diff --git a/src/main/scala/EndTurn.scala b/src/main/scala/EndTurn.scala index ad860b0d1..8065d1239 100644 --- a/src/main/scala/EndTurn.scala +++ b/src/main/scala/EndTurn.scala @@ -46,6 +46,7 @@ object EndTurn { def toGo = sys.error("Can't make a go endturn from a backgammon endturn") def toBackgammon = et def toAbalone = sys.error("Can't make a abalone endturn from a backgammon endturn") + def toDameo = sys.error("Can't make a dameo endturn from a backgammon endturn") } diff --git a/src/main/scala/Game.scala b/src/main/scala/Game.scala index 38ca48215..fd10ed409 100644 --- a/src/main/scala/Game.scala +++ b/src/main/scala/Game.scala @@ -90,6 +90,13 @@ abstract class Game( promotion = None, metrics ) + case Uci.DameoMove(uci) => + apply( + Pos.Dameo(uci.orig), + Pos.Dameo(uci.dest), + promotion = None, + metrics + ) case Uci.ChessDrop(uci) => drop( Role.ChessRole(uci.role), @@ -221,6 +228,7 @@ abstract class Game( def toGo: go.Game def toBackgammon: backgammon.Game def toAbalone: abalone.Game + def toDameo: dameo.Game } @@ -374,6 +382,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a chess game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a chess game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a chess game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a chess game into a dameo game") } @@ -540,6 +549,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a draughts game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a draughts game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a draughts game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a draughts game into a dameo game") } @@ -690,6 +700,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a fairysf game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a fairysf game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a fairysf game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a fairysf game into a dameo game") } @@ -825,6 +836,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a samurai game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a samurai game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a samurai game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a samurai game into a dameo game") } @@ -961,6 +973,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a togyzkumalak game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a togyzkumalak game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a togyzkumalak game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a togyzkumalak game into a dameo game") } @@ -1106,6 +1119,7 @@ object Game { def toGo: go.Game = g def toBackgammon: backgammon.Game = sys.error("Can't turn a go game into a backgammon game") def toAbalone: abalone.Game = sys.error("Can't turn a go game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a go game into a dameo game") } @@ -1271,6 +1285,7 @@ object Game { def toGo: go.Game = sys.error("Can't turn a backgammon game into a go game") def toBackgammon: backgammon.Game = g def toAbalone: abalone.Game = sys.error("Can't turn a backgammon game into a abalone game") + def toDameo: dameo.Game = sys.error("Can't turn a backgammon game into a dameo game") } @@ -1407,6 +1422,144 @@ object Game { def toGo: go.Game = sys.error("Can't turn a abalone game into a go game") def toBackgammon: backgammon.Game = sys.error("Can't turn a abalone game into a backgammon game") def toAbalone: abalone.Game = g + def toDameo: dameo.Game = sys.error("Can't turn a abalone game into a dameo game") + + } + + final case class Dameo(g: dameo.Game) + extends Game( + Situation.Dameo(g.situation), + g.actionStrs, + g.clock, + g.plies, + g.turnCount, + g.startedAtPly, + g.startedAtTurn + ) { + + def apply( + orig: Pos, + dest: Pos, + promotion: Option[PromotableRole] = None, + metrics: MoveMetrics = MoveMetrics(), + finalSquare: Boolean = false, + captures: Option[List[Pos]] = None, + partialCaptures: Boolean = false + ): Validated[String, (Game, Move)] = (orig, dest) match { + case (Pos.Dameo(orig), Pos.Dameo(dest)) => + g.apply(orig, dest, metrics) + .toEither + .map(t => (Dameo(t._1), Move.Dameo(t._2))) + .toValidated + case _ => sys.error("Not passed Dameo objects") + } + + def apply(action: Action): Game = + action match { + case (Move.Dameo(move)) => Dameo(g.apply(move)) + case _ => sys.error("Not passed Dameo objects") + } + + def drop( + role: Role, + pos: Pos, + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, Drop)] = sys.error("Can't drop in Dameo") + + def lift( + pos: Pos, + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, Lift)] = + sys.error("Can't lift in dameo") + + def pass(metrics: MoveMetrics = MoveMetrics()): Validated[String, (Game, Pass)] = + sys.error("Can't pass in Dameo") + + def selectSquares( + squares: List[Pos], + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, SelectSquares)] = + sys.error("Can't selectSquares in Dameo") + + def diceRoll( + dice: List[Int], + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, DiceRoll)] = + sys.error("Can't diceroll in Dameo") + + def undo(metrics: MoveMetrics = MoveMetrics()): Validated[String, (Game, Undo)] = + sys.error("Can't undo in dameo") + + def endTurn(metrics: MoveMetrics = MoveMetrics()): Validated[String, (Game, EndTurn)] = + sys.error("Can't endTurn in dameo") + + def cubeAction( + interaction: CubeInteraction, + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, CubeAction)] = + sys.error("Can't cubeaction in dameo") + + def randomizeDiceRoll: Option[DiceRoll] = None + + def randomizeAndApplyDiceRoll( + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, DiceRoll)] = + sys.error("Can't apply diceroll in dameo") + + def copy(clock: Option[ClockBase]): Game = + Dameo(g.copy(clock = clock)) + + def copy(plies: Int, turnCount: Int, startedAtPly: Int, startedAtTurn: Int): Game = + Dameo( + g.copy( + plies = plies, + turnCount = turnCount, + startedAtPly = startedAtPly, + startedAtTurn = startedAtTurn + ) + ) + + def copy( + clock: Option[ClockBase], + plies: Int, + turnCount: Int, + startedAtPly: Int, + startedAtTurn: Int + ): Game = + Dameo( + g.copy( + clock = clock, + plies = plies, + turnCount = turnCount, + startedAtPly = startedAtPly, + startedAtTurn = startedAtTurn + ) + ) + + def copy(situation: Situation, plies: Int, turnCount: Int): Game = situation match { + case Situation.Dameo(situation) => + Dameo(g.copy(situation = situation, plies = plies, turnCount = turnCount)) + case _ => + sys.error("Unable to copy dameo game with non-dameo arguments") + } + def copy(situation: Situation): Game = situation match { + case Situation.Dameo(situation) => Dameo(g.copy(situation = situation)) + case _ => sys.error("Unable to copy dameo game with non-dameo arguments") + } + + def hasJustSwitchedTurns: Boolean = g.hasJustSwitchedTurns + + def withTurnsAndPlies(p: Int, t: Int): Game = Dameo(g.withTurnsAndPlies(p, t)) + + def toFairySF: fairysf.Game = sys.error("Can't turn a dameo game into a fairysf game") + def toChess: chess.Game = sys.error("Can't turn a dameo game into a chess game") + def toDraughts: draughts.DraughtsGame = sys.error("Can't turn a dameo game into a draughts game") + def toSamurai: samurai.Game = sys.error("Can't turn a dameo game into a samurai game") + def toTogyzkumalak: togyzkumalak.Game = sys.error("Can't turn a dameo game into a togyzkumalak game") + def toGo: go.Game = sys.error("Can't turn a dameo game into a go game") + def toBackgammon: backgammon.Game = sys.error("Can't turn a dameo game into a backgammon game") + def toAbalone: abalone.Game = sys.error("Can't turn a dameo game into a abalone game") + def toDameo: dameo.Game = g } @@ -1440,6 +1593,8 @@ object Game { Backgammon(backgammon.Game(situation, actionStrs, clock, plies, turnCount, startedAtPly, startedAtTurn)) case (GameLogic.Abalone(), Situation.Abalone(situation)) => Abalone(abalone.Game(situation, actionStrs, clock, plies, turnCount, startedAtPly, startedAtTurn)) + case (GameLogic.Dameo(), Situation.Dameo(situation)) => + Dameo(dameo.Game(situation, actionStrs, clock, plies, turnCount, startedAtPly, startedAtTurn)) case _ => sys.error("Mismatched gamelogic types 32") } @@ -1460,6 +1615,8 @@ object Game { Backgammon(backgammon.Game.apply(variant)) case (GameLogic.Abalone(), Variant.Abalone(variant)) => Abalone(abalone.Game.apply(variant)) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + Dameo(dameo.Game.apply(variant)) case _ => sys.error("Mismatched gamelogic types 33") } @@ -1481,6 +1638,8 @@ object Game { Backgammon(backgammon.Game.apply(variant.map(_.toBackgammon), fen.map(_.toBackgammon))) case GameLogic.Abalone() => Abalone(abalone.Game.apply(variant.map(_.toAbalone), fen.map(_.toAbalone))) + case GameLogic.Dameo() => + Dameo(dameo.Game.apply(variant.map(_.toDameo), fen.map(_.toDameo))) case _ => sys.error("Mismatched gamelogic types 36") } @@ -1492,5 +1651,6 @@ object Game { def wrap(g: go.Game) = Go(g) def wrap(g: backgammon.Game) = Backgammon(g) def wrap(g: abalone.Game) = Abalone(g) + def wrap(g: dameo.Game) = Dameo(g) } diff --git a/src/main/scala/GameCollection.scala b/src/main/scala/GameCollection.scala index 1139d1345..a7741a9d7 100644 --- a/src/main/scala/GameCollection.scala +++ b/src/main/scala/GameCollection.scala @@ -53,8 +53,13 @@ object GameLogic { def name = "Abalone" } + final case class Dameo() extends GameLogic { + def id = 8 + def name = "Dameo" + } + def all: List[GameLogic] = - List(Chess(), Draughts(), FairySF(), Samurai(), Togyzkumalak(), Go(), Backgammon(), Abalone()) + List(Chess(), Draughts(), FairySF(), Samurai(), Togyzkumalak(), Go(), Backgammon(), Abalone(), Dameo()) // TODO: I'm sure there is a better scala way of doing this def apply(id: Int): GameLogic = id match { @@ -65,6 +70,7 @@ object GameLogic { case 5 => Go() case 6 => Backgammon() case 7 => Abalone() + case 8 => Dameo() case _ => Chess() } } @@ -464,6 +470,41 @@ object GameFamily { def playerColors = Map(P1 -> "black", P2 -> "white") } + final case class Dameo() extends GameFamily { + def id = 13 + def name = GameLogic.Dameo().name + def key = GameLogic.Dameo().name.toLowerCase() + def gameLogic = GameLogic.Dameo() + def hasFishnet = false + def hasAnalysisBoard = false + def defaultVariant = Variant.Dameo(strategygames.dameo.variant.Dameo) + def variants = Variant.all(GameLogic.Dameo()) + def displayPiece = "wK" + def pieceSetThemes = List("wide_crown", "fabirovsky", "check_yb") + def pieceSetDefault = "wide_crown" + def boardThemes = List( + "blue", + "blue2", + "blue3", + "canvas", + "wood", + "wood2", + "wood3", + "maple", + "brown", + "leather", + "green", + "marble", + "grey", + "metal", + "olive", + "purple" + ) + def boardThemeDefault = "blue3" + def playerNames = Map(P1 -> "White", P2 -> "Black") + def playerColors = Map(P1 -> "white", P2 -> "black") + } + def all: List[GameFamily] = List( Chess(), Draughts(), @@ -477,7 +518,8 @@ object GameFamily { Go(), Backgammon(), BreakthroughTroyka(), - Abalone() + Abalone(), + Dameo() ) // TODO: I'm sure there is a better scala way of doing this @@ -494,6 +536,7 @@ object GameFamily { case 10 => Backgammon() case 11 => BreakthroughTroyka() case 12 => Abalone() + case 13 => Dameo() case _ => Chess() } @@ -619,6 +662,14 @@ object GameGroup { def medley = true } + final case class Dameo() extends GameGroup { + def id = 13 + def name = "Dameo" + def key = "dameo" + def variants = Variant.all(GameLogic.Dameo()).filter(_.gameFamily.name == this.name) + def medley = true + } + def all: List[GameGroup] = List( Chess(), @@ -633,7 +684,8 @@ object GameGroup { Go(), Backgammon(), BreakthroughTroyka(), - Abalone() + Abalone(), + Dameo() ) def medley: List[GameGroup] = all.filter(_.medley) @@ -652,6 +704,7 @@ object GameGroup { case 10 => Backgammon() case 11 => BreakthroughTroyka() case 12 => Abalone() + case 13 => Dameo() case _ => Chess() } } diff --git a/src/main/scala/Hash.scala b/src/main/scala/Hash.scala index 870f82277..299062018 100644 --- a/src/main/scala/Hash.scala +++ b/src/main/scala/Hash.scala @@ -75,6 +75,12 @@ object Hash { val actorMasks: Array[Long] = zc.actorMasks } + final case class DameoZobristConstants(zc: dameo.Hash.ZobristConstants) extends ZobristConstants { + def hexToLong(s: String): Long = zc.hexToLong(s) + val p1TurnMask: Long = zc.p1TurnMask + val actorMasks: Array[Long] = zc.actorMasks + } + // The following masks are compatible with the Polyglot // opening book format. private def polyglotTable(lib: GameLogic): ZobristConstants = lib match { @@ -86,6 +92,7 @@ object Hash { case GameLogic.Go() => GoZobristConstants(new go.Hash.ZobristConstants(0)) case GameLogic.Backgammon() => BackgammonZobristConstants(new backgammon.Hash.ZobristConstants(0)) case GameLogic.Abalone() => AbaloneZobristConstants(new abalone.Hash.ZobristConstants(0)) + case GameLogic.Dameo() => DameoZobristConstants(new dameo.Hash.ZobristConstants(0)) } private def randomTable(lib: GameLogic): ZobristConstants = lib match { @@ -97,6 +104,7 @@ object Hash { case GameLogic.Go() => GoZobristConstants(new go.Hash.ZobristConstants(16)) case GameLogic.Backgammon() => BackgammonZobristConstants(new backgammon.Hash.ZobristConstants(16)) case GameLogic.Abalone() => AbaloneZobristConstants(new abalone.Hash.ZobristConstants(16)) + case GameLogic.Dameo() => DameoZobristConstants(new dameo.Hash.ZobristConstants(16)) } private def get(lib: GameLogic, situation: Situation, table: ZobristConstants): Long = @@ -121,6 +129,8 @@ object Hash { backgammon.Hash.get(situation, table) case (GameLogic.Abalone(), Situation.Abalone(situation), AbaloneZobristConstants(table)) => abalone.Hash.get(situation, table) + case (GameLogic.Dameo(), Situation.Dameo(situation), DameoZobristConstants(table)) => + dameo.Hash.get(situation, table) case _ => sys.error("Invalid lib, situation and table combination") } diff --git a/src/main/scala/History.scala b/src/main/scala/History.scala index 52a956e25..595c9df80 100644 --- a/src/main/scala/History.scala +++ b/src/main/scala/History.scala @@ -135,6 +135,14 @@ object History { score = h.score ) + final case class Dameo(h: dameo.History) + extends History( + lastTurn = h.lastTurn.map(Uci.wrap), + currentTurn = h.currentTurn.map(Uci.wrap), + positionHashes = h.positionHashes, + halfMoveClock = h.halfMoveClock + ) + implicit def chessHistory(h: chess.History) = Chess(h) implicit def draughtsHistory(h: draughts.DraughtsHistory) = Draughts(h) implicit def fairysfHistory(h: fairysf.History) = FairySF(h) @@ -143,6 +151,7 @@ object History { implicit def goHistory(h: go.History) = Go(h) implicit def backgammonHistory(h: backgammon.History) = Backgammon(h) implicit def abaloneHistory(h: abalone.History) = Abalone(h) + implicit def dameoHistory(h: dameo.History) = Dameo(h) // lila def apply( @@ -247,6 +256,15 @@ object History { score = score ) ) + case GameLogic.Dameo() => + Dameo( + dameo.History( + lastTurn = lastTurn.map(lm => lm.toDameo), + currentTurn = currentTurn.map(lm => lm.toDameo), + positionHashes = positionHashes, + halfMoveClock = halfMoveClock + ) + ) case _ => sys.error("Mismatched gamelogic types 1") } diff --git a/src/main/scala/Lift.scala b/src/main/scala/Lift.scala index 5206ed45c..c74048e29 100644 --- a/src/main/scala/Lift.scala +++ b/src/main/scala/Lift.scala @@ -49,6 +49,7 @@ object Lift { def toGo = sys.error("Can't make a go lift from a backgammon lift") def toBackgammon = l def toAbalone = sys.error("Can't make a abalone lift from a backgammon lift") + def toDameo = sys.error("Can't make a dameo lift from a backgammon lift") } diff --git a/src/main/scala/Move.scala b/src/main/scala/Move.scala index e7d33314f..6e66e1a34 100644 --- a/src/main/scala/Move.scala +++ b/src/main/scala/Move.scala @@ -46,6 +46,7 @@ sealed abstract class Move( def toTogyzkumalak: togyzkumalak.Move def toBackgammon: backgammon.Move def toAbalone: abalone.Move + def toDameo: dameo.Move } @@ -103,6 +104,7 @@ object Move { def toGo = sys.error("Can't make a go move from a chess move") def toBackgammon = sys.error("Can't make a backgammon move from a chess move") def toAbalone = sys.error("Can't make a abalone move from a chess move") + def toDameo = sys.error("Can't make a dameo move from a chess move") } @@ -156,6 +158,7 @@ object Move { def toGo = sys.error("Can't make a go move from a draughts move") def toBackgammon = sys.error("Can't make a backgammon move from a draughts move") def toAbalone = sys.error("Can't make a abalone move from a draughts move") + def toDameo = sys.error("Can't make a dameo move from a draughts move") } @@ -210,6 +213,7 @@ object Move { def toGo = sys.error("Can't make a go move from a fairysf move") def toBackgammon = sys.error("Can't make a backgammon move from a fairysf move") def toAbalone = sys.error("Can't make a abalone move from a fairysf move") + def toDameo = sys.error("Can't make a dameo move from a fairysf move") } @@ -257,6 +261,7 @@ object Move { def toGo = sys.error("Can't make a go move from a samurai move") def toBackgammon = sys.error("Can't make a backgammon move from a samurai move") def toAbalone = sys.error("Can't make a abalone move from a samurai move") + def toDameo = sys.error("Can't make a dameo move from a samurai move") } @@ -304,6 +309,7 @@ object Move { def toGo = sys.error("Can't make a go move from a togyzkumalak move") def toBackgammon = sys.error("Can't make a backgammon move from a togyzkumalak move") def toAbalone = sys.error("Can't make a abalone move from a togyzkumalak move") + def toDameo = sys.error("Can't make a dameo move from a togyzkumalak move") } @@ -348,6 +354,7 @@ object Move { def toGo = sys.error("Can't make a go move from a backgammon move") def toBackgammon = m def toAbalone = sys.error("Can't make a abalone move from a backgammon move") + def toDameo = sys.error("Can't make a dameo move from a backgammon move") } @@ -392,6 +399,58 @@ object Move { def toGo = sys.error("Can't make a go move from a abalone move") def toBackgammon = sys.error("Can't make a backgammon move from a abalone move") def toAbalone = m + def toDameo = sys.error("Can't make a dameo move from a abalone move") + + } + + final case class Dameo(m: dameo.Move) + extends Move( + Piece.Dameo(m.piece), + Pos.Dameo(m.orig), + Pos.Dameo(m.dest), + Situation.Dameo(m.situationBefore), + Board.Dameo(m.after), + m.autoEndTurn, + m.capture match { + case Some(capture) => Option(List(Pos.Dameo(capture))) + case None => None + }, + m.promotion match { + case Some(promotion) => Some(Role.DameoPromotableRole(promotion)) + case None => None + }, + None, + None, + false, + m.metrics + ) { + + def situationAfter: Situation = Situation.Dameo(m.situationAfter) + def finalizeAfter(finalSquare: Boolean = false): Board = m.finalizeAfter + + def toUci: Uci.Move = Uci.DameoMove(m.toUci) + + def toShortUci: Uci.Move = + Uci.Move( + GameLogic.Dameo(), + orig, + dest, + promotion, + if (capture.isDefined) capture.get.takeRight(1).some else None + ) + + def first: Move = this + + val unwrap = m + def toFairySF = sys.error("Can't make a fairysf move from a dameo move") + def toChess = sys.error("Can't make a chess move from a dameo move") + def toDraughts = sys.error("Can't make a draughts move from a dameo move") + def toSamurai = sys.error("Can't make a samurai move from a dameo move") + def toTogyzkumalak = sys.error("Can't make a togyzkumalak move from a dameo move") + def toGo = sys.error("Can't make a go move from a dameo move") + def toBackgammon = sys.error("Can't make a backgammon move from a dameo move") + def toAbalone = sys.error("Can't make a abalone move from a dameo move") + def toDameo = m } @@ -402,5 +461,6 @@ object Move { def wrap(m: togyzkumalak.Move): Move = Move.Togyzkumalak(m) def wrap(m: backgammon.Move): Move = Move.Backgammon(m) def wrap(m: abalone.Move): Move = Move.Abalone(m) + def wrap(m: dameo.Move): Move = Move.Dameo(m) } diff --git a/src/main/scala/Pass.scala b/src/main/scala/Pass.scala index 7ee307c25..a426174bc 100644 --- a/src/main/scala/Pass.scala +++ b/src/main/scala/Pass.scala @@ -48,6 +48,7 @@ object Pass { def toGo = p def toBackgammon = sys.error("Can't make a backgammon pass from a go pass") def toAbalone = sys.error("Can't make a abalone pass from a go pass") + def toDameo = sys.error("Can't make a dameo pass from a go pass") } diff --git a/src/main/scala/Piece.scala b/src/main/scala/Piece.scala index 4681a0ed8..da24ce942 100644 --- a/src/main/scala/Piece.scala +++ b/src/main/scala/Piece.scala @@ -97,6 +97,16 @@ object Piece { } + final case class Dameo(p: dameo.Piece) + extends Piece( + p.player, + Role.DameoRole(p.role) + ) { + + def forsyth: Char = p.forsyth + + } + def apply(lib: GameLogic, player: Player, role: Role): Piece = (lib, role) match { case (GameLogic.Draughts(), Role.DraughtsRole(role)) => Draughts(draughts.Piece(player, role)) case (GameLogic.Chess(), Role.ChessRole(role)) => Chess(chess.Piece(player, role)) @@ -107,6 +117,7 @@ object Piece { case (GameLogic.Go(), Role.GoRole(role)) => Go(go.Piece(player, role)) case (GameLogic.Backgammon(), Role.BackgammonRole(role)) => Backgammon(backgammon.Piece(player, role)) case (GameLogic.Abalone(), Role.AbaloneRole(role)) => Abalone(abalone.Piece(player, role)) + case (GameLogic.Dameo(), Role.DameoRole(role)) => Dameo(dameo.Piece(player, role)) case _ => sys.error("Mismatched gamelogic types 2") } @@ -119,6 +130,7 @@ object Piece { case (GameLogic.Go()) => sys.error("cannot get piece from Char for go anymore") case (GameLogic.Backgammon()) => sys.error("cannot get piece from Char for backgammon anymore") case (GameLogic.Abalone()) => sys.error("cannot get piece from Char for abalone anymore") + case (GameLogic.Dameo()) => dameo.Piece.fromChar(c).map(Dameo) } def chessPieceMap(pieceMap: PieceMap): chess.PieceMap = pieceMap.flatMap { @@ -162,6 +174,11 @@ object Piece { case _ => None } + def dameoPieceMap(pieceMap: PieceMap): dameo.PieceMap = pieceMap.flatMap { + case (Pos.Dameo(pos), (Dameo(piece), _)) => Some((pos, piece)) + case _ => None + } + def pieceMapForChess(pieces: strategygames.chess.PieceMap): PieceMap = pieces.flatMap { case (pos, piece) => Some((Pos.Chess(pos), (Piece.Chess(piece), 1))) diff --git a/src/main/scala/Pos.scala b/src/main/scala/Pos.scala index 730d88a53..9bf840738 100644 --- a/src/main/scala/Pos.scala +++ b/src/main/scala/Pos.scala @@ -119,13 +119,24 @@ object Pos { def piotr: Char = p.piotr - // TODO Abalone check this lazy val toInt: Int = (p.file.index << 3) + p.rank.index lazy val all: List[Pos] = abalone.Pos.all.map(Abalone) } + final case class Dameo(p: dameo.Pos) extends Pos { + + val key: String = p.key + + def piotr: Char = p.piotr + + lazy val toInt: Int = (p.file.index << 4) + p.rank.index + + lazy val all: List[Pos] = dameo.Pos.all.map(Dameo) + + } + // need to equivalate this method for draughts probably // think we need to figure out a way to map into Draughts with a board size at this point def fromKey(lib: GameLogic, key: String): Option[Pos] = lib match { @@ -137,6 +148,7 @@ object Pos { case GameLogic.Go() => go.Pos.fromKey(key).map(Go) case GameLogic.Backgammon() => backgammon.Pos.fromKey(key).map(Backgammon) case GameLogic.Abalone() => abalone.Pos.fromKey(key).map(Abalone) + case GameLogic.Dameo() => dameo.Pos.fromKey(key).map(Dameo) } // def at(lib: GameLogic, x: Int, y: Int): Option[Pos] = lib match { diff --git a/src/main/scala/Replay.scala b/src/main/scala/Replay.scala index d7fcc9dc4..1a3de926c 100644 --- a/src/main/scala/Replay.scala +++ b/src/main/scala/Replay.scala @@ -152,6 +152,18 @@ object Replay { } } + final case class Dameo(r: dameo.Replay) + extends Replay( + Game.Dameo(r.setup), + r.actions.map((m: dameo.Move) => Move.Dameo(m)), + Game.Dameo(r.state) + ) { + def copy(state: Game): Replay = state match { + case Game.Dameo(state) => Replay.wrap(r.copy(state = state)) + case _ => sys.error("Unable to copy a dameo replay with a non-dameo state") + } + } + def apply(lib: GameLogic, setup: Game, actions: List[Action], state: Game): Replay = (lib, setup, state) match { case (GameLogic.Draughts(), Game.Draughts(setup), Game.Draughts(state)) => @@ -170,6 +182,8 @@ object Replay { Backgammon(backgammon.Replay(setup, actions.map(Action.toBackgammon), state)) case (GameLogic.Abalone(), Game.Abalone(setup), Game.Abalone(state)) => Abalone(abalone.Replay(setup, actions.map(Action.toAbalone), state)) + case (GameLogic.Dameo(), Game.Dameo(setup), Game.Dameo(state)) => + Dameo(dameo.Replay(setup, actions.map(Action.toDameo), state)) case _ => sys.error("Mismatched gamelogic types 5") } @@ -295,6 +309,19 @@ object Replay { message ) } + case (GameLogic.Dameo(), FEN.Dameo(initialFen), Variant.Dameo(variant)) => + dameo.Replay.gameWithUciWhileValid( + actionStrs, + initialFen, + variant + ) match { + case (game, gameswithsan, message) => + ( + Game.Dameo(game), + gameswithsan.map { case (g, u) => (Game.Dameo(g), Uci.DameoWithSan(u)) }, + message + ) + } case _ => sys.error("Mismatched gamelogic types 7") } @@ -362,6 +389,12 @@ object Replay { .toEither .map(s => s.map(Situation.Abalone)) .toValidated + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + dameo.Replay + .situations(actionStrs, initialFen.map(_.toDameo), variant) + .toEither + .map(s => s.map(Situation.Dameo)) + .toValidated case _ => sys.error("Mismatched gamelogic types 8") } @@ -429,6 +462,14 @@ object Replay { } ) + private def dameoUcis(ucis: List[Uci]): List[dameo.format.Uci] = + ucis.flatMap(u => + u match { + case u: Uci.Dameo => Some(u.unwrap) + case _ => None + } + ) + def boardsFromUci( lib: GameLogic, ucis: List[Uci], @@ -489,6 +530,12 @@ object Replay { .toEither .map(b => b.map(Board.Abalone)) .toValidated + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + dameo.Replay + .boardsFromUci(dameoUcis(ucis), initialFen.map(_.toDameo), variant) + .toEither + .map(b => b.map(Board.Dameo)) + .toValidated case _ => sys.error("Mismatched gamelogic types 8a") } @@ -547,6 +594,12 @@ object Replay { .toEither .map(s => s.map(Situation.Abalone)) .toValidated + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + dameo.Replay + .situationsFromUci(dameoUcis(ucis), initialFen.map(_.toDameo), variant) + .toEither + .map(s => s.map(Situation.Dameo)) + .toValidated case _ => sys.error("Mismatched gamelogic types 9") } @@ -594,6 +647,10 @@ object Replay { abalone.Replay .gameFromUciStrings(ucis.flatten.toList, initialFen.map(_.toAbalone), variant) .map(Game.Abalone) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + dameo.Replay + .gameFromUciStrings(ucis.flatten.toList, initialFen.map(_.toDameo), variant) + .map(Game.Dameo) case _ => sys.error("Mismatched gamelogic types for Replay 10") } @@ -652,6 +709,12 @@ object Replay { .toEither .map(r => Replay.Abalone(r)) .toValidated + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + dameo.Replay + .apply(dameoUcis(ucis), initialFen.map(_.toDameo), variant) + .toEither + .map(r => Replay.Dameo(r)) + .toValidated case _ => sys.error("Mismatched gamelogic types Replay 11") } @@ -678,6 +741,8 @@ object Replay { backgammon.Replay.plyAtFen(actionStrs, initialFen.map(_.toBackgammon), variant, atFen) case (GameLogic.Abalone(), Variant.Abalone(variant), FEN.Abalone(atFen)) => abalone.Replay.plyAtFen(actionStrs, initialFen.map(_.toAbalone), variant, atFen) + case (GameLogic.Dameo(), Variant.Dameo(variant), FEN.Dameo(atFen)) => + dameo.Replay.plyAtFen(actionStrs, initialFen.map(_.toDameo), variant, atFen) case _ => sys.error("Mismatched gamelogic types 10") } @@ -689,5 +754,6 @@ object Replay { def wrap(r: go.Replay) = Go(r) def wrap(r: backgammon.Replay) = Backgammon(r) def wrap(r: abalone.Replay) = Abalone(r) + def wrap(r: dameo.Replay) = Dameo(r) } diff --git a/src/main/scala/Role.scala b/src/main/scala/Role.scala index a0a743030..848075558 100644 --- a/src/main/scala/Role.scala +++ b/src/main/scala/Role.scala @@ -24,6 +24,7 @@ sealed trait PromotableRole extends Role { def toGo: go.PromotableRole def toBackgammon: backgammon.PromotableRole def toAbalone: abalone.PromotableRole + def toDameo: dameo.PromotableRole } object Role { @@ -124,6 +125,18 @@ object Role { override def toString() = r.name } + final case class DameoRole(r: dameo.Role) extends Role { + lazy val gameLogic = GameLogic.Dameo() + lazy val forsyth = r.forsyth + lazy val pgn = r.pdn + lazy val binaryInt = r.binaryInt + lazy val hashInt = r.hashInt + lazy val name = r.name + lazy val groundName = r.groundName + lazy val storable = false + override def toString() = r.name + } + final case class ChessPromotableRole(r: chess.PromotableRole) extends PromotableRole { lazy val gameLogic = GameLogic.Chess() lazy val forsyth = r.forsyth @@ -142,6 +155,7 @@ object Role { def toGo: go.PromotableRole = sys.error("Not implemented for chess") def toBackgammon: backgammon.PromotableRole = sys.error("Not implemented for chess") def toAbalone: abalone.PromotableRole = sys.error("Not implemented for chess") + def toDameo: dameo.PromotableRole = sys.error("Not implemented for chess") } final case class DraughtsPromotableRole(r: draughts.PromotableRole) extends PromotableRole { @@ -162,6 +176,7 @@ object Role { def toGo: go.PromotableRole = sys.error("Not implemented for draughts") def toBackgammon: backgammon.PromotableRole = sys.error("Not implemented for draughts") def toAbalone: abalone.PromotableRole = sys.error("Not implemented for draughts") + def toDameo: dameo.PromotableRole = sys.error("Not implemented for draughts") } final case class FairySFPromotableRole(r: fairysf.PromotableRole) extends PromotableRole { @@ -182,6 +197,28 @@ object Role { def toGo: go.PromotableRole = sys.error("Not implemented for go") def toBackgammon: backgammon.PromotableRole = sys.error("Not implemented for backgammon") def toAbalone: abalone.PromotableRole = sys.error("Not implemented for fairysf") + def toDameo: dameo.PromotableRole = sys.error("Not implemented for fairysf") + } + + final case class DameoPromotableRole(r: dameo.PromotableRole) extends PromotableRole { + lazy val gameLogic = GameLogic.Dameo() + lazy val forsyth = r.forsyth + lazy val pgn = r.pdn + lazy val binaryInt = r.binaryInt + lazy val hashInt = r.hashInt + lazy val name = r.name + lazy val groundName = r.name + lazy val storable = false + override def toString() = r.name + def toDraughts = sys.error("Not implemented for dameo") + def toChess: chess.PromotableRole = sys.error("Not implemented for dameo") + def toFairySF: fairysf.PromotableRole = sys.error("Not implemented for dameo") + def toSamurai: samurai.PromotableRole = sys.error("Not implemented for dameo") + def toTogyzkumalak: togyzkumalak.PromotableRole = sys.error("Not implemented for dameo") + def toGo: go.PromotableRole = sys.error("Not implemented for dameo") + def toBackgammon: backgammon.PromotableRole = sys.error("Not implemented for dameo") + def toAbalone: abalone.PromotableRole = sys.error("Not implemented for dameo") + def toDameo: dameo.PromotableRole = r } // lila @@ -194,6 +231,7 @@ object Role { case GameLogic.Go() => go.Role.all.map(GoRole) case GameLogic.Backgammon() => backgammon.Role.all.map(BackgammonRole) case GameLogic.Abalone() => abalone.Role.all.map(AbaloneRole) + case GameLogic.Dameo() => dameo.Role.all.map(DameoRole) } def allPromotable(lib: GameLogic): List[PromotableRole] = lib match { @@ -205,6 +243,7 @@ object Role { case GameLogic.Go() => sys.error("allPromotable not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotable not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotable not implemented for abalone") + case GameLogic.Dameo() => sys.error("allPromotable not implemented for dameo") } def allByForsyth(lib: GameLogic): Map[Char, Role] = lib match { @@ -217,6 +256,7 @@ object Role { case GameLogic.Go() => go.Role.allByForsyth.map { case (f, r) => (f, GoRole(r)) } case GameLogic.Backgammon() => backgammon.Role.allByForsyth.map { case (f, r) => (f, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByForsyth.map { case (f, r) => (f, AbaloneRole(r)) } + case GameLogic.Dameo() => dameo.Role.allByForsyth.map { case (f, r) => (f, DameoRole(r)) } } def allByForsyth(lib: GameLogic, gf: GameFamily): Map[Char, Role] = lib match { @@ -231,6 +271,8 @@ object Role { backgammon.Role.allByForsyth(gf).map { case (f, r) => (f, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByForsyth(gf).map { case (f, r) => (f, AbaloneRole(r)) } + case GameLogic.Dameo() => + dameo.Role.allByForsyth(gf).map { case (f, r) => (f, DameoRole(r)) } } def allByPgn(lib: GameLogic): Map[Char, Role] = lib match { @@ -243,6 +285,7 @@ object Role { case GameLogic.Go() => go.Role.allByPgn.map { case (p, r) => (p, GoRole(r)) } case GameLogic.Backgammon() => backgammon.Role.allByPgn.map { case (p, r) => (p, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByPgn.map { case (p, r) => (p, AbaloneRole(r)) } + case GameLogic.Dameo() => dameo.Role.allByPdn.map { case (p, r) => (p, DameoRole(r)) } } def allByPgn(lib: GameLogic, gf: GameFamily): Map[Char, Role] = lib match { @@ -255,6 +298,7 @@ object Role { case GameLogic.Go() => go.Role.allByPgn(gf).map { case (p, r) => (p, GoRole(r)) } case GameLogic.Backgammon() => backgammon.Role.allByPgn(gf).map { case (p, r) => (p, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByPgn(gf).map { case (p, r) => (p, AbaloneRole(r)) } + case GameLogic.Dameo() => dameo.Role.allByPdn.map { case (p, r) => (p, DameoRole(r)) } } def allByName(lib: GameLogic): Map[String, Role] = lib match { @@ -267,6 +311,7 @@ object Role { case GameLogic.Go() => go.Role.allByName.map { case (n, r) => (n, GoRole(r)) } case GameLogic.Backgammon() => backgammon.Role.allByName.map { case (n, r) => (n, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByName.map { case (n, r) => (n, AbaloneRole(r)) } + case GameLogic.Dameo() => dameo.Role.allByName.map { case (n, r) => (n, DameoRole(r)) } } def allByName(lib: GameLogic, gf: GameFamily): Map[String, Role] = lib match { @@ -279,6 +324,7 @@ object Role { case GameLogic.Go() => go.Role.allByName(gf).map { case (n, r) => (n, GoRole(r)) } case GameLogic.Backgammon() => backgammon.Role.allByName(gf).map { case (n, r) => (n, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByName(gf).map { case (n, r) => (n, AbaloneRole(r)) } + case GameLogic.Dameo() => dameo.Role.allByName(gf).map { case (n, r) => (n, DameoRole(r)) } } def allByGroundName(lib: GameLogic): Map[String, Role] = lib match { @@ -293,6 +339,8 @@ object Role { backgammon.Role.allByGroundName.map { case (n, r) => (n, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByGroundName.map { case (n, r) => (n, AbaloneRole(r)) } + case GameLogic.Dameo() => + dameo.Role.allByGroundName.map { case (n, r) => (n, DameoRole(r)) } } def allByGroundName(lib: GameLogic, gf: GameFamily): Map[String, Role] = lib match { @@ -307,6 +355,8 @@ object Role { backgammon.Role.allByGroundName(gf).map { case (n, r) => (n, BackgammonRole(r)) } case GameLogic.Abalone() => abalone.Role.allByGroundName(gf).map { case (n, r) => (n, AbaloneRole(r)) } + case GameLogic.Dameo() => + dameo.Role.allByGroundName(gf).map { case (n, r) => (n, DameoRole(r)) } } def allPromotableByName(lib: GameLogic): Map[String, PromotableRole] = lib match { @@ -321,6 +371,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByName not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByName not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByName not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByName.map { case (n, r) => (n, DameoPromotableRole(r)) } } def allPromotableByName(lib: GameLogic, gf: GameFamily): Map[String, PromotableRole] = lib match { @@ -335,6 +387,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByName not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByName not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByName not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByName.map { case (n, r) => (n, DameoPromotableRole(r)) } } def allPromotableByGroundName(lib: GameLogic): Map[String, PromotableRole] = lib match { @@ -348,6 +402,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByGroundName not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByGroundName not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByGroundName not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByGroundName.map { case (n, r) => (n, DameoPromotableRole(r)) } } def allPromotableByGroundName(lib: GameLogic, gf: GameFamily): Map[String, PromotableRole] = lib match { @@ -361,6 +417,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByGroundName not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByGroundName not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByGroundName not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByGroundName.map { case (n, r) => (n, DameoPromotableRole(r)) } } def allPromotableByForsyth(lib: GameLogic): Map[Char, PromotableRole] = lib match { @@ -375,6 +433,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByForsyth not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByForsyth not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByForsyth not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByForsyth.map { case (f, r) => (f, DameoPromotableRole(r)) } } def allPromotableByForsyth(lib: GameLogic, gf: GameFamily): Map[Char, PromotableRole] = lib match { @@ -389,6 +449,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByForsyth not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByForsyth not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByForsyth not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByForsyth.map { case (f, r) => (f, DameoPromotableRole(r)) } } def allPromotableByPgn(lib: GameLogic): Map[Char, PromotableRole] = lib match { @@ -402,6 +464,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByPgn not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByPgn not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByPgn not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByPdn.map { case (p, r) => (p, DameoPromotableRole(r)) } } def allPromotableByPgn(lib: GameLogic, gf: GameFamily): Map[Char, PromotableRole] = lib match { @@ -415,6 +479,8 @@ object Role { case GameLogic.Go() => sys.error("allPromotableByPgn not implemented for go") case GameLogic.Backgammon() => sys.error("allPromotableByPgn not implemented for backgammon") case GameLogic.Abalone() => sys.error("allPromotableByPgn not implemented for abalone") + case GameLogic.Dameo() => + dameo.Role.allPromotableByPdn.map { case (p, r) => (p, DameoPromotableRole(r)) } } def forsyth(lib: GameLogic, c: Char): Option[Role] = lib match { @@ -426,6 +492,7 @@ object Role { case GameLogic.Go() => go.Role.forsyth(c).map(GoRole) case GameLogic.Backgammon() => backgammon.Role.forsyth(c).map(BackgammonRole) case GameLogic.Abalone() => abalone.Role.forsyth(c).map(AbaloneRole) + case GameLogic.Dameo() => dameo.Role.forsyth(c).map(DameoRole) } def promotable(lib: GameLogic, gf: GameFamily, c: Char): Option[PromotableRole] = lib match { @@ -437,6 +504,7 @@ object Role { case GameLogic.Go() => sys.error("promotable not implemented for go") case GameLogic.Backgammon() => sys.error("promotable not implemented for backgammon") case GameLogic.Abalone() => sys.error("promotable not implemented for abalone") + case GameLogic.Dameo() => dameo.Role.promotable(c).map(DameoPromotableRole) } def promotable(lib: GameLogic, gf: GameFamily, name: String): Option[PromotableRole] = lib match { @@ -448,6 +516,7 @@ object Role { case GameLogic.Go() => sys.error("promotable not implemented for go") case GameLogic.Backgammon() => sys.error("promotable not implemented for backgammon") case GameLogic.Abalone() => sys.error("promotable not implemented for abalone") + case GameLogic.Dameo() => dameo.Role.promotable(name).map(DameoPromotableRole) } def promotable(lib: GameLogic, gf: GameFamily, name: Option[String]): Option[PromotableRole] = lib match { @@ -459,6 +528,7 @@ object Role { case GameLogic.Go() => sys.error("promotable not implemented for go") case GameLogic.Backgammon() => sys.error("promotable not implemented for backgammon") case GameLogic.Abalone() => sys.error("promotable not implemented for abalone") + case GameLogic.Dameo() => dameo.Role.promotable(name).map(DameoPromotableRole) } def storable(lib: GameLogic): List[Role] = lib match { @@ -470,6 +540,7 @@ object Role { case GameLogic.Go() => List() case GameLogic.Backgammon() => backgammon.Role.storable.map(BackgammonRole) case GameLogic.Abalone() => List() + case GameLogic.Dameo() => List() } def pgnMoveToRole(lib: GameLogic, gf: GameFamily, c: Char): Role = lib match { @@ -481,6 +552,7 @@ object Role { case GameLogic.Go() => GoRole(go.Role.pgnMoveToRole(gf, c)) case GameLogic.Backgammon() => BackgammonRole(backgammon.Role.pgnMoveToRole(gf, c)) case GameLogic.Abalone() => AbaloneRole(abalone.Role.pgnMoveToRole(gf, c)) + case GameLogic.Dameo() => DameoRole(dameo.Role.pdnMoveToRole(c)) } def javaSymbolToRole(lib: GameLogic, s: String): Role = lib match { @@ -492,10 +564,12 @@ object Role { case GameLogic.Go() => GoRole(go.Role.javaSymbolToRole(s)) case GameLogic.Backgammon() => BackgammonRole(backgammon.Role.javaSymbolToRole(s)) case GameLogic.Abalone() => AbaloneRole(abalone.Role.javaSymbolToRole(s)) + case GameLogic.Dameo() => DameoRole(dameo.Role.javaSymbolToRole(s)) } def wrap(pr: chess.PromotableRole): PromotableRole = ChessPromotableRole(pr) def wrap(pr: draughts.PromotableRole): PromotableRole = DraughtsPromotableRole(pr) def wrap(pr: fairysf.PromotableRole): PromotableRole = FairySFPromotableRole(pr) + def wrap(pr: dameo.PromotableRole): PromotableRole = DameoPromotableRole(pr) } diff --git a/src/main/scala/SelectSquares.scala b/src/main/scala/SelectSquares.scala index 988775ad5..19e5d74a9 100644 --- a/src/main/scala/SelectSquares.scala +++ b/src/main/scala/SelectSquares.scala @@ -50,6 +50,7 @@ object SelectSquares { def toGo = ss def toBackgammon = sys.error("Can't make a backgammon SelectSquares from a go SelectSquares") def toAbalone = sys.error("Can't make a abalone SelectSquares from a go SelectSquares") + def toDameo = sys.error("Can't make a dameo SelectSquares from a go SelectSquares") } diff --git a/src/main/scala/Situation.scala b/src/main/scala/Situation.scala index 240d77ea0..0db9f74e3 100644 --- a/src/main/scala/Situation.scala +++ b/src/main/scala/Situation.scala @@ -150,6 +150,7 @@ sealed abstract class Situation(val board: Board, val player: Player) { def toGo: go.Situation def toBackgammon: backgammon.Situation def toAbalone: abalone.Situation + def toDameo: dameo.Situation } @@ -319,6 +320,7 @@ object Situation { def toGo = sys.error("Can't make go situation from chess situation") def toBackgammon = sys.error("Can't make backgammon situation from chess situation") def toAbalone = sys.error("Can't make abalone situation from chess situation") + def toDameo = sys.error("Can't make dameo situation from chess situation") } final case class Draughts(s: draughts.Situation) @@ -494,6 +496,7 @@ object Situation { def toGo = sys.error("Can't make go situation from draughts situation") def toBackgammon = sys.error("Can't make backgammon situation from draughts situation") def toAbalone = sys.error("Can't make abalone situation from draughts situation") + def toDameo = sys.error("Can't make dameo situation from draughts situation") } @@ -651,6 +654,7 @@ object Situation { def toGo = sys.error("Can't make go situation from fairysf situation") def toBackgammon = sys.error("Can't make backgammon situation from fairysf situation") def toAbalone = sys.error("Can't make abalone situation from fairysf situation") + def toDameo = sys.error("Can't make dameo situation from fairysf situation") } final case class Samurai(s: samurai.Situation) @@ -802,6 +806,7 @@ object Situation { def toGo = sys.error("Can't make go situation from samurai situation") def toBackgammon = sys.error("Can't make backgammon situation from samurai situation") def toAbalone = sys.error("Can't make abalone situation from samurai situation") + def toDameo = sys.error("Can't make dameo situation from samurai situation") } final case class Togyzkumalak(s: togyzkumalak.Situation) @@ -953,6 +958,7 @@ object Situation { def toGo = sys.error("Can't make go situation from togyzkumalak situation") def toBackgammon = sys.error("Can't make backgammon situation from togyzkumalak situation") def toAbalone = sys.error("Can't make abalone situation from togyzkumalak situation") + def toDameo = sys.error("Can't make dameo situation from togyzkumalak situation") } final case class Go(s: go.Situation) @@ -1108,6 +1114,7 @@ object Situation { def toGo = s def toBackgammon = sys.error("Can't make backgammon situation from go situation") def toAbalone = sys.error("Can't make abalone situation from go situation") + def toDameo = sys.error("Can't make dameo situation from go situation") } final case class Backgammon(s: backgammon.Situation) @@ -1273,6 +1280,7 @@ object Situation { def toGo = sys.error("Can't make go situation from backgammon situation") def toBackgammon = s def toAbalone = sys.error("Can't make abalone situation from backgammon situation") + def toDameo = sys.error("Can't make dameo situation from backgammon situation") } final case class Abalone(s: abalone.Situation) @@ -1423,6 +1431,158 @@ object Situation { def toGo = sys.error("Can't make go situation from abalone situation") def toBackgammon = sys.error("Can't make backgammon situation from abalone situation") def toAbalone = s + def toDameo = sys.error("Can't make dameo situation from abalone situation") + } + + final case class Dameo(s: dameo.Situation) + extends Situation( + Board.Dameo(s.board), + s.player + ) { + + lazy val moves: Map[Pos, List[Move]] = s.moves.map { case (p: dameo.Pos, l: List[dameo.Move]) => + (Pos.Dameo(p), l.map(Move.Dameo)) + } + + def takebackable = true + + def forcedAction: Option[Action] = None + + lazy val check: Boolean = false + + def checkSquare = None + + def opponentHasInsufficientMaterial: Boolean = s.opponentHasInsufficientMaterial + + def insufficientMaterialStatus: Status.type => Status = _.Outoftime + + def outOfTimeStatus: Status.type => Status = _.Outoftime + + def threefoldRepetition: Boolean = false + def isRepetition: Boolean = false + + override lazy val perpetualPossible: Boolean = false + + def end: Boolean = s.end + + def winner: Option[Player] = s.winner + + lazy val destinations: Map[Pos, List[Pos]] = s.destinations.map { + case (p: dameo.Pos, l: List[dameo.Pos]) => (Pos.Dameo(p), l.map(Pos.Dameo)) + } + + def drops: Option[List[Pos]] = None + + def dropsByRole: Option[Map[Role, List[Pos]]] = None + + def dropsAsDrops: List[Drop] = List.empty + + def lifts: List[Lift] = List.empty + + def passes: List[Pass] = List.empty + + def selectSquaresAction: List[SelectSquares] = List.empty + + def diceRolls: List[DiceRoll] = List.empty + + def undos: List[Undo] = List.empty + + def endTurns: List[EndTurn] = List.empty + + def cubeActions: List[CubeAction] = List.empty + + def canDrop: Boolean = false + + def canOnlyDrop: Boolean = false + + def canLift: Boolean = false + + def canOnlyLift: Boolean = false + + def canRollDice: Boolean = false + + def canOnlyRollDice: Boolean = false + + def canUndo: Boolean = false + + def canEndTurn: Boolean = false + + def canOnlyEndTurn: Boolean = false + + def canCubeAction: Boolean = false + + def canOnlyCubeAction: Boolean = false + + def drop(role: Role, pos: Pos): Validated[String, Drop] = + sys.error("Can't do a Drop for dameo") + + def lift(pos: Pos): Validated[String, Lift] = sys.error("Can't do a Lift for dameo") + + def pass: Validated[String, Pass] = sys.error("Can't do a Pass for dameo") + + def selectSquares(squares: List[Pos]): Validated[String, SelectSquares] = + sys.error("Can't do a SelectSquare for togyzkumalak") + + def diceRoll(dice: List[Int]): Validated[String, DiceRoll] = + sys.error("Can't do a DiceRoll for dameo") + + def undo: Validated[String, Undo] = sys.error("Can't do Undo for dameo") + + def endTurn: Validated[String, EndTurn] = sys.error("Can't do EndTurn for dameo") + + def cubeAction(interaction: CubeInteraction): Validated[String, CubeAction] = + sys.error("Can't do CubeAction for dameo") + + def playable(strict: Boolean): Boolean = s.playable(strict) + + val status: Option[Status] = s.status + + def resignStatus(player: Player): Status.type => Status = _.Resign + + def pointValue(player: Option[Player]): Option[Int] = None + + def move( + from: Pos, + to: Pos, + promotion: Option[PromotableRole] = None, + finalSquare: Boolean = false, + forbiddenUci: Option[List[String]] = None, + captures: Option[List[Pos]] = None, + partialCaptures: Boolean = false + ): Validated[String, Move] = (from, to) match { + case (Pos.Dameo(from), Pos.Dameo(to)) => + s.move(from, to).toEither.map(m => Move.Dameo(m)).toValidated + case _ => sys.error("Not passed Dameo objects") + } + + def move(uci: Uci.Move): Validated[String, Move] = uci match { + case Uci.DameoMove(uci) => s.move(uci).toEither.map(m => Move.Dameo(m)).toValidated + case _ => sys.error("Not passed Dameo objects") + } + + def withVariant(variant: Variant): Situation = variant match { + case Variant.Dameo(variant) => Dameo(s.withVariant(variant)) + case _ => sys.error("Not passed Dameo objects") + } + + def unary_! : Situation = Dameo(s.unary_!) + + def copy(board: Board): Situation = Dameo(board match { + case Board.Dameo(board) => s.copy(board) + case _ => sys.error("Can't copy a dameo situation with a non-dameo board") + }) + + def gameLogic: GameLogic = GameLogic.Dameo() + + def toFairySF = sys.error("Can't make fairysf situation from dameo situation") + def toChess = sys.error("Can't make chess situation from dameo situation") + def toDraughts = sys.error("Can't make draughts situation from dameo situation") + def toSamurai = sys.error("Can't make samurai situation from dameo situation") + def toTogyzkumalak = sys.error("Can't make togyzkumalak situation from dameo situation") + def toGo = sys.error("Can't make go situation from dameo situation") + def toBackgammon = sys.error("Can't make backgammon situation from dameo situation") + def toAbalone = sys.error("Can't make abalone situation from dameo situation") + def toDameo = s } def apply(lib: GameLogic, board: Board, player: Player): Situation = (lib, board) match { @@ -1435,6 +1595,7 @@ object Situation { case (GameLogic.Go(), Board.Go(board)) => Go(go.Situation(board, player)) case (GameLogic.Backgammon(), Board.Backgammon(board)) => Backgammon(backgammon.Situation(board, player)) case (GameLogic.Abalone(), Board.Abalone(board)) => Abalone(abalone.Situation(board, player)) + case (GameLogic.Dameo(), Board.Dameo(board)) => Dameo(dameo.Situation(board, player)) case _ => sys.error("Mismatched gamelogic types 3") } @@ -1450,6 +1611,8 @@ object Situation { Backgammon(backgammon.Situation.apply(variant)) case (GameLogic.Abalone(), Variant.Abalone(variant)) => Abalone(abalone.Situation.apply(variant)) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + Dameo(dameo.Situation.apply(variant)) case _ => sys.error("Mismatched gamelogic types 4") } @@ -1461,5 +1624,6 @@ object Situation { def wrap(s: go.Situation) = Go(s) def wrap(s: backgammon.Situation) = Backgammon(s) def wrap(s: abalone.Situation) = Abalone(s) + def wrap(s: dameo.Situation) = Dameo(s) } diff --git a/src/main/scala/Undo.scala b/src/main/scala/Undo.scala index ede023e66..09ebb96cb 100644 --- a/src/main/scala/Undo.scala +++ b/src/main/scala/Undo.scala @@ -46,6 +46,7 @@ object Undo { def toGo = sys.error("Can't make a go undo from a backgammon undo") def toBackgammon = u def toAbalone = sys.error("Can't make a abalone undo from a backgammon undo") + def toDameo = sys.error("Can't make a dameo undo from a backgammon undo") } diff --git a/src/main/scala/dameo/Action.scala b/src/main/scala/dameo/Action.scala new file mode 100644 index 000000000..fa8af9d7f --- /dev/null +++ b/src/main/scala/dameo/Action.scala @@ -0,0 +1,25 @@ +package strategygames.dameo +import strategygames.dameo.format.Uci +import strategygames.MoveMetrics +import strategygames.Player + +import scala.annotation.nowarn + +abstract class Action( + situationBefore: Situation, + @nowarn after: Board, + @nowarn metrics: MoveMetrics = MoveMetrics() +) { + + def before = situationBefore.board + + def situationAfter: Situation + def lazySituationAfter: Situation + def finalizeAfter: Board + + def player: Player + + def withMetrics(m: MoveMetrics): Action + def toUci: Uci + +} diff --git a/src/main/scala/dameo/Actor.scala b/src/main/scala/dameo/Actor.scala new file mode 100644 index 000000000..10d6c27e6 --- /dev/null +++ b/src/main/scala/dameo/Actor.scala @@ -0,0 +1,55 @@ +package strategygames.dameo + +import strategygames.Player + +import scala.annotation.nowarn + +// TODO Dameo - move generation probably wants to go in here +// You can change the functions here to whatever works best +// but I've stubbed out what the draughts gamelogic has +final case class Actor( + piece: Piece, + pos: Pos, + board: Board +) { + + lazy val noncaptures: List[Move] = noncaptureMoves() + lazy val captures: List[Move] = captureMoves(false) + lazy val capturesFinal: List[Move] = captureMoves(true) + + def getCaptures(finalSquare: Boolean) = if (finalSquare) capturesFinal else captures + + private def noncaptureMoves(): List[Move] = List() + + @nowarn private def captureMoves(finalSquare: Boolean): List[Move] = List() + + def player: Player = piece.player + def is(c: Player): Boolean = c == piece.player + def is(p: Piece): Boolean = p == piece + + def onLongDiagonal: Boolean = false + + @nowarn private def shortRangeMoves(dirs: Directions, checkPromotion: Boolean): List[Move] = List() + + @nowarn private def longRangeMoves(dirs: Directions): List[Move] = List() + + def move( + dest: Pos, + after: Board, + /* Set this to upgrade to multiaction */ + autoEndTurn: Boolean, + /* Single capture or none */ + capture: Option[Pos], + promotion: Option[PromotableRole] = None + ) = Move( + piece = piece, + orig = pos, + dest = dest, + situationBefore = Situation(board, piece.player), + after = after, + autoEndTurn = autoEndTurn, + capture = capture, + promotion = promotion + ) + +} diff --git a/src/main/scala/dameo/Board.scala b/src/main/scala/dameo/Board.scala new file mode 100644 index 000000000..ef64e50d0 --- /dev/null +++ b/src/main/scala/dameo/Board.scala @@ -0,0 +1,82 @@ +package strategygames.dameo + +import strategygames.Player + +import variant.Variant + +case class Board( + pieces: PieceMap, + history: History, + variant: Variant +) { + + def apply(at: Pos): Option[Piece] = pieces get at + def apply(file: File, rank: Rank) = pieces get Pos(file, rank) + + lazy val actors: Map[Pos, Actor] = pieces map { case (pos, piece) => + (pos, Actor(piece, pos, this)) + } + + lazy val actorsOf: Player.Map[Seq[Actor]] = { + val (w, b) = actors.values.toSeq.partition { + _.player.p1 + } + Player.Map(w, b) + } + + lazy val posMap: Map[Piece, Iterable[Pos]] = pieces.groupMap(_._2)(_._1) + + def withHistory(h: History): Board = copy(history = h) + def updateHistory(f: History => History) = copy(history = f(history)) + + def withVariant(v: Variant): Board = + if (v.dropsVariant) copy(variant = v) + else copy(variant = v) + + def situationOf(player: Player) = Situation(this, player) + + lazy val ghosts = pieces.values.count(_.isGhost) + + def autoDraw: Boolean = + ghosts == 0 && variant.maxDrawingMoves(this).fold(false)(m => history.halfMoveClock >= m) + + def valid(strict: Boolean) = variant.valid(this, strict) + + def materialImbalance: Int = variant.materialImbalance(this) + + override def toString = s"$variant Position after ${history.recentTurnUciString}" +} + +object Board { + + def apply(pieces: Iterable[(Pos, Piece)], variant: Variant): Board = + Board(pieces.toMap, History(), variant) + + def init(variant: Variant): Board = Board(variant.pieces, variant) + + sealed abstract class BoardSize( + val width: Int, + val height: Int + ) { + + val key = s"${width}x${height}" + val sizes = List(width, height) + + val validPos: List[Pos] = + Pos.all.filter(p => p.file.index < width && p.rank.index < height) + + override def toString = key + + } + + object BoardSize { + val all: List[BoardSize] = List(Dim8x8) + } + + case object Dim8x8 + extends BoardSize( + width = 8, + height = 8 + ) + +} diff --git a/src/main/scala/dameo/Divider.scala b/src/main/scala/dameo/Divider.scala new file mode 100644 index 000000000..1f0af93c7 --- /dev/null +++ b/src/main/scala/dameo/Divider.scala @@ -0,0 +1,11 @@ +package strategygames.dameo + +import strategygames.Division + +import scala.annotation.nowarn + +object Divider { + + def apply(@nowarn boards: List[Board]): Division = Division.empty + +} diff --git a/src/main/scala/dameo/File.scala b/src/main/scala/dameo/File.scala new file mode 100644 index 000000000..7db9cecdc --- /dev/null +++ b/src/main/scala/dameo/File.scala @@ -0,0 +1,42 @@ +package strategygames.dameo + +case class File private (val index: Int) extends AnyVal with Ordered[File] { + @inline def -(that: File): Int = index - that.index + @inline override def compare(that: File) = this - that + + def offset(delta: Int): Option[File] = + if (-File.all.size < delta && delta < File.all.size) File(index + delta) + else None + + @inline def char: Char = (97 + index).toChar + def sgfChar: Char = char + override def toString = char.toString + + @inline def upperCaseChar: Char = (65 + index).toChar + def toUpperCaseString = upperCaseChar.toString +} + +object File { + def apply(index: Int): Option[File] = + if (0 <= index && index < all.size) Some(new File(index)) + else None + + @inline def of(pos: Pos): File = new File(pos.index % all.size) + + def fromChar(ch: Char): Option[File] = apply(ch.toInt - 97) + + //leave as 10 files for now, this could be useful later + val A = new File(0) + val B = new File(1) + val C = new File(2) + val D = new File(3) + val E = new File(4) + val F = new File(5) + val G = new File(6) + val H = new File(7) + val I = new File(8) + val J = new File(9) + + val all = List(A, B, C, D, E, F, G, H, I, J) + val allReversed = all.reverse +} diff --git a/src/main/scala/dameo/Game.scala b/src/main/scala/dameo/Game.scala new file mode 100644 index 000000000..edadf5b5e --- /dev/null +++ b/src/main/scala/dameo/Game.scala @@ -0,0 +1,103 @@ +package strategygames.dameo +import strategygames.{ ClockBase, MoveMetrics, Player, VActionStrs } + +import cats.data.Validated + +import strategygames.dameo.format.{ FEN, Uci } +import strategygames.dameo.variant.Variant + +case class Game( + situation: Situation, + actionStrs: VActionStrs = Vector(), + clock: Option[ClockBase] = None, + plies: Int = 0, + turnCount: Int = 0, + startedAtPly: Int = 0, + startedAtTurn: Int = 0 +) { + + def apply( + orig: Pos, + dest: Pos, + metrics: MoveMetrics = MoveMetrics() + ): Validated[String, (Game, Move)] = + situation.move(orig, dest).map(_ withMetrics metrics) map { move => + apply(move) -> move + } + + def apply(move: Move): Game = { + val newSituation = move.situationAfter + val switchPlayer = situation.player != newSituation.player + + copy( + situation = newSituation, + plies = plies + 1, + turnCount = turnCount + (if (switchPlayer) 1 else 0), + actionStrs = applyActionStr(move.toUci.uci), + clock = applyClock(move.metrics, newSituation.status.isEmpty, switchPlayer) + ) + } + + def apply(uci: Uci.Move): Validated[String, (Game, Move)] = apply(uci.orig, uci.dest) + def apply(uci: Uci): Validated[String, (Game, Action)] = (uci match { + case u: Uci.Move => apply(u) + case u => sys.error(s"Cannot apply uci $u") + }) map { case (g, a) => g -> a } + + private def applyClock(metrics: MoveMetrics, gameActive: Boolean, switchClock: Boolean) = + clock.map { c => + { + val newC = c.step(metrics, gameActive, switchClock) + if (turnCount - startedAtTurn == 1 && switchClock) newC.start else newC + } + } + + private def applyActionStr(actionStr: String): VActionStrs = + if (hasJustSwitchedTurns || actionStrs.size == 0) + actionStrs :+ Vector(actionStr) + else + actionStrs.updated(actionStrs.size - 1, actionStrs(actionStrs.size - 1) :+ actionStr) + + def hasJustSwitchedTurns: Boolean = + player == Player.fromTurnCount(actionStrs.size + startedAtTurn) + + def player = situation.player + + def board = situation.board + + def halfMoveClock: Int = board.history.halfMoveClock + + // Aka Fullmove number (in Forsyth-Edwards Notation): + // The number of the completed turns by each player ('full move') + // It starts at 1, and is incremented after P2's move (turn) + def fullTurnCount: Int = 1 + turnCount / 2 + + def withTurnsAndPlies(p: Int, t: Int) = copy(plies = p, turnCount = t) + +} + +object Game { + + def apply(variant: Variant): Game = + new Game(Situation(Board init variant, P1)) + + def apply(variantOption: Option[Variant], fen: Option[FEN]): Game = { + val variant = variantOption | Variant.default + val g = apply(variant) + fen + .flatMap { + format.Forsyth.<<<@(variant, _) + } + .fold(g) { parsed => + g.copy( + situation = Situation( + board = parsed.situation.board withVariant g.board.variant, + player = parsed.situation.player + ), + plies = parsed.plies, + turnCount = parsed.turnCount + ) + } + } + +} diff --git a/src/main/scala/dameo/Hash.scala b/src/main/scala/dameo/Hash.scala new file mode 100644 index 000000000..160564ba4 --- /dev/null +++ b/src/main/scala/dameo/Hash.scala @@ -0,0 +1,1071 @@ +package strategygames.dameo + +final class Hash(size: Int) { + + def apply(situation: Situation): PositionHash = { + val l = Hash.get(situation, Hash.polyglotTable) + if (size <= 8) { + Array.tabulate(size)(i => (l >>> ((7 - i) * 8)).toByte) + } else { + val m = Hash.get(situation, Hash.randomTable) + Array.tabulate(size)(i => + if (i < 8) (l >>> ((7 - i) * 8)).toByte + else (m >>> ((15 - i) * 8)).toByte + ) + } + } +} + +object Hash { + + val size = 3 + + class ZobristConstants(start: Int) { + def hexToLong(s: String): Long = + (java.lang.Long.parseLong(s.substring(start, start + 8), 16) << 32) | + java.lang.Long.parseLong(s.substring(start + 8, start + 16), 16) + val p1TurnMask = hexToLong(ZobristTables.p1TurnMask) + val actorMasks = ZobristTables.actorMasks.map(hexToLong) + } + + object ZobristConstants {} + + // The following masks are compatible with the Polyglot opening book format. + private val polyglotTable = new ZobristConstants(0) + private lazy val randomTable = new ZobristConstants(16) + + private def pieceIndex(piece: Piece) = + piece.role.hashInt * 2 + piece.player.fold(1, 0) + + private def actorIndex(actor: Actor) = + Pos.all.size * pieceIndex(actor.piece) + actor.pos.hashCode + + def get(situation: Situation, table: ZobristConstants): Long = { + + val board = situation.board + val hturn = situation.player.fold(table.p1TurnMask, 0L) + + board.actors.values.view + .map { + table.actorMasks compose actorIndex _ + } + .fold(hturn)(_ ^ _) + + } + + private val h = new Hash(size) + + def apply(situation: Situation): PositionHash = h.apply(situation) + + def debug(hashes: PositionHash) = hashes.map(_.toInt).sum.toString + +} + +private object ZobristTables { + // to work out the size of this calculate what the max value actorIndex can produce + val actorMasks = Array( + "9d39247e33776d4152b375aa7c0d7bac", + "2af7398005aaa5c7208d169a534f2cf5", + "44db0150246235478981513722b47f24", + "9c15f73e62a76ae209b8f20f910a8ff7", + "75834465489c0c890b8ea70255209cc0", + "3290ac3a203001bfa688a9791f027500", + "0fbbad1f6104227919b88b8ffaed8f55", + "e83a908ff2fb60ca88bf7822d00d5526", + "0d7e765d58755c10db7bf62ab390b71b", + "1a083822ceafe02d33a6ac1d85c9f22f", + "9605d5f0e25ec3b055ab2a27271d42ac", + "d021ff5cd13a2ed540a21ff9c803fca4", + "40bdf15d4a672e323c169aeb80a1d5d2", + "011355146fd5639587684e27293ecf96", + "5db4832046f3d9e5f8b91b39d4c6997c", + "239f8b2d7ff719cc1d5f744f312fd467", + "05d1a1ae85b49aa1eda18c452d5de5b4", + "679f848f6e8fc9717497db888eccda0f", + "7449bbff801fed0b94c1bb7016749887", + "7d11cdb1c3b7adf035b23d663606fde2", + "82c7709e781eb7cc17b4ae80b8184845", + "f3218f1c9510786c8bd98922a3089d8e", + "331478f3af51bbe6fec77fb07cea5e84", + "4bb38de5e7219443e153a54d23c93a8a", + "aa649c6ebcfd50fca196fa76c24405eb", + "8dbd98a352afd40b6f333e11d079240a", + "87d2074b81d7921723b8d480df5bb521", + "19f3c751d3e92ae1634adaec002b3000", + "b4ab30f062b19abf3d0e41d65872d549", + "7b0500ac42047ac45aba83908462b892", + "c9452ca81a09d85d26457864aff288af", + "24aa6c514da2750043f10561015da64e", + "4c9f34427501b447545cc6285df42807", + "14a68fd73c910841a7140dc7b82e96ef", + "a71b9b83461cbd93b1dcadc8fe30a8d4", + "03488b95b0f1850f72ebd048ba373ac4", + "637b2b34ff93c0402eb0ddf1351a1adb", + "09d1bc9a3dd90a949cdc8c44a201836d", + "3575668334a1dd3b0afc1fb45a728973", + "735e2b97a4c45a2358c8fa415b96ec95", + "18727070f1bd400b497a9b9a7f9f8872", + "1fcbacd259bf02e7bff840799ee05fdf", + "d310a7c2ce9b6555e4ec1554316c2704", + "bf983fe0fe5d82443c9f0c8b89f31f3e", + "9f74d14f7454a8244a601b99475baf4e", + "51ebdc4ab9ba30356c65e1386536c3a9", + "5c82c505db9ab0fab60a571d59e8a485", + "fcf7fe8a3430b241e23c5d7045696d85", + "3253a729b9ba3ddec9d4d61b569ec607", + "8c74c368081b3075ce9ed71a6d18deb2", + "b9bc6c87167c33e72dbc16559bdba870", + "7ef48f2b83024e2050cda5339d836c83", + "11d505d4c351bd7f98091ef4f2ab1ed3", + "6568fca92c76a243f5803ac17fc45ecf", + "4de0b0f40f32a7b809730ef15a78c687", + "96d693460cc37e5df8bb209d715ab566", + "42e240cb63689f2f0c5b201d6cb89a50", + "6d2bdcdae291966152571fbfabb4a367", + "42880b0236e4d9511b1db82269890861", + "5f0f4a5898171bb69423f70ed512f1ea", + "39f890f579f92f8879e448c72183e2a5", + "93c5b5f47356388b3c88a0cf5b852900", + "63dc359d8d231b789e12f819acaa6653", + "ec16ca8aea98ad76c6f09266299a5902", + "5355f900c2a82dc73e8cad2210fce3f3", + "07fb9f855a997142a3868eff53346da1", + "5093417aa8a7ed5e61de5496186b0d70", + "7bcbc38da25a7f3ce17a09f0e53bc940", + "19fc8a768cf4b6d4e0ffe83afe44ec11", + "637a7780decfc0d9f35a5e3184c1c980", + "8249a47aee0e41f783390d9b2e7563a6", + "79ad695501e7d1e8950f14737ed6be5b", + "14acbaf4777d57766df42fcfa743809d", + "f145b6beccdea1950f2b1872ba3fef30", + "dabf2ac8201752fc04171b94f58c5d2e", + "24c3c94df9c8d3f678b05fea0dc77c38", + "bb6e2924f03912eaa76ddf41aa675504", + "0ce26c0b95c980d9e634bc8f87d0fe75", + "a49cd132bfbf7cc42dbf77a8851237de", + "e99d662af424393910f9b7d996836741", + "27e6ad7891165c3f26f6547bb0471fb0", + "8535f040b9744ff1f727c06db8bb34e0", + "54b3f4fa5f40d87328984171f866b615", + "72b12c32127fed2b349d245078c312ef", + "ee954d3c7b411f4799f8f1ab94a13206", + "9a85ac909a24eaa1967d7e5e99566e67", + "70ac4cd9f04f21f5470fda103f9476cc", + "f9b89d3e99a075c237dad4fcdedc6db8", + "87b3e2b2b5c907b199f91b1cd65c50f0", + "a366e5b8c54f48b86d89c29cb7034aef", + "ae4a9346cc3f7cf2824c9daa114a11c7", + "1920c04d47267bbdf3b1ee14505939c6", + "87bf02c6b49e2ae92fa0d39cbee05ced", + "092237ac237f3859c0c43ea8a642a49c", + "ff07f64ef8ed14d04824a871bca34e17", + "8de8dca9f03cc54e394b500f07f96989", + "9c1633264db49c897c6efb4dc9bea9d9", + "b3f22c3d0b0b38edca09213bfeb36c6a", + "390e5fb44d01144b0069832f9f2bd0b5", + "5bfea5b4712768e9f092a01d0d4420da", + "1e1032911fa789846952e2015db39c5a", + "9a74acb964e78cb3f3993e2a4cdf615e", + "4f80f7a035dafb047d3ddc6bef2ed6f2", + "6304d09a0b3738c4a7040db38e233bac", + "2171e64683023a08a8b59fe4836ffc08", + "5b9b63eb9ceff80cc55dbb54360414a9", + "506aacf4898893423e24465359dc03c0", + "1881afc9a3a701d6d27a416ed84cc3b7", + "65030804407506447a77677e0de620c4", + "dfd395339cdbf4a730cacd32e0313d3b", + "ef927dbcf00c20f2dc6952fd3e61c11a", + "7b32f7d1e03680ec08ab1642b5129e01", + "b9fd7620e7316243aa8e0962b8eebcdc", + "05a7e8a57db91b776dda36bacc3b2e2c", + "b5889c6e15630a75ba82f4e2cb60b43e", + "4a750a09ce9573f7509da4ba5295c4a5", + "cf464cec899a2f8a36a18fa38c3d74c6", + "f538639ce705b824b9e5652481a6df69", + "3c79a0ff5580ef7f5f4946b7dd41d1c7", + "ede6c87f8477609dfd7a4fb7bfe1d23d", + "799e81f05bc93f3132e0b2b68ea83031", + "86536b8cf3428a8cf25fcb24f0c19623", + "97d7374c60087b73a317676dc1eb8797", + "a246637cff328532f754b557c09ae146", + "043fcae60cc0eba05bbd920fffe5fa7a", + "920e449535dd359e5b3c978e296d2280", + "70eb093b15b290ccca9c1ea34fc1484f", + "73a1921916591cbd470c408e3b3d2dc5", + "56436c9fe1a1aa8d6a0772093f97e152", + "efac4b70633b8f81fa76d36719e7e5e3", + "bb215798d45df7af2e799233a544062a", + "45f20042f24f1768e003451144a03be8", + "930f80f4e8eb7462974d8f4ee692ed35", + "ff6712ffcfd75ea1d30afadfc4dc52f5", + "ae623fd67468aa70278dde02bf30c1da", + "dd2c5bc84bc8d8fc8b7e3a2bf5a061a3", + "7eed120d54cf2dd9d1443752e511a579", + "22fe545401165f1cd1b02672a0ec44cf", + "c91800e98fb99929ba3005c8512514f1", + "808bd68e6ac103652e3b86211f6b4295", + "dec468145b7605f6057431bfeaa9d6f5", + "1bede3a3aef53302a348ddd66378afaf", + "43539603d6c556024a1817775b086ce1", + "aa969b5c691ccb7a184b1c983a6a1a77", + "a87832d392efee56cb70392e7b7db185", + "65942c7b3c7e11ae3b1b1166a648330e", + "ded2d633cad004f6e5201b155f51cc30", + "21f08570f420e5654d053ee865f21b96", + "b415938d7da94e3cd8d8062e343d9c66", + "91b859e59ecb635013507b31a966da7d", + "10cff333e0ed804ad637536d2e7a58b0", + "28aed140be0bb7ddcbcae035eab824a0", + "c5cc1d89724fa4568c77fe1e1b06691b", + "5648f680f11a2741fd7841ed1ab4b961", + "2d255069f0b7dab3caa88da017669d53", + "9bc5a38ef729abd4abda5baf650b3675", + "ef2f054308f6a2bcfe14d0c6b8f11e97", + "af2042f5cc5c2858c13423f81c4b9adf", + "480412bab7f5be2a34507a3503935243", + "aef3af4a563dfe43ec504bd0c7ae79a1", + "19afe59ae451497f0bc761ea4004d2ae", + "52593803dff1e8403a0748078a78fd4d", + "f4f076e65f2ce6f0c5f36bde8caa93fe", + "11379625747d5af35b2299dc44080278", + "bce5d2248682c1153f99cbeb6ec653fa", + "9da4243de836994f48ebcfc004b524ca", + "066f70b33fe09017d2278829cd344d05", + "4dc4de189b671a1c5fe637e58fc1c0f3", + "51039ab7712457c30a4b136f25a65a32", + "c07a3f80c31fb4b44119314b520d04d9", + "b46ee9c5e64a6e7c5354a8b08947cc8e", + "b3819a42abe61c876001d6a94517300b", + "21a007933a522a2014597a074f133855", + "2df16f761598aa4fdc9a6baf92ffde03", + "763c4a1371b368fdc5cbc5270de986b0", + "f793c46702e086a095c72d49bd7560be", + "d7288e012aeb8d3112b437e4c286737a", + "de336a2a4bc1c44baa7c6f89f1442c5d", + "0bf692b38d079f231a3ebbf317bfc4d8", + "2c604a7a177326b34ad3c9fa863a5aa3", + "4850e73e03eb6064c7c94147de663b5b", + "cfc447f1e53c8e1b840e7fe4b35d4a4b", + "b05ca3f564268d998921109126e23341", + "9ae182c8bc9474e8a18a4f12c127de17", + "a4fc4bd4fc5558ca471973e4dc6efb4b", + "e755178d58fc4e76c723867b98d07330", + "69b97db1a4c03dfef5fcc6de350950d1", + "f9b5b7c4acc67c96b90913e02bde576a", + "fc6a82d64b8655fb5554c92f272b73c5", + "9c684cb6c4d244173738a3f0fdf5d9c6", + "8ec97d2917456ed03771f25e5e278ee3", + "6703df9d2924e97ea9d58a812b10906e", + "c547f57e42a7444e2814f2a19d8670eb", + "78e37644e7cad29e5ced6d617e9d6b4d", + "fe9a44e9362f05fa69be27bdc682e06a", + "08bd35cc38336615945e3cd54c7a41f4", + "9315e5eb3a129acefac825c29c4e52fc", + "94061b871e04df7595c380633671f3c0", + "df1d9f9d784ba010b1f0f11b309f849f", + "3bba57b68871b59d36b7ac17862bc4ac", + "d2b7adeeded1f73f8b89835b5e731ac5", + "f7a255d83bc373f8122138b676fc6561", + "d7f4f2448c0ceb81ce3107b858b368ea", + "d95be88cd210ffa7aa14dd3733de4203", + "336f52f8ff4728e7ec4ea6805a8dad1e", + "a74049dac312ac71ce5cd5938049dcf0", + "a2f61bb6e437fdb521156227c06a4b0b", + "4f2a5cb07f6a35b3da13d541802c4d5e", + "87d380bda5bf7859fbf03d5c9f783bb2", + "16b9f7e06c453a21e512fa9fd5c68b8a", + "7ba2484c8a0fd54e30bd781b22277dcd", + "f3a678cad9a2e38c56625e22aef316ec", + "39b0bf7dde437ba2ed75251a71024db6", + "fcaf55c1bf8a4424b468dc45b39cde2f", + "18fcf680573fa59468b33836bea9a0a0", + "4c0563b89f495ac33187565f03cc0d85", + "40e087931a00930d9bbc591ddc43447f", + "8cffa9412eb642c1c53a29458191d2db", + "68ca39053261169f6bc263803d691ec8", + "7a1ee967d27579e204cca68628858bac", + "9d1d60e5076f5b6fa20a13cffa4679d1", + "3810e399b6f65ba285725a1e096e1abf", + "32095b6d4ab5f9b1bc986393043f78d5", + "35cab62109dd038a0fa47125507ccb12", + "a90b24499fcfafb1c2c27b60c8b2ce36", + "77a225a07cc2c6bd217520a809c97da6", + "513e5e634c70e331552ad48c96617c16", + "4361c0ca3f692f12758c0637401144ae", + "d941aca44b20a45bf1ae50d591aeb10f", + "528f7c8602c5807b0c127280b89240a3", + "52ab92beb9613989a9a8cd5ddd7737b0", + "9d1dfa2efc557f738506683f3e28c050", + "722ff175f572c3488105b0573483941f", + "1d1260a51107fe97d00bcf6974e8788c", + "7a249a57ec0c9ba23311a2a4e61fc638", + "04208fe9e8f7f2d65b31cba035ff4f50", + "5a110c6058b920a09ef049141a01e743", + "0cd9a497658a56983355b7a63e03cf20", + "56fd23c8f9715a4c78bf716c2f94ffcf", + "284c847b9d887aae232304d6a359676e", + "04feabfbbdb619cbffeebdd04f15816e", + "742e1e651c60ba83594fdc90c434a4fd", + "9a9632e65904ad3cba5cc088b72c0942", + "881b82a13b51b9e2036efdc30e389de2", + "506e6744cd9749245038b23c9af174d2", + "b0183db56ffc6a799b5e64f304474d48", + "0ed9b915c66ed37e280a4b8c73c2e8d8", + "5e11e86d5873d484fda076be88bcc507", + "f678647e3519ac6eafc896ae852c60c2", + "1b85d488d0f20cc5be903340939e63fd", + "dab9fe6525d890217a97bd60aba4c349", + "0d151d86adb73615f62e51f178597cf9", + "a865a54edcc0f0198f9ab42711b663dc", + "93c42566aef98ffbc8d003d119dcac63", + "99e7afeabe000731ef2101c32adaed38", + "48cbff086ddf285adde81906502ad1b0", + "7f9b6af1ebf78baf149756bc21368632", + "58627e1a149bba2125c80f323a516eaa", + "2cd16e2abd791e333ea039f7ff28ae8e", + "d363eff5f09779960caf481f40063dd8", + "0ce2a38c344a6eedbce23e106b1eefd7", + "1a804aadb9cfa74110853ea82a5ccb34", + "907f30421d78c5dee7c76ac3dbbf8c8c", + "501f65edb3034d071624c0ce1532313d", + "37624ae5a48fa6e95f3895b25d7b4744", + "957baf61700cff4efbe363cbb55a913e", + "3a6c27934e31188a35850e8f63400ddd", + "d49503536abca3453d300047b5ddde66", + "088e049589c432e01c1c7ca8b3386353", + "f943aee7febf21b8986ec52ac2c88cec", + "6c3b8e3e336139d3c93b616a554d23c8", + "364f6ffa464ee52e211d7b5759da7504", + "d60f6dcedc314222f2663fc59b541585", + "56963b0dca418fc0f57fefeadb21b029", + "16f50edf91e513af30fd60d9ee260966", + "ef1955914b609f933c29da000d5b9a08", + "565601c0364e3228d0d6203fa69da0ba", + "ecb53939887e81758167e4bd87c6f05e", + "bac7a9a18531294b5c063405c62f8154", + "b344c470397bba52b86fe57d53081fe6", + "65d34954daf3cebdeb60ad080cd573fe", + "b4b81b3fa97511e2bfbbb41602635b78", + "b422061193d6f6a74e39d536b723213c", + "071582401c38434d56d7e0468df15a47", + "7a13f18bbedc4ff59e601537348ed732", + "bc4097b116c524d2ee2e827d9faa74c1", + "59b97885e2f2ea28e43302e0d517a316", + "99170a5dc3115544f662e9ba781a1fae", + "6f423357e7c6a9f9e83da2efce442856", + "325928ee6e6f8794143ae097749a513a", + "d0e4366228b03343a203386e6a86f7c7", + "565c31f7de89ea2773905f8c5056ecee", + "30f56114841194140da07ce44c0142e4", + "d873db391292ed4f8b9e97003ef01d2e", + "7bd94e1d8e17debcd8c666a665840842", + "c7d9f16864a76e948bb1069bba169263", + "947ae053ee56e63c6bdc866d7daa19dc", + "c8c93882f9475f5fe1115bb3f8ad0cfe", + "3a9bf55ba91f81ca0859ae34a51ed77c", + "d9a11fbb3d9808e4f1d73663c53a0156", + "0fd22063edc29fca669283df212c93db", + "b3f256d8aca0b0b97489abc08bd4db15", + "b03031a8b4516e84f9d2b26d0375aab0", + "35dd37d5871448afde4856e7777e27d1", + "e9f6082b05542e4e2caeaf61386fa1f2", + "ebfafa33d7254b597c6d4b00383f052a", + "9255abb50d532280b1943df6ea3687ff", + "b9ab4ce57f2d34f37e4d1baca94da20d", + "693501d62829755138d1a6b6448fdc40", + "c62c58f97dd949bf8aed53051756d212", + "cd454f8f19c5126a1805fa7482c60f4e", + "bbe83f4ecc2bdecb24c9891c2f4db0b1", + "dc842b7e2819e230bb703190b30eb664", + "ba89142e007503b8b52f857f41e68fce", + "a3bc941d0a5061cbeb5a7a714d4ec1a1", + "e9f6760e32cd8021c41334a36d4211ea", + "09c7e552bc76492fe60188ecb537010d", + "852f54934da55cc9ff67dd932e5755cc", + "8107fccf064fcf5614c92be552467cfb", + "098954d51fff6580c94fb8eae42b3453", + "23b70edb1955c4bf0bd007042735acc6", + "c330de426430f69db9dd82debb9abd3d", + "4715ed43e8a45c0a3312b675a5bc5dfb", + "a8d7e4dab780a08dd2e9447e6c6d3509", + "0572b974f03ce0bbd4d771a89c91beb6", + "b57d2e985e1419c731d0724183248884", + "e8d9ecbe2cf3d73fe7229acdb568bc00", + "2fe4b17170e59750b4ef94e8251ecb66", + "11317ba87905e790ac817cad3cfb00ed", + "7fbf21ec8a1f45ec3fe869890b69552c", + "1725cabfcb045b0091aec362daa37fcd", + "964e915cd5e2b207fb25f88ca887ca77", + "3e2b8bcbf016d66d053bff886db0847c", + "be7444e39328a0ac991fd5641b666e80", + "f85b2b4fbcde44b724fc37ad820ed73a", + "49353fea39ba63b12b2bcac1fa28086e", + "1dd01aafcd53486a4c1a3a2190e08f26", + "1fca8a92fd719f858e718591cf07851e", + "fc7c95d827357afab14fadbd3baa703f", + "18a6a990c8b35ebd8f1eb8cc7eedd98e", + "cccb7005c6b9c28d89ed662f01bcb2fd", + "3bdbb92c43b17f26a263fa3b9a325a8f", + "aa70b5b4f89695a2bae9f4e14d09637c", + "e94c39a54a98307f6be076b52945a007", + "b7a0b174cff6f36ed264830e7dc7f906", + "d4dba84729af48adc26059b78ed6854f", + "2e18bc1ad9704a68148dca9a9b0c8474", + "2de0966daf2f8b1c9749c69073bafeb8", + "b9c11d5b1e43a07ebba6f4662b0cfd3c", + "64972d68dee33360620103f01e5b63f8", + "94628d38d0c205844c7820f950a4c583", + "dbc0d2b6ab90a559e1262fa8ff1d3269", + "d2733c4335c6a72f8f5121c2873029ef", + "7e75d99d94a70f4d4fb3edb54d507b36", + "6ced1983376fa72bf8594c470632ebb6", + "97fcaacbf030bc24b6e876e78ecf5164", + "7b77497b32503b12afeb0a5d807150f5", + "8547eddfb81ccb94f651bea4c88fcaae", + "79999cdff70902cbbfbce123f03177da", + "cffe1939438e9b24b6aa0fd22855e81c", + "829626e3892d95d7a240adf54d70b24e", + "92fae24291f2b3f1732ea6db834bf5a4", + "63e22c147b9c3403b47231e07ae8b35f", + "c678b6d860284a1c80554c039ab7af15", + "5873888850659ae7db2e83297a30b541", + "0981dcd296a8736dd58b2a396b5a1669", + "9f65789a6509a4402151156aaffdf4b7", + "9ff38fed72e9052f65479a629704845e", + "e479ee5b9930578ca9bed81039cf1c6d", + "e7f28ecd2d49eecdd1a3f98b97eea710", + "56c074a581ea17feff78a5d72aa18fe3", + "5544f7d774b14aef96d1a9830ba7ffd3", + "7b3f0195fc6f290fe19f501dcdf116db", + "12153635b2c0cf578e68f253a278535d", + "7f5126dbba5e0ca7dc75c481b2cdc0fa", + "7a76956c3eafb4137162fa408d9042fd", + "3d5774a11d31ab399fadef0bce1a7da1", + "8a1b083821f40cb45421bbff426d4e84", + "7b4a38e32537df62e7944beeb699c0e7", + "950113646d1d6e032ed8b2db03799071", + "4da8979a0041e8a9fc4e188df10d5454", + "3bc36e078f7515d79de7df26c1457c8f", + "5d0a12f27ad310d18fd73517b47f22c8", + "7f9d1a2e1ebe1327a3005e22b1bd3e53", + "da3a361b1c5157b12bb2c23c899cbc9e", + "dcdd7d20903d0c25bfb0766d26526dbf", + "36833336d068f707166dbdc38309e26b", + "ce68341f798933896796f7e11a4c3cba", + "ab9090168dd05f34562c093e14a87ff2", + "43954b3252dc25e53627e637e2413092", + "b438c2b67f98e5e9240414702e63067a", + "10dcd78e3851a492a6f63494f774323c", + "dbc27ab5447822bfc19ea801fb344afb", + "9b3cdb65f82ca3829d18e8c21a6c8c61", + "b67b7896167b4c8487f9503f9fded941", + "bfced1b0048eac503e5098f627c4ed6c", + "a9119b60369ffebd6eb063443f58c2ae", + "1fff7ac80904bf453427200616f65462", + "ac12fb171817eee7e3b74fe55c76691f", + "af08da9177dda93dd6316d5008f932ec", + "1b0cab936e65c74435a0d4ca7416842f", + "b559eb1d04e5e932de4683286c56072d", + "c37b45b3f8d6f2ba158754657dc0f21d", + "c3a9dc228caac9e96d808b472208eab2", + "f3b8b6675a6507ffcb208f7c937f44e6", + "9fc477de4ed681dadff1dc38532c3a2e", + "67378d8eccef96cb64ee6958558a5d83", + "6dd856d94d259236f103b408d1245a6d", + "a319ce15b0b4db31a1f19c518c3e0b41", + "073973751f12dd5e48f0c2ff42819bfd", + "8a8e849eb32781a51472ba925a4c6123", + "e1925c71285279f5e3b750660989609a", + "74c04bf1790c0efe6dd760ab49ed7373", + "4dda48153c94938a67926c593a78bcaa", + "9d266d6a1cc0542c5978ff009a18007c", + "7440fb816508c4fe5568e6bf328b448e", + "13328503df48229f7cc90fbed1165f2b", + "d6bf7baee43cac40156f28d728f97cba", + "4838d65f6ef6748f2edf603ec74b4900", + "1e152328f3318dead48f299033fa9c9a", + "8f8419a348f296bf543751766a18d326", + "72c8834a5957b51104f25bdd180f31cb", + "d7a023a73260b45cc66a3569f903b0dc", + "94ebc8abcfb56daece61b42c7eead35c", + "9fc10d0f989993e0a705d68144caaf00", + "de68a2355b93cae63371ede2968498fb", + "a44cfe79ae538bbe1c0c9a220f8fbf8a", + "9d1d84fcce3714256631fc26158faebd", + "51d2b1ab2ddfb636ef0ec337ff2aef59", + "2fd7e4b9e72cd38cc979ef8243e71d7a", + "65ca5b96b7552210d6c1a70601c91112", + "dd69a0d8ab3b546df3a1da1866141057", + "604d51b25fbf70e2d3e2b4c698f2a99e", + "73aa8a564fb7ac9e1b4f5d5760ac5121", + "1a8c1e992b94114827d0b28f28e7d0ef", + "aac40a2703d9bea0f3a2d309d1e72bc0", + "764dbeae7fa4f3a61294c73b3f914dda", + "1e99b96e70a9be8b56dab15dc8b3fc48", + "2c5e9deb57ef47435b77d3202095d45c", + "3a938fee32d29981e0d317e26a17ae47", + "26e6db8ffdf5adfe5b1d671e17069897", + "469356c504ec9f9d937dc438ef99030b", + "c8763c5b08d1908cce09ea57087ebea9", + "3f6c6af859d80055d0d8fac3d4cfa048", + "7f7cc39420a3a54527a5886862d56b94", + "9bfb227ebdf4c5ce591673661c00d80b", + "89039d79d6fc5c5cd1cc44ae71a9791d", + "8fe88b57305e2ab6df5a715ada209d36", + "a09e8c8c35ab96de491171944e677dae", + "fa7e393983325753d0cf92367e04163c", + "d6b6d0ecc617c69967b6a5b051ce8d5c", + "dfea21ea9e7557e388d41953044d621e", + "b67c1fa481680af8b93bcd9dd369fe49", + "ca1e3785a9e724e579999db57b235430", + "1cfc8bed0d68163920bad52dd13a90d4", + "d18d8549d140caea4cccc5ae16a29dd2", + "4ed0fe7e9dc91335a0615c69803b77c7", + "e4dbf0634473f5d22e2cb0938ba801a0", + "1761f93a44d5aefe7ba91914cab50528", + "53898e4c3910da552738873108dcba8a", + "734de8181f6ec39a4502a97899131230", + "2680b122baa28d9738108697800e8bb5", + "298af231c85bafaba46ede145d66a90b", + "7983eed3740847d5e99d73feedfd98b3", + "66c1a2a1a60cd8893267a96bed604a38", + "9e17e49642a3e4c1ad66f2c81cc3fc42", + "edb454e7badc0805fa2de5ba2d8c3693", + "50b704cab602c329d4ca1c86d116bcbd", + "4cc317fb9cddd023525f7774ee1dde6b", + "66b4835d9eafea22a8e346682e2883b9", + "219b97e26ffc81bdae03d84b90df4cb2", + "261e4e4c0a333a9dd12735c3d08e24d9", + "1fe2cca76517db90b737b467cee71d3a", + "d7504dfa8816edbb36970fe334b2a37e", + "b9571fa04dc089c8d5c73db7872be26f", + "1ddc0325259b27de8aeb0ed56a4177fa", + "cf3f4688801eb9aa0199f29dbf7a1802", + "f4f5d05c10cab2431caba957a1ff78f0", + "38b6525c21a42b0e2abfd2ecbf62492e", + "36f60e2ba4fa6800add370c3cd316a3e", + "eb3593803173e0ce08a307d218ffbcbf", + "9c4cd6257c5a36037bf02813994b261f", + "af0c317d32adaa8a894290366274ef43", + "258e5a80c7204c4bc0821c96582294b4", + "8b889d624d44885dbd2ce07a63da7db1", + "f4d14597e660f85503ba38df61dba3a6", + "d4347f66ec8941c34bc8ce1e706bb08d", + "e699ed85b0dfb40d7dca263ea9024a3c", + "2472f6207c2d04844e876b140c9cda33", + "c2a1e7b5b459aeb55bdabc35a2fa1b4e", + "ab4f6451cc1d45ec383a46ece18c27c4", + "63767572ae3d6174532ee826c31a9b81", + "a59e0bd101731a2884f3d8faecfd7924", + "116d0016cb948f09ba905cc371f066d9", + "2cf9c8ca052f6e9f3da882318279b416", + "0b090a7560a968e3b3566f6dccae32ed", + "abeeddb2dde06ff16eb13ec5ca4cb179", + "58efc10b06a2068dab38266303f672c0", + "c6e57a78fbd986e0c5e23f8698084689", + "2eab8ca63ce802d7502fe8a1ce0a1bc6", + "14a195640116f3364664fe5154093ec2", + "7c0828dd624ec3903e39d0fefb4ffaf8", + "d74bbe77e6116ac74162ffb2b58aed88", + "804456af10f5fb535e1e505c7c916883", + "ebe9ea2adf4321c72185b1f34d275173", + "03219a39ee587a30db898d0487271365", + "49787fef17af9924ddb7e20f4f0b0a5f", + "a1e9300cd8520548e39ecdbb80830b57", + "5b45e522e4b1b4efd1e12d09eb3d6c76", + "b49c3b3995091a36f33de05912951acf", + "d4490ad526f144317ddca4e7cf6a2022", + "12a8f216af9418c237dae73934d2b45c", + "001f837cc7350524ddf7d6c08847b906", + "1877b51e57a764d576a4f4c4bd5b4dc4", + "a2853b80f17f58eede916ef3cda04b7a", + "993e1de72d36d310f79a5f06f0548dcf", + "b3598080ce64a6567980b63a972639bc", + "252f59cf0d9f04bbc327c42efcd3dcb0", + "d23c8e176d1136008ae99f5a13771265", + "1bda0492e7e4586eaeb96c60887bf568", + "21e0bd5026c619bf0262cf9cfb1be6f7", + "3b097adaf088f94e96f28db3af9f8eaf", + "8d14dedb30be846ebd74fc2e2cd130ed", + "f95cffa23af5f6f4bfb40b3bf2130455", + "3871700761b3f7430d27390428f909cc", + "ca672b91e9e4fa16e64118d41047bff4", + "64c8e531bff53b559f175874ee74dfc0", + "241260ed4ad1e87d3c41278759da9b49", + "106c09b972d2e822ce243a587f0b6bbd", + "7fba195410e5ca3019790b6cab0ef9bc", + "7884d9bc6cb569d81b7bb956b20e0ff1", + "0647dfedcd894a29fcd55d2a3d92556c", + "63573ff03e2247744f6a491571d2a627", + "4fc8e9560f91b1232a4bd439b9085684", + "1db956e450275779b8eb5b22d497aa9d", + "b8d91274b9e9d4fbf6cb137e88679a76", + "a2ebee47e2fbfce122bc990578e7dd2c", + "d9f1f30ccd97fb0901fc46a5af41ba72", + "efed53d75fd64e6b9ee540b9ce7e922a", + "2e6d02c36017f67f5a823d7da29de33e", + "a9aa4d20db084e9bdcb54247ed750238", + "b64be8d8b25396c1bbe346044327e21a", + "70cb6af7c2d5bcf03905a0daabfe04e3", + "98f076a4f7a2322e1670290ab9118147", + "bf84470805e69b5f68163cb777eab10d", + "94c3251f06f90cf3ed240589e171a9a1", + "3e003e616a6591e92281ad67174bb66b", + "b925a6cd0421aff3579ed522cb20efa0", + "61bdd1307c66e3009c319f6b6fd2554a", + "bf8d5108e27e0d480dd4faaafa80e520", + "240ab57a8b888b2074bbae7f87e860f9", + "fc87614baf287e07dff318b4d732a759", + "ef02cdd06ffdb432c371ee9d7d2af132", + "a1082c0466df6c0a76cc4be2658dda80", + "8215e577001332c8fd9e506d75af7df5", + "d39bb9c3a48db6cfc5a0d6e02e703f74", + "2738259634305c14631701e42e35f5cf", + "61cf4f94c97df93d3055402095f743c0", + "1b6baca2ae4e125b37e89e5deff34268", + "758f450c88572e0bcc01cd9d386a49cb", + "959f587d507a835904a328251bd5828f", + "b063e962e045f54dc8dc0ff4362a8c8f", + "60e8ed72c0dff5d1ee814f9b3bce7af1", + "7b64978555326f9f57ae92f6b7f094ef", + "fd080d236da814ba0dbc0ba7bb1c2445", + "8c90fd9b083f4558ec57a417b3ae8ad1", + "106f72fe81e2c59008d8ec9d3a3a3a85", + "7976033a39f7d95218a2e3daa4c6bbe3", + "a4ec0132764ca04b68f8161c35388cf9", + "733ea705fae4fa77e6e7cc9733d7aa2f", + "b4d8f77bc3e56167206c16a493a194f6", + "9e21f4f903b33fd9221153dec7e37554", + "9d765e419fb69f6dd110651aa3d83db7", + "d30c088ba61ea5ef841c207674a2a59d", + "5d94337fbfaf7f5bc09b4da25303cd0e", + "1a4e4822eb4d7a59e48fec76c3d485e3", + "6ffe73e81b637fb34c37f28895f6b9e0", + "ddf957bc36d8b9ca97fe8d6e8425ea84", + "64d0e29eea8838b3e13176251539f9db", + "08dd9bdfd96b9f634e887953d43713f5", + "087e79e5a57d1d1332dc49e0f8b96e5c", + "e328e230e3e2b3fb1240f0315707df84", + "1c2559e30f0946bee22f60c878f03163", + "720bf5f26f4d2eaa733ede4131a1662d", + "b0774d261cc609db5c82e1f58657d112", + "443f64ec5a3711956b39b17300529a95", + "4112cf68649a260e9a366e73434c37a3", + "d813f2fab7f5c5ca3af04822ac63ca77", + "660d3257380841ee6c20030dcbb3e153", + "59ac2c7873f910a3812bc00a41d9de43", + "e846963877671a179d698757f729c30b", + "93b633abfa3469f8764407e6839c724e", + "c0c0f5a60ef4cdcf347b8bcf7aa8a816", + "caf21ecd4377b28ca15cbc1e73577012", + "57277707199b81758387a4b08cc28a2e", + "506c11b9d90e8b1dce827a97fe830269", + "d83cc2687a19255ffc3677ad21589c7a", + "4a29c6465a314cd1c902d33e0a464ec1", + "ed2df21216235097fb39294699a1e2aa", + "b5635c95ff7296e2d7bf168e258f8f01", + "22af003ab672e8112e602229d2b37a22", + "52e762596bf68235821bc37e2c1f8912", + "9aeba33ac6ecc6b0c57a1ba0f260acc5", + "944f6de09134dfb6ce78cb667346e38c", + "6c47bec883a7de39bb39eae7a290fcb0", + "6ad047c430a121041bbc42a45ca20618", + "a5b1cfdba0ab4067ef37e286f590dcfb", + "7c45d833aff07862d9708ed7ec641deb", + "5092ef950a16da0b12c5e2f484724605", + "9338e69c052b8e7bf3e85d7b8a77b033", + "455a4b4cfe30e3f58ee17c0021438904", + "6b02e63195ad0cf8462f7fba7c2868f2", + "6b17b224bad6bf27fb7a14d6137fa5b1", + "d1e0ccd25bb9c16999ceec99465fe08c", + "de0c89a556b9ae70a4be0f0017879351", + "50065e535a213cf648c9c74efaeff5a7", + "9c1169fa2777b874682f784ba23dc02e", + "78edefd694af1eed226e96a540113353", + "6dc93d9526a50e68aa04b725850e2e32", + "ee97f453f06791edbc3338f53303ff19", + "32ab0edb696703d394d80fc8e2559d54", + "3a6853c7e70757a7b470b50e6769b2e4", + "31865ced6120f37d9d5caf79d12f737c", + "67fef95d926078900335ce6790cce15a", + "1f2b1d1f15f6dc9ca9d88c05289cbd6d", + "b69e38a8965c6b65f3d1d29b153dbd5e", + "aa9119ff184cccf44104764512849057", + "f43c732873f24c131d6d02af20051f53", + "fb4a3d794a9a80d2c8e487152b256129", + "3550c2321fd6109caf60d64fbda8f2ce", + "371f77e76bb8417ebf6d091a05417402", + "6bfa9aae5ec05779da184d8ff54f3fc1", + "cd04f3ff001a4778b83657ca6633e2ac", + "e3273522064480ca75d3e0b8feedbdb7", + "9f91508bffcfc14a26a58648fd56deab", + "049a7f41061a9e605da2dbf10323e25c", + "fcb6be43a9f2fe9b1e3ed1a390724f66", + "08de8a1c7797da9bc76159619f3af003", + "8f9887e6078735a1b4af22b5f5b26389", + "b5b4071dbfc73a66466792d7a2e6c6fa", + "230e343dfba08d3366feaacd911b81a2", + "43ed7f5a0fae657d94c1e6508f5f1f47", + "3a88a0fbbcb05c634206c6c80ef8fd9f", + "21874b8b4d2dbc4f3c1dea6d0afcae52", + "1bdea12e35f6a8c9e79cf8044680e415", + "53c065c6c8e6352848676fcc85844eb5", + "e34a1d250e7a8d6b62f20ecb301144ad", + "d6b04d3b7651dd7e2778709cb5ff3fc7", + "5e90277e7cb39e2dd87c68010650c250", + "2c046f22062dc67dbe61c9fc8adc7260", + "b10bb459132d0a26eaec3f6695236d35", + "3fa9ddfb67e2f199d47e91679b5bbefc", + "0e09b88e1914f7af195693617db7534c", + "10e8b35af3eeab372725134b52fd2c81", + "9eedeca8e272b93322770ffa079a1704", + "d4c718bc4ae8ae5f012c2a69dda5ad22", + "81536d601170fc2009338c04f427a66f", + "91b534f885818a060d85fd7519aa9dec", + "ec8177f83f9009780246a0f9fd642861", + "190e714fada5156eac615f38f5a451ea", + "b592bf39b036496307a44f6430336f1c", + "89c350c893ae7dc12184303422b53201", + "ac042e70f8b383f26e47c77585ab8164", + "b49b52e587a1ee6055d75131c650586c", + "fb152fe3ff26da89420514ac928637fa", + "3e666e6f69ae2c15bb00290a9289ce13", + "3b544ebe544c19f9321f8022ccc2553a", + "e805a1e290cf24567effcb24d14c9d18", + "24b33c9d7ed25117dc418c09511a5174", + "e74733427b72f0c11130c8b2334d05c7", + "0a804d18b709747557f10554d8e9323b", + "57e3306d881edb4f90a5ce5a89ea0b56", + "4ae7d6a36eb5dbcbc27327f936e68d1b", + "2d8d5432157064c8417b730cb2a966b0", + "d1e649de1e7f268b1d301ea15b8ea672", + "8a328a1cedfe552cf14dee3399ddf91c", + "07a3aec79624c7dabd989097807f7fbf", + "84547ddc3e203c94c3a0533a6d96954b", + "990a98fd5071d2631992575160c43696", + "1a4ff12616eefc89d49493b523ad7777", + "f6f7fd1431714200b77c0f9cf9e436f2", + "30c05b1ba332f41ce62702fef78982ef", + "8d2636b81555a786236a476ed9466eb7", + "46c9feb55d120902e304cfd61b4f416c", + "ccec0a73b49c99212cb2ea092e5f1215", + "4e9d2827355fc492e2004d8b7660e169", + "19ebb029435dcb0f7fcabe79c4442ade", + "4659d2b743848a2c3ec3d0686df24ad5", + "963ef2c96b33be318f3d9e86c7b31fff", + "74f85198b05a2e7d6ba130e1edb0873d", + "5a0f544dd2b1fb1840fe52eaaac04a87", + "03727073c2e134b1789eb8c74c29b5bd", + "c7f6aa2de59aea614630d70199d8fe85", + "352787baa0d7c22fc4f6e06e6eadb36d", + "9853eab63b5e0b352d9cd0536bddc355", + "abbdcdd7ed5c08609522ac8d318de072", + "cf05daf5ac8d77b0dac15872053fb2a8", + "49cad48cebf4a71e4b63b05120422ba5", + "7a4c10ec2158c4a6c4f797ac06e0c775", + "d9e92aa246bf719e4b9efbbed8c2bd98", + "13ae978d09fe555767b2d7640bd6d12a", + "730499af921549ff69216ca21f6e1bf4", + "4e4b705b92903ba449ab0589118fe345", + "ff577222c14f0a3a90b672aaf0764986", + "55b6344cf97aafaec24aa6db9b0e9300", + "b862225b055b69608478cc06efce550e", + "cac09afbddd2cdb482e3ec2ccd28350f", + "daf8e9829fe96b5fbc059bb74b993690", + "b5fdfc5d3132c498e31111d14445238b", + "310cb380db6f7503a7ed1df77e79a736", + "e87fbb46217a360ea137dc0582888c63", + "2102ae466ebb11487630e70040281934", + "f8549e1a3aa5e00d2c17c3b74634a6d6", + "07a69afdcc42261a05eff03a838a4fb4", + "c4c118bfe78feaae3c28d21d40d4f80e", + "f9f4892ed96bd438beb9cec18b163f7c", + "1af3dbe25d8f45dadc75195297484115", + "f5b4b0b0d2deeeb4548955be3bde572b", + "962aceefa82e1c84c8281bde4d280b81", + "046e3ecaaf453ce9914da129a132922b", + "f05d129681949a4cff11d08f1cee77a4", + "964781ce734b3c848070295bd01f6bfd", + "9c2ed44081ce5fbd006a9346f317a9c9", + "522e23f3925e319e37e62ccdf9739fc3", + "177e00f9fc32f7915cbf8b753a7e703c", + "2bc60a63a6f3b3f2fe0f162fcebe01c8", + "222bbfae61725606094f481a19d464ff", + "486289ddcc3d67809737e370b3c679cf", + "7dc7785b8efdfc8060d67575f3b9b1c2", + "8af38731c02ba9803948fbaf41196093", + "1fab64ea29a2ddf70c204d1cfdbf6e2a", + "e4d9429322cd065ab7d2d42a9be29c2a", + "9da058c67844f20c49fae729fc2974b3", + "24c0e332b70019b06fb26356dad98ed6", + "233003b5a6cfe6ad4847b6cc14eeffd4", + "d586bd01c5c217f63b5285a9f0152c99", + "5e5637885f29bc2bff1fe7f4b91cff4c", + "7eba726d8c94094b16e20774363e99d0", + "0a56a5f0bfe39272437d1aa9cb4159e0", + "d79476a84ee20d067bdcc9d5c1c4da0b", + "9e4c1269baa4bf378fd087733782eecd", + "17efee45b0dee640bf94dee3ad478cda", + "1d95b0a5fcf90bc683c94bbe4c623bf5", + "93cbe0b699c2585d08fda03aa45ee9ba", + "65fa4f227a2b6d79c188f92d4d403856", + "d5f9e858292504d5fd6611dfb12345fc", + "c2b5a03f71471a6fef003ffd18aecc12", + "59300222b4561e00b7b474cbf2934019", + "ce2f8642ca0712dcbc2a55e58b30deee", + "7ca9723fbb2e89883e77de18337cda42", + "2785338347f2ba082f81e058ffb75885", + "c61bb3a141e50e8c8f0c300cf585707e", + "150f361dab9dec26cf4f4c536e0e2af2", + "9f6a419d382595f424c81ee5fd39a8e4", + "64a53dc924fe7ac92841577e66ad726e", + "142de49fff7a7c3d68090c81a1357214", + "0c335248857fa9e7a6aa70d44a613a24", + "0a9c32d5eae45305db805d26087f4db9", + "e6c42178c4bbb92e6dd954b45a122182", + "71f1ce2490d20b07c34729fd9f1948a3", + "f1bcc3d275afe51af0682ca0764cc153", + "e728e8c83c334074bf29824279ca73e1", + "96fbf83a12884624f0c70fb4e725caff", + "81a1549fd6573da51e18cad809e9eedc", + "5fa7867caf35e149a893a8fa258f383e", + "56986e2ef3ed091be78c30cc1179b849", + "917f1dd5f8886c612e4432e6ce4996d9", + "d20d8c88c8ffe65f576ec7a84e0b932d", + "e451a35fdc1a15680be771561ec918cc", + "a7491dbcd15111d48c1d8d1f652a529e", + "8a4217fcd5606ec1e77cfdee91420b23", + "5d58f803bcc36ce9e817a8578960ff4e", + "a90baddaceded9031c729588f29a5aca", + "a9471d9e37b819e2b6e41b1c4a06a3f5", + "e156bff8c89bc39a0508ed09c3302ab5", + "0615d8d376bc7fcded07ce16d1ef259f", + "f456457534903782b909e6354382f1e8", + "32b536e093de22e9d41b48ed5273a71b", + "78b4539b20717c761639f5383a52a61b", + "a911e65a7773a2e0bd764b105ac02726", + "f627e5a63397a52416b3e6e67931c70f", + "4aafe20703afe6fd6c4590dcf159b130", + "39abad557a5979408f5f986770948b92", + "ac6032dffcf895202dccb0a1104abc38", + "92737a916770b1ef93f6cfaf16b73787", + "baeead670f8e4541de080d510ba6b8ad", + "c3e988bb11d2a6566038769bd67a9ff5", + "d8c983e407515dc00b33f8b0348d9f49", + "c8e67b5353d9c19fe856d64c017dce27", + "ea99450e132d73cbb5fa1987248dee24", + "12aa814460ac477c36eca5ebc4a2e4cd", + "619c162f3194d65186004e0fc630a7e1", + "f2f0f063aa2c16f521d47ec9fd7a02e1", + "6947f25cd65aa856dd30a05f9e19dcf6", + "25f20b4a567c64f5da2a9bf2f48d8533", + "9a2229ec2807889875f13543748549fa", + "2c48815c49a5f0fabd2c1a78eb76674b", + "af77c31d4c7265afc7e6a3f2f0a3aa37", + "acce0c833a3573efbafb036e101ebab3", + "30e93ba51f97b02246ef1cb7cdfac4fb", + "51f262e5a63efba0784655d00b1b28dd", + "cad75ea0533450f427608d1669c3199b", + "b2d06c848a5dd3e0dccf6e2d796b8daf", + "54a93f6f6155cd7c80e63fc44c3a5c00", + "3f5e483a930616001f173727808a8b85", + "f9229b26700da30a679fea485444b664", + "0ab39ecfc086ff245e818c8204e8dbe1", + "99f363d7d7529302d5df5ddc8f7114ad", + "91ce601f9b79806adc0ae2e42c2e3824", + "5faa8e971b369e61059556ea22a383c4", + "cf918b7638ff0f6adc9aa9c48ac35339", + "da681fb5553e51f3e43342224ea75fa2", + "35ea31a821d07a97241a05687374bbac", + "6174472a0ba38852a8aeb7ec6fd154c9", + "7d2353972cb50561b5ed67257e94af27", + "3086c452a8d1f217ca82922c96b4891e", + "b3493a4890144e823c49ed4447f5c1af", + "b77eb04ced6b4da680e5697a959cd265", + "0f9674061d9dde98f2f908462b7a7c82", + "0f5b409c8cd6d7558e8ad8c31c8d7c35", + "314b1563fffd7df412b190f269af3d15", + "49194a321ca48e15ae59e4212375c373", + "ac1f8d82620b99ecca85acc80d4792cc", + "f3d45ad6cc8143721738dbaf12f77c02", + "9f512e364fe3918b5fc34f06b9a581c5", + "db330613d3ede96adcc7365abfdb4ca5", + "599fb83195c12763d4b6179359571cc7", + "3630c153c8eb55f537e740d456eb6ac7", + "d142d302f484b36d5f549dfeee2a614b", + "bd3aba4c2361aefdea27d301fedf63e7", + "ae61e9ac401204d817ebad4fe2786896", + "e1761b467bd6200fbf12eb6d219e9039", + "41a7e1766724bd0d934f629fd32ae8e6", + "a3cd0a0fe0cb3221c1f94584f4a4718f", + "720c42f46a4a90bb4a7f154207c6f29a", + "bafd4f0ff903b1252efca997124c46aa", + "aab22508c3ddb9d1782e52d302382123", + "8a1fe9ebd768c2ec66d9b5295421ef82", + "d1ca0b47c5fae423a2171d841fd76862", + "edc4c252d59a45afb08e24258449c0f8", + "61e3c107bcd6086344b69ef29abcf104", + "0fc798dd2b02375eb856b9b6373e52b1", + "7a947bcae228e9706d5d2adf1bfc1524", + "c0677350c96d27adb5d88ef51ff0f808", + "1d0b08749687673391fffe853657b1db", + "3cef42fef7be3ee3836fe98efbc0a9ac", + "6b3371897f768168650e36cf10e68f5b", + "c2559854c0155ddfd69248be73e5f1fa", + "484ef707ad593f1114e20a4810507627", + "7001bb600a185cdfb89314fa2e10d6c3", + "dde856a34f8409113a989115b4cb1b74", + "c0383bd196c95464786fa46e50886c70", + "dd054cc476f9528884350262671ef260", + "fe72e1e8cc12d9b27f1cb78c5607eea1", + "82eebd2d55ef0bc7031620e7a9d440a4", + "dc0bae8b3c4d14e5da3208de90e91130", + "eebf7e1e21ae613a4911bf4adb2c5495", + "40338b058d9529b0ff74e78ac20b5606", + "f4f11d4cd9b76d1af11adadcce55f66d", + "6c13ad29ec64895bb5a5b848cbb2a3b2", + "0e2afbefd0bb63f426f20a1d6e531b16", + "ef1676dfa4c7f70476961b8fc2803de5", + "0aedb1a2cf66bcf03ab58a50aefbc505", + "53ff331b68b80b7409cc66874722c2c8", + "23ca594272c0e24dd23ea2458c58f9a2", + "7bb0e2eccb4a002988f1e8b49a77b37e", + "18fb00871630e7cc9c940772bf8bf640", + "b53d7e4052b0e6ed9803c0ac9989696d", + "5116a581878fbae0f93f42bae0d928de", + "7acdc7087d4423e0e791d17177dc66e5", + "0dbeeaeda792606213669a27d85fd282", + "7ffa5dae727405beedb817a210993d87", + "c71b75d78d28cdd211ca64062996e3d8", + "a64d6eaf17bd8eff86de6dfb079540bc", + "c4ae0d72878a4e17b2b1d1264ef70fdb", + "b5594460bd362f25a59c743beece4151", + "03c957eb70fd2d319a7edb235ba97eca", + "be7bf16e1398fd31dbe49ee3777fe59d", + "bfd7597a15804ccf48bc46404e9b0ac6", + "4e021708b31e165df350519970010467", + "36ba1af5eb9bd99d0dc581bb9f26b9d3", + "f5c9a9fd9222bbc4d708e13546b467e4", + "ea4c994af8783870ac39a8adcd1b2b15", + "1124c1533080e0853668d95537083a34", + "2feeec908fcea8efc15c2314889520cb", + "e274244547fb495828b2fbb3a00e69a4", + "d57059580c87b8aa44230d8dae0e8b75", + "bd76f6ad928d4194ec15b393ebddb27b", + "d3d11982cb9c6045e2a0c9d3ac375ad1", + "9f100b15d844d76924d7f01a6a0c12cc", + "91798a2918a4f379cc75ebc58b1affd3", + "218056f83ba6f96223b282eda43fdc12", + "94b3aab28111b3be02e7cd6d0b2dbcda", + "81c76d5d6d293239a4dd056a7b039528", + "b3b8051e4d3405c9cc44d2e7b563d30e", + "c4b5e4f9f06c3ae0d4ef932e79e182ff", + "2f9900cc2b7a19ca2b9178eb57f3db25", + "f75235beb01886d317d16678351d3778", + "8ae7e29889ac996488b17afbdae836b1", + "ad30091ce7cb4204a8985bb047c388b1", + "aae118773ddd4e4d2577b875de120fd2", + "8ec514ce4736aa07bdf2fdea13ee21fd", + "26a412bd8cef4f153bcf1cf1605da5e7", + "1bdce26bd9af059f6ea6f4fb2ca4d856", + "ea5f4ade5acc0516b1198a9621b237f4", + "69ab7ebc076505656c87686b28782362", + "3e655f895a188a1c5158703d9536de86", + "f394f6882a114d65b3126fafbea75501", + "3173cfa2be5bd4d380d3335852547580", + "434d20d2ca00ae71c5afc4f44d5ab019", + "3ba297f73d338c937aff7035ddcde586", + "099ba1b0205a5ea5c338837c953120b2", + "c49f050b5e1c56537f8b4b715fc6eee8", + "e14eec50a9c690e876d1cd962b7c0005", + "2571cc79f4ce0169ad3db13d420b8915", + "de0f98d6002f4323e5a6076284061351", + "0682220b02e5c3e87f61ddc8b19de688", + "cb900d3a6b38c39d8648ca9be5696f33", + "24620fbf09d50d66469dc25e1b32d323", + "0f40a9b2781a119d4f529fa289578425", + "83c6980df0d0493282d550f6a40a3d66", + "ab6f9af720cb5df48e791844cfb87476", + "1c906974166ee8d4b229ab8bb6054dad", + "9c1ba3db0784ebda50a0b7e3c796ad7e", + "81a19098d16aa929186c0c4a7a5d68d1", + "fce56173c63ccefd748301e9e571a670", + "43cb7aa20c6209c22119955cd577ee40", + "7e96e2ae86924bab6c57c1380ffb21fb", + "01860725034b0fefadd607ff5aaaf995", + "f74d369066ec4e96d41ec439c73a3e0f", + "1ae9962c6e0d12323ee343c1d9837a9e", + "5d66fa465ccfc56097707414bf743321", + "e9c13ae1fc36afaae6a8ec453ed67917", + "caec4035fb840be403845f0849e183df", + "839d28adafad0f8fccd5f9e7ce7e601f", + "e4703b6e30422003e2c04ba3484232d8", + "1e2fd5b2827d5e435d9b925a3e6af022", + "96f1e8d8b94bd960a852b77063b9e148", + "90f2075c3f43960c279d2c0c53ecaac6", + "c48e0774c4f9134f39f0827a4f811c72", + "f17e5f6a2cb000c73b064b8614517b48", + "6248409bf55a4925db19bcf000dd394a", + "967bd94eb30505cca63dddb617c59634", + "e91e89853f9e844fece83ffa46fd173a", + "b841038e24193f0801c4d4d39c11557f", + "46f3b25cae82a6cca618dd21a5f6b67f", + "3e97e042449e3ed58356bdf440563fb0", + "868a166af46dcbd2f35ad55d727351a1", + "f71be788b3fd1a7aa3a0c83ec173d41c", + "cb6d65410533cc37a8b2fe93fc5ddd19", + "7e30d70559efaedc984fd612fa3936bd", + "32db0f5ca18159ce71c7e142bb029d2a", + "97a9116e874228c5759f8a9460c634a6", + "85ee68ee3a1752976fa2ca2ca3bdeb90", + "076a14170b409e2aea5f0228fc314b97", + "bad49d47dc95855bc40c265d97e763c0", + "636187d94ded991e201d68d0d89f0e31", + "962e50971f09cfab1b769456c77879cf", + "8f16c910d6776589a8baaf8b31da09a5", + "7e3de4bfbef5566f06a79ac414c9632e", + "b262e9f9d61233206e21a47d5b561a1d", + "91533947cdaa8bec4263a757e414fe44", + "a13b56b45723a3d493c43f67cf55b53f", + "9a35cce29ca3ac75a89732f339d35eec", + "2716940e1d4f28d75df4ac25c29fbebf", + "7447209cfb79306662059230cedcd78f", + "5cf91d8ae6402e1a9b0f7261932d2c8e", + "4625588d38487ac5fc0f99f00e0cc0e7", + "e42ec6191353e3bdcba8a5a02c351aa5", + "478e6cc8f6b2dada77a6c01bd0174d69", + "1726fc948b994b87c200e5264100e463", + "fb9d2e5a66b467413f340a89f525effe", + "7f668e401ffe9e6ff748b2be597b2f7a", + "ee4d6fe11c46a236f7b2eddf7b8838c2", + "006cb70064259959bdec7e9f4317a678", + "33535a7c4def1b245e022239fdf20b36", + "479e792f8171fc29fdd92fa05b03b629", + "656a6e71de97097580cdab95a89927dc", + "cada3e48618a1c2b92cb516b8eba4a30", + "b37ad7262db9c99eac950f8bce3af2d9", + "85ae25402a311d5d9a43060d3aaae01a", + "3de4e82d52dbb44cf12b2f6012f9333c", + "b1c8499674464c218ae9143ece61584a", + "f1c1853cc6827b84676f70930e72993c", + "51f97ed3ba004fb025e0f5f014476a1f", + "00da9ede878e3e981e33827f042de978", + "3cd0fd658e1cdb12dd158e4a7838524d", + "ac2940b688a1d0f92b75316dfa1b15e2", + "e51acb5b336db0df0283b57f325ea495", + "cf7517fbdcb16174146e60f56ab91765", + "dfe901aba4a2ced3f50d2497f8b12819", + "24bfd4b72c8852eb7600c53f3c60308b", + "f085bcd9711883d4b75208a6056dc7e9", + "41b71908a3d86274e5f0eb83c20e921b", + "6d604cc0a2df1a697529d0a0c95f08ed", + "aedf8291e0048c3933c7b70ee04a511c", + "09d3c83f59547935727a256dad06cc11", + "257d5c7ebc7182421b26058e1ba73008", + "56ac1c998f5c2ede09c48fc7e167f3b0", + "a25c0b0679937316954f57cdf6076cd4" + ) + + val p1TurnMask = "f8d626aaaf2785093815e537b6222c85" + +} diff --git a/src/main/scala/dameo/History.scala b/src/main/scala/dameo/History.scala new file mode 100644 index 000000000..e5a0de068 --- /dev/null +++ b/src/main/scala/dameo/History.scala @@ -0,0 +1,85 @@ +package strategygames.dameo + +import format.Uci + +import strategygames.Player + +//TODO Dameo - might need to add extra info into history, like captured pieces on this turn +case class History( + lastTurn: List[Uci] = List.empty, + currentTurn: List[Uci] = List.empty, + forcedTurn: Boolean = false, + positionHashes: PositionHash = Array.empty, + // not sure Dameo needs this? Might be only needed for Frisian? + kingMoves: KingMoves = KingMoves(), + // this is tracking fullMove for Dameo + halfMoveClock: Int = 0 +) { + + lazy val lastAction: Option[Uci] = + if (currentTurn.nonEmpty) currentTurn.reverse.headOption else lastTurn.reverse.headOption + + lazy val recentTurn: List[Uci] = if (currentTurn.nonEmpty) currentTurn else lastTurn + + lazy val recentTurnUciString: Option[String] = + if (recentTurn.nonEmpty) Some(recentTurn.map(_.uci).mkString(",")) else None + + private def isRepetition(times: Int) = + positionHashes.length > (times - 1) * 4 * Hash.size && { + // compare only hashes for positions with the same side to move + val positions = positionHashes.sliding(Hash.size, 2 * Hash.size).toList + positions.headOption match { + case Some(Array(x, y, z)) => + (positions count { + case Array(x2, y2, z2) => x == x2 && y == y2 && z == z2 + case _ => false + }) >= times + case _ => times <= 1 + } + } + + def threefoldRepetition = isRepetition(3) + + // generates random positionHashes to satisfy the half move clock + def setHalfMoveClock(v: Int) = + copy(positionHashes = History.spoofHashes(v + 1)) + +} + +object History { + + private def spoofHashes(n: Int): PositionHash = { + (1 to n).toArray.flatMap { i => + Array((i >> 16).toByte, (i >> 8).toByte, i.toByte) + } + } + +} + +// Consecutive king moves by the respective side. +case class KingMoves( + p1: Int = 0, + p2: Int = 0, + p1King: Option[Pos] = None, + p2King: Option[Pos] = None +) { + + def add(player: Player, pos: Option[Pos]) = copy( + p1 = p1 + player.fold(1, 0), + p2 = p2 + player.fold(0, 1), + p1King = player.fold(pos, p1King), + p2King = player.fold(p2King, pos) + ) + + def reset(player: Player) = copy( + p1 = player.fold(0, p1), + p2 = player.fold(p2, 0), + p1King = player.fold(None, p1King), + p2King = player.fold(p2King, None) + ) + + def nonEmpty = p1 > 0 || p2 > 0 + + def apply(player: Player) = player.fold(p1, p2) + def kingPos(player: Player) = player.fold(p1King, p2King) +} diff --git a/src/main/scala/dameo/Move.scala b/src/main/scala/dameo/Move.scala new file mode 100644 index 000000000..9615d83e2 --- /dev/null +++ b/src/main/scala/dameo/Move.scala @@ -0,0 +1,48 @@ +package strategygames.dameo +import strategygames.MoveMetrics + +import strategygames.dameo.format.Uci + +case class Move( + piece: Piece, + orig: Pos, + dest: Pos, + situationBefore: Situation, + after: Board, + autoEndTurn: Boolean, + capture: Option[Pos] = None, + promotion: Option[PromotableRole] = None, + metrics: MoveMetrics = MoveMetrics() +) extends Action(situationBefore, after, metrics) { + + def situationAfter = + Situation(finalizeAfter, situationBefore.player) + + // TODO Dameo - might need to edit this, look at how draughts gamelogic does this (ghosts) + def finalizeAfter: Board = after updateHistory { h => + h.copy( + currentTurn = h.currentTurn :+ toUci + ) + } + + def lazySituationAfter = + Situation(lazyFinalizeAfter, situationBefore.player) + + def lazyFinalizeAfter: Board = after updateHistory { h => + h.copy( + currentTurn = h.currentTurn :+ toUci + ) + } + + def captures = capture.isDefined + + def promotes = promotion.isDefined + + def player = piece.player + + def withMetrics(m: MoveMetrics) = copy(metrics = m) + + def toUci = Uci.Move(orig, dest, promotion, capture) + + override def toString = s"$piece ${toUci.uci}" +} diff --git a/src/main/scala/dameo/Piece.scala b/src/main/scala/dameo/Piece.scala new file mode 100644 index 000000000..b9f46caaa --- /dev/null +++ b/src/main/scala/dameo/Piece.scala @@ -0,0 +1,35 @@ +package strategygames.dameo + +import strategygames.Player + +case class Piece(player: Player, role: Role) { + + def is(c: Player) = c == player + def is(r: Role) = r == role + def isNot(c: Player) = c != player + def isNot(r: Role) = r != role + + def oneOf(rs: Set[Role]) = rs(role) + + def forsyth: Char = player.fold(role.forsythUpper, role.forsyth) + + def isGhost = role == GhostMan || role == GhostKing + + def ghostRole = + if (role == Man) GhostMan + else if (role == King) GhostKing + else role + + override def toString = s"${player.toString.toLowerCase}-$role" + +} + +object Piece { + + // This is only currently used for the pockets + def fromChar(c: Char): Option[Piece] = + Role.allByPdn get c.toLower map { + Piece(Player.fromP1(c.isUpper), _) + } + +} diff --git a/src/main/scala/dameo/Pos.scala b/src/main/scala/dameo/Pos.scala new file mode 100644 index 000000000..ec8161906 --- /dev/null +++ b/src/main/scala/dameo/Pos.scala @@ -0,0 +1,317 @@ +package strategygames.dameo + +import scala.math.{ abs, max, min } + +// Matches with: https://github.com/Mind-Sports-Games/lila/blob/incoming-prs/ui/chess/src/piotr.ts +object Piotr { + val lookup: Map[Int, Char] = Map( + Pos.A1.index -> 'a', + Pos.B1.index -> 'b', + Pos.C1.index -> 'c', + Pos.D1.index -> 'd', + Pos.E1.index -> 'e', + Pos.F1.index -> 'f', + Pos.G1.index -> 'g', + Pos.H1.index -> 'h', + Pos.A2.index -> 'i', + Pos.B2.index -> 'j', + Pos.C2.index -> 'k', + Pos.D2.index -> 'l', + Pos.E2.index -> 'm', + Pos.F2.index -> 'n', + Pos.G2.index -> 'o', + Pos.H2.index -> 'p', + Pos.A3.index -> 'q', + Pos.B3.index -> 'r', + Pos.C3.index -> 's', + Pos.D3.index -> 't', + Pos.E3.index -> 'u', + Pos.F3.index -> 'v', + Pos.G3.index -> 'w', + Pos.H3.index -> 'x', + Pos.A4.index -> 'y', + Pos.B4.index -> 'z', + Pos.C4.index -> 'A', + Pos.D4.index -> 'B', + Pos.E4.index -> 'C', + Pos.F4.index -> 'D', + Pos.G4.index -> 'E', + Pos.H4.index -> 'F', + Pos.A5.index -> 'G', + Pos.B5.index -> 'H', + Pos.C5.index -> 'I', + Pos.D5.index -> 'J', + Pos.E5.index -> 'K', + Pos.F5.index -> 'L', + Pos.G5.index -> 'M', + Pos.H5.index -> 'N', + Pos.A6.index -> 'O', + Pos.B6.index -> 'P', + Pos.C6.index -> 'Q', + Pos.D6.index -> 'R', + Pos.E6.index -> 'S', + Pos.F6.index -> 'T', + Pos.G6.index -> 'U', + Pos.H6.index -> 'V', + Pos.A7.index -> 'W', + Pos.B7.index -> 'X', + Pos.C7.index -> 'Y', + Pos.D7.index -> 'Z', + Pos.E7.index -> '0', + Pos.F7.index -> '1', + Pos.G7.index -> '2', + Pos.H7.index -> '3', + Pos.A8.index -> '4', + Pos.B8.index -> '5', + Pos.C8.index -> '6', + Pos.D8.index -> '7', + Pos.E8.index -> '8', + Pos.F8.index -> '9', + Pos.G8.index -> '!', + Pos.H8.index -> '?', + Pos.A9.index -> '\"', + Pos.B9.index -> '#', + Pos.C9.index -> '$', + Pos.D9.index -> '%', + Pos.E9.index -> '&', + Pos.F9.index -> '\'', + Pos.G9.index -> '(', + Pos.H9.index -> ')', + Pos.I9.index -> '*', + Pos.J9.index -> '+', + Pos.A10.index -> '§', // NOTE: comma is not a valid piotr due to the way lila stores analysis. + Pos.B10.index -> '-', + Pos.C10.index -> '.', + Pos.D10.index -> '/', + Pos.E10.index -> ':', + Pos.F10.index -> '¨', // NOTE: semicolon can't be used for the same reason as above + Pos.G10.index -> '<', + Pos.H10.index -> '=', + Pos.I10.index -> '>', + Pos.J10.index -> '@', + Pos.I1.index -> '[', + Pos.J1.index -> '\\', + Pos.I2.index -> ']', + Pos.J2.index -> '^', + Pos.I3.index -> '_', + Pos.J3.index -> '`', + Pos.I4.index -> '{', + Pos.J4.index -> '|', + Pos.I5.index -> '}', + Pos.J5.index -> '~', + // https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin_script + // from the latin-1 script + Pos.I6.index -> '¡', + Pos.J6.index -> '¢', + Pos.I7.index -> '£', + Pos.J7.index -> '¤', + Pos.I8.index -> '¥', + Pos.J8.index -> '¦' + // NOTE: "§" and "¨" are used above + ) +} + +case class Pos private (index: Int) extends AnyVal { + + def down: Option[Pos] = Pos.at(file.index, rank.index - 1) + def left: Option[Pos] = Pos.at(file.index - 1, rank.index) + def downLeft: Option[Pos] = Pos.at(file.index - 1, rank.index - 1) + def downRight: Option[Pos] = Pos.at(file.index + 1, rank.index - 1) + def up: Option[Pos] = Pos.at(file.index, rank.index + 1) + def right: Option[Pos] = Pos.at(file.index + 1, rank.index) + def upLeft: Option[Pos] = Pos.at(file.index - 1, rank.index + 1) + def upRight: Option[Pos] = Pos.at(file.index + 1, rank.index + 1) + + def >|(stop: Pos => Boolean): List[Pos] = |<>|(stop, _.right) + def |<(stop: Pos => Boolean): List[Pos] = |<>|(stop, _.left) + def |<>|(stop: Pos => Boolean, dir: Direction): List[Pos] = + dir(this) map { p => + p :: (if (stop(p)) Nil else p.|<>|(stop, dir)) + } getOrElse Nil + + def ?<(other: Pos): Boolean = file < other.file + def ?>(other: Pos): Boolean = file > other.file + def ?+(other: Pos): Boolean = rank < other.rank + def ?^(other: Pos): Boolean = rank > other.rank + def ?|(other: Pos): Boolean = file == other.file + def ?-(other: Pos): Boolean = rank == other.rank + + def <->(other: Pos): Iterable[Pos] = + min(file.index, other.file.index) to max(file.index, other.file.index) flatMap { Pos.at(_, rank.index) } + + def touches(other: Pos): Boolean = xDist(other) <= 1 && yDist(other) <= 1 + + def onSameDiagonal(other: Pos): Boolean = + file.index - rank.index == other.file.index - other.rank.index || file.index + rank.index == other.file.index + other.rank.index + def onSameLine(other: Pos): Boolean = ?-(other) || ?|(other) + + def xDist(other: Pos) = abs(file - other.file) + def yDist(other: Pos) = abs(rank - other.rank) + + def isLight: Boolean = (file.index + rank.index) % 2 == 1 + + @inline def file = File of this + @inline def rank = Rank of this + + def piotr: Char = Piotr.lookup.get(index).getOrElse('?') + def piotrStr = piotr.toString + + def sgf(numRanks: Int) = file.sgfChar.toString + rank.sgfChar(numRanks).toString + + def key = file.toString + rank.toString + override def toString = key +} + +object Pos { + def apply(index: Int): Option[Pos] = + if (0 <= index && index < File.all.size * Rank.all.size) Some(new Pos(index)) + else None + + def apply(file: File, rank: Rank): Pos = new Pos(File.all.size * rank.index + file.index) + + def at(x: Int, y: Int): Option[Pos] = + File(x) zip Rank(y) map { case (file, rank) => + Pos(file, rank) + } + + def fromKey(key: String): Option[Pos] = allKeys get key + + def piotr(c: Char): Option[Pos] = allPiotrs get c + + def keyToPiotr(key: String) = fromKey(key) map (_.piotr) + def doubleKeyToPiotr(key: String) = + for { + a <- keyToPiotr(key take 2) + b <- keyToPiotr(key drop 2) + } yield s"$a$b" + def doublePiotrToKey(piotrs: String) = + for { + a <- piotr(piotrs.head) + b <- piotr(piotrs(1)) + } yield s"${a.key}${b.key}" + + //TODO Dameo - write this + //Works out the pos between the orig and dest that has been jumped over (i.e. where the capture took place + def capturePos(orig: Pos, dest: Pos): Option[Pos] = None + + val A1 = new Pos(0) + val B1 = new Pos(1) + val C1 = new Pos(2) + val D1 = new Pos(3) + val E1 = new Pos(4) + val F1 = new Pos(5) + val G1 = new Pos(6) + val H1 = new Pos(7) + val I1 = new Pos(8) + val J1 = new Pos(9) + val A2 = new Pos(10) + val B2 = new Pos(11) + val C2 = new Pos(12) + val D2 = new Pos(13) + val E2 = new Pos(14) + val F2 = new Pos(15) + val G2 = new Pos(16) + val H2 = new Pos(17) + val I2 = new Pos(18) + val J2 = new Pos(19) + val A3 = new Pos(20) + val B3 = new Pos(21) + val C3 = new Pos(22) + val D3 = new Pos(23) + val E3 = new Pos(24) + val F3 = new Pos(25) + val G3 = new Pos(26) + val H3 = new Pos(27) + val I3 = new Pos(28) + val J3 = new Pos(29) + val A4 = new Pos(30) + val B4 = new Pos(31) + val C4 = new Pos(32) + val D4 = new Pos(33) + val E4 = new Pos(34) + val F4 = new Pos(35) + val G4 = new Pos(36) + val H4 = new Pos(37) + val I4 = new Pos(38) + val J4 = new Pos(39) + val A5 = new Pos(40) + val B5 = new Pos(41) + val C5 = new Pos(42) + val D5 = new Pos(43) + val E5 = new Pos(44) + val F5 = new Pos(45) + val G5 = new Pos(46) + val H5 = new Pos(47) + val I5 = new Pos(48) + val J5 = new Pos(49) + val A6 = new Pos(50) + val B6 = new Pos(51) + val C6 = new Pos(52) + val D6 = new Pos(53) + val E6 = new Pos(54) + val F6 = new Pos(55) + val G6 = new Pos(56) + val H6 = new Pos(57) + val I6 = new Pos(58) + val J6 = new Pos(59) + val A7 = new Pos(60) + val B7 = new Pos(61) + val C7 = new Pos(62) + val D7 = new Pos(63) + val E7 = new Pos(64) + val F7 = new Pos(65) + val G7 = new Pos(66) + val H7 = new Pos(67) + val I7 = new Pos(68) + val J7 = new Pos(69) + val A8 = new Pos(70) + val B8 = new Pos(71) + val C8 = new Pos(72) + val D8 = new Pos(73) + val E8 = new Pos(74) + val F8 = new Pos(75) + val G8 = new Pos(76) + val H8 = new Pos(77) + val I8 = new Pos(78) + val J8 = new Pos(79) + val A9 = new Pos(80) + val B9 = new Pos(81) + val C9 = new Pos(82) + val D9 = new Pos(83) + val E9 = new Pos(84) + val F9 = new Pos(85) + val G9 = new Pos(86) + val H9 = new Pos(87) + val I9 = new Pos(88) + val J9 = new Pos(89) + val A10 = new Pos(90) + val B10 = new Pos(91) + val C10 = new Pos(92) + val D10 = new Pos(93) + val E10 = new Pos(94) + val F10 = new Pos(95) + val G10 = new Pos(96) + val H10 = new Pos(97) + val I10 = new Pos(98) + val J10 = new Pos(99) + + // current pos limit in db is 128, if adding more perhaps use a different method (map of index to file, rank) + + // if adding new Pos check for use of Pos.all + val all: List[Pos] = (0 to (File.all.size * Rank.all.size) - 1).map(new Pos(_)).toList + + val allKeys: Map[String, Pos] = all + .map { pos => + pos.key -> pos + } + .to(Map) + + val allPiotrs: Map[Char, Pos] = all + .map { pos => + pos.piotr -> pos + } + .to(Map) + + val posR = "([a-j][1-9]|[a-j]10)" + +} diff --git a/src/main/scala/dameo/Rank.scala b/src/main/scala/dameo/Rank.scala new file mode 100644 index 000000000..130c14775 --- /dev/null +++ b/src/main/scala/dameo/Rank.scala @@ -0,0 +1,39 @@ +package strategygames.dameo + +case class Rank private (val index: Int) extends AnyVal with Ordered[Rank] { + @inline def -(that: Rank): Int = index - that.index + @inline override def compare(that: Rank) = this - that + + def offset(delta: Int): Option[Rank] = + if (-Rank.all.size < delta && delta < Rank.all.size) Rank(index + delta) + else None + + @inline def char: Char = if (index < 9) (49 + index).toChar else 48.toChar // 0 + def sgfChar(numRanks: Int): Char = (97 + (numRanks - 1 - index)).toChar + override def toString = (index + 1).toString +} + +object Rank { + def apply(index: Int): Option[Rank] = + if (0 <= index && index < all.size) Some(new Rank(index)) + else None + + @inline def of(pos: Pos): Rank = new Rank(pos.index / all.size) + + def fromChar(ch: Char): Option[Rank] = apply(if (ch.toInt == 48) 9 else ch.toInt - 49) + + val First = new Rank(0) + val Second = new Rank(1) + val Third = new Rank(2) + val Fourth = new Rank(3) + val Fifth = new Rank(4) + val Sixth = new Rank(5) + val Seventh = new Rank(6) + val Eighth = new Rank(7) + val Ninth = new Rank(8) + val Tenth = new Rank(9) + + val all = List(First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, Ninth, Tenth) + val allReversed: List[Rank] = all.reverse + +} diff --git a/src/main/scala/dameo/Replay.scala b/src/main/scala/dameo/Replay.scala new file mode 100644 index 000000000..f0aa35d4c --- /dev/null +++ b/src/main/scala/dameo/Replay.scala @@ -0,0 +1,290 @@ +package strategygames.dameo + +import cats.data.Validated +import cats.data.Validated.{ invalid, valid } +import cats.implicits._ + +import strategygames.format.pgn.San +import strategygames.dameo.format.pdn.{ Parser, Reader } +import strategygames.dameo.format.{ FEN, Forsyth, Uci } +import strategygames.{ Action => StratAction, ActionStrs, Move => StratMove, Situation => StratSituation } + +case class Replay(setup: Game, actions: List[Move], state: Game) { + + lazy val chronoPlies = actions.reverse + + lazy val chronoActions: List[List[Move]] = + chronoPlies + .drop(1) + .foldLeft(List(chronoPlies.take(1))) { case (turn, action) => + if (turn.head.head.player != action.player) { + List(action) +: turn + } else { + (turn.head :+ action) +: turn.tail + } + } + .reverse + + def addAction(move: Move) = + copy( + actions = move :: actions, + state = state.apply(move) + ) + +} + +object Replay { + + def apply(game: Game) = new Replay(game, Nil, game) + + def apply( + actionStrs: ActionStrs, + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, Reader.Result] = { + val fen = initialFen.getOrElse(variant.initialFen) + val (init, gameWithActions, error) = + gameWithActionWhileValid(actionStrs, fen, variant) + val game = + gameWithActions.reverse.lastOption.map(_._1).getOrElse(init) + + error match { + case None => + Validated.valid( + Reader.Result.Complete( + new Replay(init, gameWithActions.reverse.map(_._2), game) + ) + ) + case Some(msg) => Validated.invalid(msg) + } + } + + // TODO: because this is primarily used in a Validation context, we should be able to + // return something that's runtime safe as well. + private def dameoAction(action: StratAction) = action match { + case StratMove.Dameo(m) => m + case _ => sys.error(s"Invalid dameo action $action") + } + + def replayMove(before: Game, orig: Pos, dest: Pos, capture: Option[Pos], endTurn: Boolean): Move = + // TODO Dameo set this + Move( + piece = before.situation.board.pieces(orig), + orig = orig, + dest = dest, + situationBefore = before.situation, + after = before.situation.board.copy(), + autoEndTurn = endTurn, + capture = capture, + promotion = None + ) + + private def gameWithActionWhileValid( + actionStrs: ActionStrs, + initialFen: FEN, + variant: strategygames.dameo.variant.Variant + ): (Game, List[(Game, Move)], Option[String]) = { + val init = makeGame(variant, initialFen.some) + var state = init + var errors = "" + + def replayMoveFromUci(orig: Option[Pos], dest: Option[Pos], capture: Option[Pos]): (Game, Move) = + (orig, dest) match { + case (Some(orig), Some(dest)) => { + val move = replayMove(state, orig, dest, capture, true) + state = state(move) + (state, move) + } + case (orig, dest) => { + val uciMove = s"${orig}${dest}" + errors += uciMove + "," + sys.error(s"Invalid move for replay: ${uciMove}") + } + } + + val gameWithActions: List[(Game, Move)] = + // can flatten as specific EndTurn action marks turn end + actionStrs.flatten.toList.map { + case Uci.Move.moveR(orig, capture, dest) => + replayMoveFromUci( + Pos.fromKey(orig), + Pos.fromKey(dest), + if (capture.length == 1) Pos.fromKey(orig) else None + ) + case (action: String) => + sys.error(s"Invalid action for replay: $action") + } + + (init, gameWithActions, errors match { case "" => None; case _ => errors.some }) + } + + def gameWithUciWhileValid( + actionStrs: ActionStrs, + initialFen: FEN, + variant: strategygames.dameo.variant.Variant + ): (Game, List[(Game, Uci.WithSan)], Option[String]) = { + val (game, gameWithActions, error) = gameWithActionWhileValid( + actionStrs, + initialFen, + variant + ) + ( + game, + gameWithActions.map { v => + { + val (state, action) = v + (state, Uci.WithSan(action.toUci, "NOSAN")) + } + }, + error + ) + } + + private def recursiveSituations(sit: Situation, sans: List[San]): Validated[String, List[Situation]] = + sans match { + case Nil => valid(Nil) + case san :: rest => + san(StratSituation.wrap(sit)).map(dameoAction) flatMap { move => + val after = Situation(move.finalizeAfter, !sit.player) + recursiveSituations(after, rest) map { after :: _ } + } + } + + private def recursiveSituationsFromUci( + sit: Situation, + ucis: List[Uci] + ): Validated[String, List[Situation]] = + ucis match { + case Nil => valid(Nil) + case uci :: rest => + uci(sit) andThen { move => + val after = Situation(move.finalizeAfter, !sit.player) + recursiveSituationsFromUci(after, rest) map { after :: _ } + } + } + + private def recursiveReplayFromUci(replay: Replay, ucis: List[Uci]): Validated[String, Replay] = + ucis match { + case Nil => valid(replay) + case uci :: rest => + uci(replay.state.situation) andThen { action => + recursiveReplayFromUci(replay.addAction(action), rest) + } + } + + private def initialFenToSituation( + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Situation = { + initialFen.flatMap(Forsyth.<<) | Situation(strategygames.dameo.variant.Variant.default) + } withVariant variant + + def boards( + actionStrs: ActionStrs, + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, List[Board]] = situations(actionStrs, initialFen, variant) map (_ map (_.board)) + + def situations( + actionStrs: ActionStrs, + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, List[Situation]] = { + val sit = initialFenToSituation(initialFen, variant) + // seemingly this isn't used + Parser.sans(actionStrs.flatten, sit.board.variant) andThen { sans => + recursiveSituations(sit, sans.value) map { sit :: _ } + } + } + + def boardsFromUci( + ucis: List[Uci], + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, List[Board]] = situationsFromUci(ucis, initialFen, variant) map (_ map (_.board)) + + def situationsFromUci( + ucis: List[Uci], + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, List[Situation]] = { + val sit = initialFenToSituation(initialFen, variant) + recursiveSituationsFromUci(sit, ucis) map { sit :: _ } + } + + private def recursiveGamesFromUci( + game: Game, + ucis: List[Uci] + ): Validated[String, List[Game]] = + ucis match { + case Nil => valid(List(game)) + case uci :: rest => + game.apply(uci) andThen { case (game, _) => + recursiveGamesFromUci(game, rest) map { game :: _ } + } + } + + def gameFromUciStrings( + uciStrings: List[String], + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, Game] = { + val init = makeGame(variant, initialFen) + val ucis = uciStrings.flatMap(Uci.apply(_)) + if (uciStrings.size != ucis.size) invalid("Invalid Ucis") + else recursiveGamesFromUci(init, ucis).map(_.last) + } + + def apply( + ucis: List[Uci], + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant + ): Validated[String, Replay] = + recursiveReplayFromUci(Replay(makeGame(variant, initialFen)), ucis) + + def plyAtFen( + actionStrs: ActionStrs, + initialFen: Option[FEN], + variant: strategygames.dameo.variant.Variant, + atFen: FEN + ): Validated[String, Int] = + if (Forsyth.<<@(variant, atFen).isEmpty) invalid(s"Invalid FEN $atFen") + else { + + // we don't want to compare the full move number, to match transpositions + def truncateFen(fen: FEN) = fen.value.split(' ').take(FEN.fullMoveIndex) mkString " " + val atFenTruncated = truncateFen(atFen) + def compareFen(fen: FEN) = truncateFen(fen) == atFenTruncated + + def recursivePlyAtFen(sit: Situation, sans: List[San], ply: Int, turn: Int): Validated[String, Int] = + sans match { + case Nil => invalid(s"Can't find $atFenTruncated, reached ply $ply, turn $turn") + case san :: rest => + san(StratSituation.wrap(sit)).map(dameoAction) flatMap { move => + val after = move.situationAfter + val newPlies = ply + 1 + val newTurnCount = turn + (if (sit.player != after.player) 1 else 0) + val fen = Forsyth >> Game(after, plies = newPlies, turnCount = newTurnCount) + if (compareFen(fen)) Validated.valid(ply) + else recursivePlyAtFen(after, rest, newPlies, newTurnCount) + } + } + + val sit = initialFen.flatMap { + Forsyth.<<@(variant, _) + } | Situation(variant) + + // seemingly this isn't used + Parser.sans(actionStrs.flatten, sit.board.variant) andThen { sans => + recursivePlyAtFen(sit, sans.value, 0, 0) + } + } + + private def makeGame(variant: strategygames.dameo.variant.Variant, initialFen: Option[FEN]): Game = { + val g = Game(variant.some, initialFen) + g.copy( + startedAtPly = g.plies, + startedAtTurn = g.turnCount + ) + } +} diff --git a/src/main/scala/dameo/Role.scala b/src/main/scala/dameo/Role.scala new file mode 100644 index 000000000..9256795ee --- /dev/null +++ b/src/main/scala/dameo/Role.scala @@ -0,0 +1,141 @@ +package strategygames.dameo + +import strategygames.{ GameFamily, P1, P2, Player } + +sealed trait Role { + val forsyth: Char + val forsythUpper: Char = forsyth.toUpper + lazy val pdn: Char = forsyth + lazy val name = toString + lazy val groundName = s"${forsyth}-piece" + val binaryInt: Int + val hashInt: Int = binaryInt + val valueOf: Option[Int] + lazy val gameFamily: GameFamily = GameFamily.Dameo() + final def -(player: Player) = Piece(player, this) + final def p1 = this - P1 + final def p2 = this - P2 +} + +sealed trait PromotableRole extends Role + +//TODO Dameo - check forsyth is what we want. Depends on FEN format +case object King extends PromotableRole { + val forsyth = 'k' + val binaryInt = 1 + val valueOf = Some(2) +} + +case object Man extends Role { + val forsyth = 'm' + val binaryInt = 2 + val valueOf = Some(1) +} + +case object GhostMan extends Role { + val forsyth = 'g' + val binaryInt = 4 + val valueOf = Some(0) +} + +case object GhostKing extends Role { + val forsyth = 'p' + val binaryInt = 3 + val valueOf = Some(0) +} + +object Role { + + val all: List[Role] = List(King, Man) + val allPromotable: List[PromotableRole] = List(King) + + def defaultRole: Role = Man + + def allByGameFamily(gf: GameFamily): List[Role] = + all.filter(r => r.gameFamily == gf) + + val allByForsyth: Map[Char, Role] = all map { r => + (r.forsyth, r) + } toMap + def allByForsyth(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => + (r.forsyth, r) + } toMap + val allByPdn: Map[Char, Role] = all map { r => + (r.pdn, r) + } toMap + def allByPdn(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => + (r.pdn, r) + } toMap + val allByName: Map[String, Role] = all map { r => + (r.name, r) + } toMap + def allByName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => + (r.name, r) + } toMap + val allByGroundName: Map[String, Role] = all map { r => + (r.groundName, r) + } toMap + def allByGroundName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => + (r.groundName, r) + } toMap + val allByBinaryInt: Map[Int, Role] = all map { r => + (r.binaryInt, r) + } toMap + def allByBinaryInt(gf: GameFamily): Map[Int, Role] = allByGameFamily(gf) map { r => + (r.binaryInt, r) + } toMap + val allByHashInt: Map[Int, Role] = all map { r => + (r.hashInt, r) + } toMap + val allPromotableByName: Map[String, PromotableRole] = allPromotable.map(r => (r.toString, r)) toMap + val allPromotableByForsyth: Map[Char, PromotableRole] = allPromotable.map(r => (r.forsyth, r)) toMap + val allPromotableByPdn: Map[Char, PromotableRole] = allPromotable.map(r => (r.pdn, r)) toMap + val allPromotableByGroundName: Map[String, PromotableRole] = + allPromotable map { r => + (r.groundName, r) + } toMap + + def forsyth(c: Char): Option[Role] = allByForsyth get c + + def binaryInt(i: Int): Option[Role] = allByBinaryInt get i + + def hashInt(i: Int): Option[Role] = allByHashInt get i + + def promotable(c: Char): Option[PromotableRole] = + allPromotableByForsyth get c + + def promotable(name: String): Option[PromotableRole] = + allPromotableByName get name.capitalize + + def promotable(name: Option[String]): Option[PromotableRole] = + name flatMap promotable + + // only used in lila by insight module + def pdnMoveToRole(c: Char): Role = + // We dont want ghosts to be returned here + c match { + case 'K' | 'O' => King + case _ => Man + } + + // unused by lila + def javaSymbolToRole(s: String): Role = + s match { + case "" => Man + case _ => King + } + + // unused by lila + def javaSymbolToInt(s: String): Int = + s.headOption match { + case Some(c) if c.toInt >= 65 && c.toInt <= 90 => c.toInt - 64 + case Some(c) if c.toInt >= 97 && c.toInt <= 122 => c.toInt - 70 + case _ => sys.error(s"Could not get Int from java symbol: $s") + } + + def valueOf(r: Role): Option[Int] = r.valueOf + + val roleR = s"([${allByForsyth.keys.mkString("")}])" + val roleRr = s"([${allByForsyth.keys.map(k => s"${k.toLower}${k.toUpper}").mkString("")}]?)" + +} diff --git a/src/main/scala/dameo/Setup.scala b/src/main/scala/dameo/Setup.scala new file mode 100644 index 000000000..356882aab --- /dev/null +++ b/src/main/scala/dameo/Setup.scala @@ -0,0 +1,6 @@ +package strategygames.dameo + +object Setup { + + def apply(variant: strategygames.dameo.variant.Variant) = Game(variant) +} diff --git a/src/main/scala/dameo/Situation.scala b/src/main/scala/dameo/Situation.scala new file mode 100644 index 000000000..048de054a --- /dev/null +++ b/src/main/scala/dameo/Situation.scala @@ -0,0 +1,82 @@ +package strategygames.dameo + +import strategygames.{ Player, Status } +import strategygames.dameo.format.Uci + +import cats.data.Validated +//import cats implicits in this file causes mindtrap to have problems +//import cats.implicits._ + +case class Situation(board: Board, player: Player) { + + lazy val actors = board.actorsOf(player) + + lazy val ghosts = board.ghosts + + lazy val moves: Map[Pos, List[Move]] = board.variant.validMoves(this) + + private val movesList: List[Move] = moves.values.flatten.toList + + lazy val destinations: Map[Pos, List[Pos]] = moves.view.mapValues { _ map (_.dest) }.to(Map) + + def actions: List[Action] = movesList + + def canMove: Boolean = moves.nonEmpty + + def canCapture: Boolean = actions + .map { + case m: Move => m.capture.nonEmpty + case _ => false + } + .contains(true) + + def history = board.history + + def autoDraw: Boolean = board.autoDraw + + def variantEnd = board.variant.specialEnd(this) + + def end: Boolean = variantEnd || autoDraw + + def winner: Option[Player] = board.variant.winner(this) + + def playable(strict: Boolean): Boolean = + (board valid strict) && !end + + def opponentHasInsufficientMaterial: Boolean = false + + lazy val status: Option[Status] = + if (variantEnd) Some(Status.VariantEnd) + else if (autoDraw) Some(Status.Draw) + else None + + def move( + from: Pos, + to: Pos, + promotion: Option[PromotableRole] = None, + capture: Option[Pos] = None + ): Validated[String, Move] = + board.variant.move(this, from, to, promotion, capture) + + def move(uci: Uci.Move): Validated[String, Move] = + board.variant.move(this, uci.orig, uci.dest, uci.promotion, uci.capture) + + def withHistory(history: History) = + copy( + board = board withHistory history + ) + + def withVariant(variant: strategygames.dameo.variant.Variant) = + copy( + board = board withVariant variant + ) + + def unary_! = copy(player = !player) +} + +object Situation { + + def apply(variant: strategygames.dameo.variant.Variant): Situation = + Situation(Board init variant, variant.startPlayer) + +} diff --git a/src/main/scala/dameo/StartingPosition.scala b/src/main/scala/dameo/StartingPosition.scala new file mode 100644 index 000000000..dbfc4f9a7 --- /dev/null +++ b/src/main/scala/dameo/StartingPosition.scala @@ -0,0 +1,1101 @@ +package strategygames.dameo + +import strategygames.dameo.format.FEN + +case class StartingPosition( + eco: String, + name: String, + fen: FEN, + wikiPath: String, + moves: String, + featurable: Boolean = true +) { + + def url = s"https://en.wikipedia.org/wiki/$wikiPath" + + val shortName = name takeWhile (':' !=) + + def fullName = s"$eco $name" + + def initial = fen == format.Forsyth.initial +} + +object StartingPosition { + + case class Category(name: String, positions: List[StartingPosition]) + + val categories: List[Category] = List( + Category( + "e4", + List( + StartingPosition( + "B00", + "King's Pawn", + FEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"), + "King's_Pawn_Game", + "1. e4", + featurable = false + ), + StartingPosition( + "B00", + "Open Game", + FEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "Open_Game", + "1. e4 e5", + featurable = false + ), + StartingPosition( + "B02", + "Alekhine's Defence", + FEN("rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 1 2"), + "Alekhine's_Defence", + "1. e4 Nf6" + ), + StartingPosition( + "B04", + "Alekhine's Defence: Modern Variation", + FEN("rnbqkb1r/ppp1pppp/3p4/3nP3/3P4/5N2/PPP2PPP/RNBQKB1R b KQkq - 1 4"), + "Alekhine's_Defence#Modern_Variation:_3.d4_d6_4.Nf3", + "1. e4 Nf6 2. e5 Nd5 3. d4 d6 4. Nf3" + ), + StartingPosition( + "C23", + "Bishop's Opening", + FEN("rnbqkbnr/pppp1ppp/8/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR b KQkq - 1 2"), + "Bishop%27s_Opening", + "1. e4 e5 2. Bc4" + ), + StartingPosition( + "B10", + "Caro-Kann Defence", + FEN("rnbqkbnr/pp1ppppp/2p5/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "Caro–Kann_Defence", + "1. e4 c6" + ), + StartingPosition( + "B12", + "Caro-Kann Defence: Advance Variation", + FEN("rnbqkbnr/pp2pppp/2p5/3pP3/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"), + "Caro–Kann_Defence#Advance_Variation:_3.e5", + "1. e4 c6 2. d4 d5 3. e5" + ), + StartingPosition( + "B18", + "Caro-Kann Defence: Classical Variation", + FEN("rn1qkbnr/pp2pppp/2p5/5b2/3PN3/8/PPP2PPP/R1BQKBNR w KQkq - 1 5"), + "Caro–Kann_Defence#Classical_Variation:_4...Bf5", + "1. e4 c6 2. d4 d5 3. Nc3 dxe4 4. Nxe4 Bf5" + ), + StartingPosition( + "B13", + "Caro-Kann Defence: Exchange Variation", + FEN("rnbqkbnr/pp2pppp/2p5/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"), + "Caro%E2%80%93Kann_Defence#Exchange_Variation:_3.exd5_cxd5", + "1. e4 c6 2. d4 d5 3. exd5" + ), + StartingPosition( + "B14", + "Caro-Kann Defence: Panov-Botvinnik Attack", + FEN("rnbqkb1r/pp2pppp/5n2/3p4/2PP4/2N5/PP3PPP/R1BQKBNR b KQkq - 2 5"), + "Caro–Kann_Defence#Panov.E2.80.93Botvinnik_Attack:_4.c4", + "1. e4 c6 2. d4 d5 3. exd5 cxd5 4. c4 Nf6 5. Nc3" + ), + StartingPosition( + "B17", + "Caro-Kann Defence: Steinitz Variation", + FEN("r1bqkbnr/pp1npppp/2p5/8/3PN3/8/PPP2PPP/R1BQKBNR w KQkq - 1 5"), + "Caro–Kann_Defence#Modern_Variation:_4...Nd7", + "1. e4 c6 2. d4 d5 3. Nc3 dxe4 4. Nxe4 Nd7" + ), + StartingPosition( + "C21", + "Danish Gambit", + FEN("rnbqkbnr/pppp1ppp/8/8/3pP3/2P5/PP3PPP/RNBQKBNR b KQkq - 0 3"), + "Danish_Gambit", + "1. e4 e5 2. d4 exd4 3. c3" + ), + StartingPosition( + "C46", + "Four Knights Game", + FEN("r1bqkb1r/pppp1ppp/2n2n2/4p3/4P3/2N2N2/PPPP1PPP/R1BQKB1R w KQkq - 4 4"), + "Four_Knights_Game", + "1. e4 e5 2. Nf3 Nc6 3. Nc3 Nf6" + ), + StartingPosition( + "C47", + "Four Knights Game: Scotch Variation", + FEN("r1bqkb1r/pppp1ppp/2n2n2/4p3/3PP3/2N2N2/PPP2PPP/R1BQKB1R b KQkq - 0 4"), + "Four_Knights_Game#4.d4", + "1. e4 e5 2. Nf3 Nc6 3. Nc3 Nf6 4. d4" + ), + StartingPosition( + "C48", + "Four Knights Game: Spanish Variation", + FEN("r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/2N2N2/PPPP1PPP/R1BQK2R b KQkq - 5 4"), + "Four_Knights_Game#4.Bb5", + "1. e4 e5 2. Nf3 Nf6 3. Nc3 Nc6 4. Bb5" + ), + StartingPosition( + "C00", + "French Defence", + FEN("rnbqkbnr/pppp1ppp/4p3/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "French_Defence", + "1. e4 e6" + ), + StartingPosition( + "C02", + "French Defence: Advance Variation", + FEN("rnbqkbnr/ppp2ppp/4p3/3pP3/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"), + "French_Defence#Advance_Variation:_3.e5", + "1. e4 e6 2. d4 d5 3. e5" + ), + StartingPosition( + "C11", + "French Defence: Burn Variation", + FEN("rnbqkb1r/ppp2ppp/4pn2/3p2B1/3PP3/2N5/PPP2PPP/R2QKBNR b KQkq - 0 5"), + "French_Defence#3.Nc3", + "1. e4 e6 2. d4 d5 3. Nc3 Nf6 4. Bg5 dxe4" + ), + StartingPosition( + "C11", + "French Defence: Classical Variation", + FEN("rnbqkb1r/ppp2ppp/4pn2/3p4/3PP3/2N5/PPP2PPP/R1BQKBNR w KQkq - 2 4"), + "French_Defence#Classical_Variation:_3...Nf6", + "1. e4 e6 2. d4 d5 3. Nc3 Nf6" + ), + StartingPosition( + "C01", + "French Defence: Exchange Variation", + FEN("rnbqkbnr/ppp2ppp/4p3/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"), + "French_Defence#Exchange_Variation:_3.exd5_exd5", + "1. e4 e6 2. d4 d5 3. exd5" + ), + StartingPosition( + "C10", + "French Defence: Rubinstein Variation", + FEN("rnbqkbnr/ppp2ppp/4p3/8/3Pp3/2N5/PPP2PPP/R1BQKBNR w KQkq - 0 4"), + "French_Defence#Rubinstein_Variation:_3...dxe4", + "1. e4 e6 2. d4 d5 3. Nc3 dxe4" + ), + StartingPosition( + "C03", + "French Defence: Tarrasch Variation", + FEN("rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/8/PPPN1PPP/R1BQKBNR b KQkq - 1 3"), + "French_Defence#Tarrasch_Variation:_3.Nd2", + "1. e4 e6 2. d4 d5 3. Nd2" + ), + StartingPosition( + "C15", + "French Defence: Winawer Variation", + FEN("rnbqk1nr/ppp2ppp/4p3/3p4/1b1PP3/2N5/PPP2PPP/R1BQKBNR w KQkq - 2 4"), + "French_Defence#Winawer_Variation:_3...Bb4", + "1. e4 e6 2. d4 d5 3. Nc3 Bb4" + ), + StartingPosition( + "C50", + "Giuoco Piano", + FEN("r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"), + "Giuoco_Piano", + "1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5" + ), + StartingPosition( + "C50", + "Italian Game", + FEN("r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3"), + "Italian_Game", + "1. e4 e5 2. Nf3 Nc6 3. Bc4" + ), + StartingPosition( + "C51", + "Evans Gambit", + FEN("r1bqk1nr/pppp1ppp/2n5/2b1p3/1PB1P3/5N2/P1PP1PPP/RNBQK2R b KQkq - 0 4"), + "Evans_Gambit", + "1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. b4" + ), + StartingPosition( + "C50", + "Italian Game: Hungarian Defence", + FEN("r1bqk1nr/ppppbppp/2n5/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"), + "Hungarian_Defense", + "1. e4 e5 2. Nf3 Nc6 3. Bc4 Be7" + ), + StartingPosition( + "C55", + "Italian Game: Two Knights Defence", + FEN("r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"), + "Two_Knights_Defense", + "1. e4 e5 2. Nf3 Nc6 3. Bc4 Nf6" + ), + StartingPosition( + "C30", + "King's Gambit", + FEN("rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq - 0 2"), + "King's_Gambit", + "1. e4 e5 2. f4" + ), + StartingPosition( + "C33", + "King's Gambit Accepted", + FEN("rnbqkbnr/pppp1ppp/8/8/4Pp2/8/PPPP2PP/RNBQKBNR w KQkq - 0 3"), + "King's_Gambit#King.27s_Gambit_Accepted:_2...exf4", + "1. e4 e5 2. f4 exf4" + ), + StartingPosition( + "C33", + "King's Gambit Accepted: Bishop's Gambit", + FEN("rnbqkbnr/pppp1ppp/8/8/2B1Pp2/8/PPPP2PP/RNBQK1NR b KQkq - 1 3"), + "King's_Gambit#King.27s_Gambit_Accepted:_2...exf4", + "1. e4 e5 2. f4 exf4 3. Bc4" + ), + StartingPosition( + "C36", + "King's Gambit Accepted: Modern Defence", + FEN("rnbqkbnr/ppp2ppp/8/3p4/4Pp2/5N2/PPPP2PP/RNBQKB1R w KQkq d6 0 4"), + "King's_Gambit#Modern_Defence:_3...d5", + "1. e4 e5 2. f4 exf4 3. Nf3 d5" + ), + StartingPosition( + "C30", + "King's Gambit Accepted: Classical Variation", + FEN("rnbqkbnr/pppp1p1p/8/6p1/4Pp2/5N2/PPPP2PP/RNBQKB1R w KQkq - 0 4"), + "King's_Gambit#Classical_Variation:_3...g5", + "1. e4 e5 2. f4 exf4 3. Nf3 g5" + ), + StartingPosition( + "C30", + "King's Gambit Declined: Classical Variation", + FEN("rnbqk1nr/pppp1ppp/8/2b1p3/4PP2/8/PPPP2PP/RNBQKBNR w KQkq - 1 3"), + "King's_Gambit#Classical_Defence:_2...Bc5", + "1. e4 e5 2. f4 Bc5" + ), + StartingPosition( + "C31", + "King's Gambit: Falkbeer Countergambit", + FEN("rnbqkbnr/ppp2ppp/8/3pp3/4PP2/8/PPPP2PP/RNBQKBNR w KQkq - 0 3"), + "King%27s_Gambit,_Falkbeer_Countergambit", + "1. e4 e5 2. f4 d5" + ), + StartingPosition( + "B06", + "Modern Defence", + FEN("rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "Modern_Defense", + "1. e4 g6" + ), + StartingPosition( + "B06", + "Modern Defence: Robatsch Defence", + FEN("rnbqk1nr/ppppppbp/6p1/8/3PP3/2N5/PPP2PPP/R1BQKBNR b KQkq - 2 3"), + "Modern_Defense", + "1. e4 g6 2. d4 Bg7 3. Nc3" + ), + StartingPosition( + "C41", + "Philidor Defence", + FEN("rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3"), + "Philidor_Defence", + "1. e4 e5 2. Nf3 d6" + ), + StartingPosition( + "C41", + "Philidor Defence: Lion Variation", + FEN("r1bqkb1r/pppn1ppp/3p1n2/4p3/3PP3/2N2N2/PPP2PPP/R1BQKB1R w KQkq - 2 5"), + "Philidor_Defence", + "1. e4 d6 2. d4 Nf6 3. Nc3 e5 4. Nf3 Nbd7" + ), + StartingPosition( + "B07", + "Lion Variation: Anti-Philidor", + FEN("r1bqkb1r/pppn1ppp/3p1n2/4p3/3PPP2/2N5/PPP3PP/R1BQKBNR w KQkq - 0 5"), + "Philidor_Defence", + "1. e4 d6 2. d4 Nf6 3. Nc3 Nbd7 4. f4 e5" + ), + StartingPosition( + "B07", + "Pirc Defence", + FEN("rnbqkb1r/ppp1pppp/3p1n2/8/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 2 3"), + "Pirc_Defence", + "1. e4 d6 2. d4 Nf6 3. Nc3" + ), + StartingPosition( + "B09", + "Pirc Defence: Austrian Attack", + FEN("rnbqkb1r/ppp1pp1p/3p1np1/8/3PPP2/2N5/PPP3PP/R1BQKBNR b KQkq - 0 4"), + "Pirc_Defence#Austrian_Attack:_4.f4", + "1. e4 d6 2. d4 Nf6 3. Nc3 g6 4. f4" + ), + StartingPosition( + "B07", + "Pirc Defence: Classical Variation", + FEN("rnbqkb1r/ppp1pp1p/3p1np1/8/3PP3/2N2N2/PPP2PPP/R1BQKB1R b KQkq - 1 4"), + "Pirc_Defence#Classical_.28Two_Knights.29_System:_4.Nf3", + "1. e4 d6 2. d4 Nf6 3. Nc3 g6 4. Nf3" + ), + StartingPosition( + "B07", + "Pirc Defence: Lion Variation", + FEN("r1bqkb1r/pppnpppp/3p1n2/8/3PP3/2N5/PPP2PPP/R1BQKBNR w KQkq - 3 4"), + "Pirc_Defence#Classical_.28Two_Knights.29_System", + "1. e4 d6 2. d4 Nf6 3. Nc3 Nbd7" + ), + StartingPosition( + "C42", + "Petrov's Defence", + FEN("rnbqkb1r/pppp1ppp/5n2/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 2 3"), + "Petrov's_Defence", + "1. e4 e5 2. Nf3 Nf6" + ), + StartingPosition( + "C42", + "Petrov's Defence: Classical Attack", + FEN("rnbqkb1r/ppp2ppp/3p4/8/3Pn3/5N2/PPP2PPP/RNBQKB1R b KQkq - 0 5"), + "Petrov's_Defence#3.Nxe5", + "1. e4 e5 2. Nf3 Nf6 3. Nxe5 d6 4. Nf3 Nxe4 5. d4" + ), + StartingPosition( + "C43", + "Petrov's Defence: Steinitz Attack", + FEN("rnbqkb1r/pppp1ppp/5n2/4p3/3PP3/5N2/PPP2PPP/RNBQKB1R b KQkq - 0 3"), + "Petrov's_Defence#3.d4", + "1. e4 e5 2. Nf3 Nf6 3. d4" + ), + StartingPosition( + "C42", + "Petrov's Defence: Three Knights Game", + FEN("rnbqkb1r/pppp1ppp/5n2/4p3/4P3/2N2N2/PPPP1PPP/R1BQKB1R b KQkq - 3 3"), + "Petrov's_Defence#3.Nc3", + "1. e4 e5 2. Nf3 Nf6 3. Nc3" + ), + StartingPosition( + "C60", + "Ruy Lopez", + FEN("r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3"), + "Ruy_Lopez", + "1. e4 e5 2. Nf3 Nc6 3. Bb5" + ), + StartingPosition( + "C65", + "Ruy Lopez: Berlin Defence", + FEN("r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"), + "Ruy_Lopez#Berlin_Defence:_3...Nf6", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 Nf6" + ), + StartingPosition( + "C64", + "Ruy Lopez: Classical Variation", + FEN("r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"), + "Ruy_Lopez#Classical_Defence:_3...Bc5", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 Bc5" + ), + StartingPosition( + "C84", + "Ruy Lopez: Closed Variation", + FEN("r1bqk2r/2ppbppp/p1n2n2/1p2p3/4P3/1B3N2/PPPP1PPP/RNBQR1K1 b kq - 1 7"), + "Ruy_Lopez#Main_line:_4.Ba4_Nf6_5.0-0_Be7_6.Re1_b5_7.Bb3_d6_8.c3_0-0", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3" + ), + StartingPosition( + "C68", + "Ruy Lopez: Exchange Variation", + FEN("r1bqkbnr/1ppp1ppp/p1B5/4p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 0 4"), + "Ruy_Lopez,_Exchange_Variation", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Bxc6" + ), + StartingPosition( + "C89", + "Ruy Lopez: Marshall Attack", + FEN("r1bq1rk1/2p1bppp/p1n2n2/1p1pp3/4P3/1BP2N2/PP1P1PPP/RNBQR1K1 w - - 0 9"), + "Ruy_Lopez#Marshall_Attack", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 O-O 8. c3 d5" + ), + StartingPosition( + "C63", + "Ruy Lopez: Schliemann Defence", + FEN("r1bqkbnr/pppp2pp/2n5/1B2pp2/4P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 4"), + "Ruy_Lopez#Schliemann_Defence:_3...f5", + "1. e4 e5 2. Nf3 Nc6 3. Bb5 f5" + ), + StartingPosition( + "B01", + "Scandinavian Defence", + FEN("rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "Scandinavian_Defense", + "1. e4 d5" + ), + StartingPosition( + "B01", + "Scandinavian Defence: Modern Variation", + FEN("rnbqkb1r/ppp1pppp/5n2/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"), + "Scandinavian_Defense#2...Nf6", + "1. e4 d5 2. exd5 Nf6 3. d4" + ), + StartingPosition( + "B01", + "Scandinavian Defence: Icelandic-Palme Gambit", + FEN("rnbqkb1r/ppp2ppp/4pn2/3P4/2P5/8/PP1P1PPP/RNBQKBNR w KQkq - 0 4"), + "Scandinavian_Defense#2...Nf6", + "1. e4 d5 2. exd5 Nf6 3. c4 e6" + ), + StartingPosition( + "C44", + "Scotch Game", + FEN("r1bqkbnr/pppp1ppp/2n5/4p3/3PP3/5N2/PPP2PPP/RNBQKB1R b KQkq - 0 3"), + "Scotch_Game", + "1. e4 e5 2. Nf3 Nc6 3. d4" + ), + StartingPosition( + "C45", + "Scotch Game: Classical Variation", + FEN("r1bqk1nr/pppp1ppp/2n5/2b5/3NP3/8/PPP2PPP/RNBQKB1R w KQkq - 1 5"), + "Scotch_Game,_Classical_Variation", + "1. e4 e5 2. Nf3 Nc6 3. d4 exd4 4. Nxd4 Bc5" + ), + StartingPosition( + "C45", + "Scotch Game: Mieses Variation", + FEN("r1bqkb1r/p1pp1ppp/2p2n2/4P3/8/8/PPP2PPP/RNBQKB1R b KQkq - 0 6"), + "Scotch_Game#Schmidt_Variation:_4...Nf6", + "1. e4 e5 2. Nf3 Nc6 3. d4 exd4 4. Nxd4 Nf6 5. Nxc6 bxc6 6. e5" + ), + StartingPosition( + "C45", + "Scotch Game: Steinitz Variation", + FEN("r1b1kbnr/pppp1ppp/2n5/8/3NP2q/8/PPP2PPP/RNBQKB1R w KQkq - 1 5"), + "Scotch_Game#Steinitz_Variation:_4...Qh4.21.3F", + "1. e4 e5 2. Nf3 Nc6 3. d4 exd4 4. Nxd4 Qh4" + ), + StartingPosition( + "B20", + "Sicilian Defence", + FEN("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"), + "Sicilian_Defence", + "1. e4 c5" + ), + StartingPosition( + "B36", + "Sicilian Defence: Accelerated Dragon", + FEN("r1bqkbnr/pp1ppp1p/2n3p1/8/3NP3/8/PPP2PPP/RNBQKB1R w KQkq - 0 5"), + "Sicilian_Defence,_Accelerated_Dragon", + "1. e4 c5 2. Nf3 Nc6 3. d4 cxd4 4. Nxd4 g6" + ), + StartingPosition( + "B22", + "Sicilian Defence: Alapin Variation", + FEN("rnbqkbnr/pp1ppppp/8/2p5/4P3/2P5/PP1P1PPP/RNBQKBNR b KQkq - 0 2"), + "Sicilian_Defence,_Alapin_Variation", + "1. e4 c5 2. c3" + ), + StartingPosition( + "B23", + "Sicilian Defence: Closed Variation", + FEN("rnbqkbnr/pp1ppppp/8/2p5/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2"), + "Sicilian_Defence#Closed_Sicilian", + "1. e4 c5 2. Nc3" + ), + StartingPosition( + "B70", + "Sicilian Defence: Dragon Variation", + FEN("rnbqkb1r/pp2pp1p/3p1np1/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6"), + "Sicilian_Defence,_Dragon_Variation", + "1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 g6" + ), + StartingPosition( + "B23", + "Sicilian Defence: Grand Prix Attack", + FEN("rnbqkbnr/pp1ppppp/8/2p5/4PP2/8/PPPP2PP/RNBQKBNR b KQkq - 0 2"), + "Sicilian_Defence#Grand_Prix_Attack", + "1. e4 c5 2. f4" + ), + StartingPosition( + "B27", + "Sicilian Defence: Hyper-Accelerated Dragon", + FEN("rnbqkbnr/pp1ppp1p/6p1/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3"), + "Sicilian_Defence#2...g6:_Hungarian_Variation", + "1. e4 c5 2. Nf3 g6" + ), + StartingPosition( + "B41", + "Sicilian Defence: Kan Variation", + FEN("rnbqkbnr/1p1p1ppp/p3p3/8/3NP3/8/PPP2PPP/RNBQKB1R w KQkq - 0 5"), + "Sicilian_Defence#Kan_.28Paulsen.29_Variation:_4...a6", + "1. e4 c5 2. Nf3 e6 3. d4 cxd4 4. Nxd4 a6" + ), + StartingPosition( + "B90", + "Sicilian Defence: Najdorf Variation", + FEN("rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6"), + "Sicilian_Defence,_Najdorf_Variation", + "1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 a6" + ), + StartingPosition( + "B60", + "Sicilian Defence: Richter-Rauzer Variation", + FEN("r1bqkb1r/pp2pppp/2np1n2/6B1/3NP3/2N5/PPP2PPP/R2QKB1R b KQkq - 4 6"), + "Sicilian_Defence#Classical_Variation:_5...Nc6", + "1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 Nc6 6. Bg5" + ), + StartingPosition( + "B80", + "Sicilian Defence: Scheveningen Variation", + FEN("rnbqkb1r/pp3ppp/3ppn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R w KQkq - 0 6"), + "Sicilian_Defence,_Scheveningen_Variation", + "1. e4 c5 2. Nf3 d6 3. d4 cxd4 4. Nxd4 Nf6 5. Nc3 e6" + ), + StartingPosition( + "B21", + "Sicilian Defence: Smith-Morra Gambit", + FEN("rnbqkbnr/pp1ppppp/8/8/3pP3/2P5/PP3PPP/RNBQKBNR b KQkq - 0 3"), + "Sicilian_Defence,_Smith–Morra_Gambit", + "1. e4 c5 2. d4 cxd4 3. c3" + ), + StartingPosition( + "C25", + "Vienna Game", + FEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR b KQkq - 1 2"), + "Vienna_Game", + "1. e4 e5 2. Nc3" + ), + StartingPosition( + "C27", + "Vienna Game: Frankenstein-Dracula Variation", + FEN("rnbqkb1r/pppp1ppp/8/4p3/2B1n3/2N5/PPPP1PPP/R1BQK1NR w KQkq - 0 4"), + "Frankenstein-Dracula_Variation", + "1. e4 e5 2. Nc3 Nf6 3. Bc4 Nxe4" + ), + StartingPosition( + "C46", + "Four Knights Game: Halloween Gambit", + FEN("r1bqkb1r/pppp1ppp/2n2n2/4N3/4P3/2N5/PPPP1PPP/R1BQKB1R b KQkq - 0 4"), + "Halloween_Gambit", + "1. e4 e5 2. Nf3 Nc6 3. Nc3 Nf6 4. Nxe5" + ), + StartingPosition( + "C20", + "King's Pawn Game: Wayward Queen Attack", + FEN("rnbqkbnr/pppp1ppp/8/4p2Q/4P3/8/PPPP1PPP/RNB1KBNR b KQkq - 1 2"), + "Danvers_Opening", + "1. e4 e5 2. Qh5" + ), + StartingPosition( + "C20", + "Bongcloud Attack", + FEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR b kq - 1 2"), + "Bong", + "1. e4 e5 2. Ke2", + featurable = false + ) + ) + ), + Category( + "d4", + List( + StartingPosition( + "A40", + "Queen's Pawn", + FEN("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1"), + "Queen's_Pawn_Game", + "1. d4", + featurable = false + ), + StartingPosition( + "A57", + "Benko Gambit", + FEN("rnbqkb1r/p2ppppp/5n2/1ppP4/2P5/8/PP2PPPP/RNBQKBNR w KQkq - 0 4"), + "Benko_Gambit", + "1. d4 Nf6 2. c4 c5 3. d5 b5" + ), + StartingPosition( + "A61", + "Benoni Defence: Modern Benoni", + FEN("rnbqkb1r/pp1p1ppp/4pn2/2pP4/2P5/8/PP2PPPP/RNBQKBNR w KQkq - 0 4"), + "Modern_Benoni", + "1. d4 Nf6 2. c4 c5 3. d5 e6" + ), + StartingPosition( + "A43", + "Benoni Defence: Czech Benoni", + FEN("rnbqkb1r/pp1p1ppp/5n2/2pPp3/2P5/8/PP2PPPP/RNBQKBNR w KQkq e6 0 4"), + "Benoni_Defense#Czech_Benoni:_1.d4_Nf6_2.c4_c5_3.d5_e5", + "1. d4 Nf6 2. c4 c5 3. d5 e5" + ), + StartingPosition( + "D00", + "Blackmar Gambit", + FEN("rnbqkbnr/ppp1pppp/8/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2"), + "Blackmar–Diemer_Gambit", + "1. d4 d5 2. e4" + ), + StartingPosition( + "E11", + "Bogo-Indian Defence", + FEN("rnbqk2r/pppp1ppp/4pn2/8/1bPP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 2 4"), + "Bogo-Indian_Defence", + "1. d4 Nf6 2. c4 e6 3. Nf3 Bb4+" + ), + StartingPosition( + "E00", + "Catalan Opening", + FEN("rnbqkb1r/pppp1ppp/4pn2/8/2PP4/6P1/PP2PP1P/RNBQKBNR b KQkq - 0 3"), + "Catalan_Opening", + "1. d4 Nf6 2. c4 e6 3. g3" + ), + StartingPosition( + "E06", + "Catalan Opening: Closed Variation", + FEN("rnbqk2r/ppp1bppp/4pn2/3p4/2PP4/5NP1/PP2PPBP/RNBQK2R b KQkq - 3 5"), + "Catalan_Opening", + "1. d4 Nf6 2. c4 e6 3. g3 d5 4. Nf3 Be7 5. Bg2" + ), + StartingPosition( + "A80", + "Dutch Defence", + FEN("rnbqkbnr/ppppp1pp/8/5p2/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2"), + "Dutch_Defence", + "1. d4 f5" + ), + StartingPosition( + "A96", + "Dutch Defence: Classical Variation", + FEN("rnbq1rk1/ppp1b1pp/3ppn2/5p2/2PP4/5NP1/PP2PPBP/RNBQ1RK1 w - - 0 7"), + "Dutch_Defence", + "1. d4 f5 2. c4 Nf6 3. g3 e6 4. Bg2 Be7 5. Nf3 O-O 6. O-O d6" + ), + StartingPosition( + "A87", + "Dutch Defence: Leningrad Variation", + FEN("rnbqk2r/ppppp1bp/5np1/5p2/2PP4/5NP1/PP2PPBP/RNBQK2R b KQkq - 3 5"), + "Dutch_Defence", + "1. d4 f5 2. c4 Nf6 3. g3 g6 4. Bg2 Bg7 5. Nf3" + ), + StartingPosition( + "A83", + "Dutch Defence: Staunton Gambit", + FEN("rnbqkb1r/ppppp1pp/5n2/6B1/3Pp3/2N5/PPP2PPP/R2QKBNR b KQkq - 3 4"), + "Dutch_Defence", + "1. d4 f5 2. e4 fxe4 3. Nc3 Nf6 4. Bg5" + ), + StartingPosition( + "A92", + "Dutch Defence: Stonewall Variation", + FEN("rnbq1rk1/ppp1b1pp/4pn2/3p1p2/2PP4/5NP1/PP2PPBP/RNBQ1RK1 w - - 0 7"), + "Dutch_Defence", + "1. d4 f5 2. c4 Nf6 3. g3 e6 4. Bg2 Be7 5. Nf3 O-O 6. O-O d5" + ), + StartingPosition( + "D80", + "Grünfeld Defence", + FEN("rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 0 4"), + "Grünfeld_Defence", + "1. d4 Nf6 2. c4 g6 3. Nc3 d5" + ), + StartingPosition( + "D82", + "Grünfeld Defence: Brinckmann Attack", + FEN("rnbqkb1r/ppp1pp1p/5np1/3p4/2PP1B2/2N5/PP2PPPP/R2QKBNR b KQkq - 1 4"), + "Grünfeld_Defence#Lines_with_4.Bf4_and_the_Gr.C3.BCnfeld_Gambit", + "1. d4 Nf6 2. c4 g6 3. Nc3 d5 4. Bf4" + ), + StartingPosition( + "D85", + "Grünfeld Defence: Exchange Variation", + FEN("rnbqkb1r/ppp1pp1p/6p1/3n4/3P4/2N5/PP2PPPP/R1BQKBNR w KQkq - 0 5"), + "Grünfeld_Defence#Exchange_Variation:_4.cxd5_Nxd5_5.e4", + "1. d4 Nf6 2. c4 g6 3. Nc3 d5 4. cxd5 Nxd5" + ), + StartingPosition( + "D80", + "Grünfeld Defence: Russian Variation", + FEN("rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/1QN5/PP2PPPP/R1B1KBNR b KQkq - 1 4"), + "Grünfeld_Defence#Russian_System:_4.Nf3_Bg7_5.Qb3", + "1. d4 Nf6 2. c4 g6 3. Nc3 d5 4. Qb3" + ), + StartingPosition( + "D90", + "Grünfeld Defence: Taimanov Variation", + FEN("rnbqk2r/ppp1ppbp/5np1/3p2B1/2PP4/2N2N2/PP2PPPP/R2QKB1R b KQkq - 3 5"), + "Grünfeld_Defence#Taimanov.27s_Variation_with_4.Nf3_Bg7_5.Bg5", + "1. d4 Nf6 2. c4 g6 3. Nc3 d5 4. Nf3 Bg7 5. Bg5" + ), + StartingPosition( + "E61", + "King's Indian Defence", + FEN("rnbqkb1r/pppppp1p/5np1/8/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "King's_Indian_Defence", + "1. d4 Nf6 2. c4 g6" + ), + StartingPosition( + "E77", + "King's Indian Defence: 4.e4", + FEN("rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N5/PP3PPP/R1BQKBNR w KQkq - 0 5"), + "King's_Indian_Defence", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6" + ), + StartingPosition( + "E73", + "King's Indian Defence: Averbakh Variation", + FEN("rnbq1rk1/ppp1ppbp/3p1np1/6B1/2PPP3/2N5/PP2BPPP/R2QK1NR b KQ - 3 6"), + "King's_Indian_Defence#Averbakh_Variation:_5.Be2_0-0_6.Bg5", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. Be2 O-O 6. Bg5" + ), + StartingPosition( + "E62", + "King's Indian Defence: Fianchetto Variation", + FEN("rnbqk2r/ppp1ppbp/3p1np1/8/2PP4/2N2NP1/PP2PP1P/R1BQKB1R b KQkq - 0 5"), + "King's_Indian_Defence#Fianchetto_Variation:_3.Nf3_Bg7_4.g3", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. Nf3 d6 5. g3" + ), + StartingPosition( + "E76", + "King's Indian Defence: Four Pawns Attack", + FEN("rnbqk2r/ppp1ppbp/3p1np1/8/2PPPP2/2N5/PP4PP/R1BQKBNR b KQkq - 0 5"), + "King%27s_Indian_Defence,_Four_Pawns_Attack", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. f4" + ), + StartingPosition( + "E91", + "King's Indian Defence: Classical Variation", + FEN("rnbq1rk1/ppp1ppbp/3p1np1/8/2PPP3/2N2N2/PP2BPPP/R1BQK2R b KQ - 3 6"), + "King's_Indian_Defence#Classical_Variation:_5.Nf3_0-0_6.Be2_e5", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. Nf3 O-O 6. Be2" + ), + StartingPosition( + "E80", + "King's Indian Defence: Sämisch Variation", + FEN("rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N2P2/PP4PP/R1BQKBNR b KQkq - 0 5"), + "King's_Indian_Defence#S.C3.A4misch_Variation:_5.f3", + "1. d4 Nf6 2. c4 g6 3. Nc3 Bg7 4. e4 d6 5. f3" + ), + StartingPosition( + "A41", + "Queens's Pawn Game: Modern Defence", + FEN("rnbqk1nr/ppp1ppbp/3p2p1/8/2PP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 2 4"), + "Queen's_Pawn_Game#1...g6", + "1. d4 g6 2. c4 d6 3. Nc3 Bg7" + ), + StartingPosition( + "E20", + "Nimzo-Indian Defence", + FEN("rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 2 4"), + "Nimzo-Indian_Defence", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4" + ), + StartingPosition( + "E32", + "Nimzo-Indian Defence: Classical Variation", + FEN("rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N5/PPQ1PPPP/R1B1KBNR b KQkq - 3 4"), + "Nimzo-Indian_Defence#Classical_Variation:_4.Qc2", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. Qc2" + ), + StartingPosition( + "E43", + "Nimzo-Indian Defence: Fischer Variation", + FEN("rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 5"), + "Nimzo-Indian_Defence#4...b6", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. e3 b6" + ), + StartingPosition( + "E41", + "Nimzo-Indian Defence: Hübner Variation", + FEN("r1bqk2r/pp3ppp/2nppn2/2p5/2PP4/2PBPN2/P4PPP/R1BQK2R w KQkq - 0 8"), + "Nimzo-Indian_Defence#4...c5", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. e3 c5 5. Bd3 Nc6 6. Nf3 Bxc3+ 7. bxc3 d6" + ), + StartingPosition( + "E21", + "Nimzo-Indian Defence: Kasparov Variation", + FEN("rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N2N2/PP2PPPP/R1BQKB1R b KQkq - 3 4"), + "Nimzo-Indian_Defence#Kasparov_Variation:_4.Nf3", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. Nf3" + ), + StartingPosition( + "E30", + "Nimzo-Indian Defence: Leningrad Variation", + FEN("rnbqk2r/pppp1ppp/4pn2/6B1/1bPP4/2N5/PP2PPPP/R2QKBNR b KQkq - 3 4"), + "Nimzo-Indian_Defence#Other_variations", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. Bg5" + ), + StartingPosition( + "E26", + "Nimzo-Indian Defence: Sämisch Variation", + FEN("rnbqk2r/pppp1ppp/4pn2/8/2PP4/P1P5/4PPPP/R1BQKBNR b KQkq - 0 5"), + "Nimzo-Indian_Defence#Other_variations", + "1. d4 Nf6 2. c4 e6 3. Nc3 Bb4 4. a3 Bxc3+ 5. bxc3" + ), + StartingPosition( + "A53", + "Old Indian Defence", + FEN("rnbqkb1r/ppp1pppp/3p1n2/8/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "Old_Indian_Defense", + "1. d4 Nf6 2. c4 d6" + ), + StartingPosition( + "D06", + "Queen's Gambit", + FEN("rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq - 0 2"), + "Queen's_Gambit", + "1. d4 d5 2. c4" + ), + StartingPosition( + "D20", + "Queen's Gambit Accepted", + FEN("rnbqkbnr/ppp1pppp/8/8/2pP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "Queen%27s_Gambit_Accepted", + "1. d4 d5 2. c4 dxc4" + ), + StartingPosition( + "D43", + "Queen's Gambit Declined: Semi-Slav Defence", + FEN("rnbqkb1r/pp3ppp/2p1pn2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R w KQkq - 0 5"), + "Semi-Slav_Defense", + "1. d4 d5 2. c4 e6 3. Nc3 Nf6 4. Nf3 c6" + ), + StartingPosition( + "D10", + "Queen's Gambit Declined: Slav Defence", + FEN("rnbqkbnr/pp2pppp/2p5/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "Slav_Defense", + "1. d4 d5 2. c4 c6" + ), + StartingPosition( + "D40", + "Queen's Gambit Declined: Semi-Tarrasch Defence", + FEN("rnbqkb1r/pp3ppp/4pn2/2pp4/2PP4/2N2N2/PP2PPPP/R1BQKB1R w KQkq - 0 5"), + "Tarrasch_Defense#Semi-Tarrasch_Defense", + "1. d4 d5 2. c4 e6 3. Nc3 Nf6 4. Nf3 c5" + ), + StartingPosition( + "D32", + "Queen's Gambit Declined: Tarrasch Defence", + FEN("rnbqkbnr/pp3ppp/4p3/2pp4/2PP4/2N5/PP2PPPP/R1BQKBNR w KQkq - 0 4"), + "Tarrasch_Defense", + "1. d4 d5 2. c4 e6 3. Nc3 c5" + ), + StartingPosition( + "D08", + "Queen's Gambit: Albin Countergambit", + FEN("rnbqkbnr/ppp2ppp/8/3pp3/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "Albin_Countergambit", + "1. d4 d5 2. c4 e5" + ), + StartingPosition( + "D07", + "Queen's Gambit: Chigorin Defence", + FEN("r1bqkbnr/ppp1pppp/2n5/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 1 3"), + "Chigorin_Defense", + "1. d4 d5 2. c4 Nc6" + ), + StartingPosition( + "E12", + "Queen's Indian Defence", + FEN("rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 0 4"), + "Queen's_Indian_Defense", + "1. d4 Nf6 2. c4 e6 3. Nf3 b6" + ), + StartingPosition( + "D02", + "London System", + FEN("rnbqkb1r/ppp1pppp/5n2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3"), + "London_System", + "1. d4 d5 2. Nf3 Nf6 3. Bf4" + ), + StartingPosition( + "D00", + "London System: Mason Attack", + FEN("rnbqkbnr/ppp1pppp/8/3p4/3P1B2/8/PPP1PPPP/RN1QKBNR b KQkq - 1 2"), + "London_System", + "1. d4 d5 2. Bf4" + ), + StartingPosition( + "D01", + "Rapport-Jobava System", + FEN("rnbqkb1r/ppp1pppp/5n2/3p4/3P1B2/2N5/PPP1PPPP/R2QKBNR b KQkq - 3 3"), + "London_System", + "1. d4 d5 2. Nc3 Nf6 3. Bf4" + ), + StartingPosition( + "D03", + "Torre Attack", + FEN("rnbqkb1r/ppp1pppp/5n2/3p2B1/3P4/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3"), + "Torre_Attack", + "1. d4 d5 2. Nf3 Nf6 3. Bg5" + ), + StartingPosition( + "D01", + "Richter-Veresov Attack", + FEN("rnbqkb1r/ppp1pppp/5n2/3p2B1/3P4/2N5/PPP1PPPP/R2QKBNR b KQkq - 3 3"), + "Richter-Veresov_Attack", + "1. d4 d5 2. Nc3 Nf6 3. Bg5" + ), + StartingPosition( + "A52", + "Budapest Defence", + FEN("rnbqkb1r/pppp1ppp/5n2/4p3/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 3"), + "Budapest_Gambit", + "1. d4 Nf6 2. c4 e5", + featurable = false + ), + StartingPosition( + "D00", + "Closed Game", + FEN("rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 2"), + "Closed_Game", + "1. d4 d5", + featurable = false + ), + StartingPosition( + "A45", + "Trompowsky Attack", + FEN("rnbqkb1r/pppppppp/5n2/6B1/3P4/8/PPP1PPPP/RN1QKBNR b KQkq - 2 2"), + "Trompowsky_Attack", + "1. d4 Nf6 2. Bg5" + ) + ) + ), + Category( + "Nf3", + List( + StartingPosition( + "A04", + "Zukertort Opening", + FEN("rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1"), + "Zukertort_Opening", + "1. Nf3", + featurable = false + ), + StartingPosition( + "A07", + "King's Indian Attack", + FEN("rnbqkbnr/ppp1pppp/8/3p4/8/5NP1/PPPPPP1P/RNBQKB1R b KQkq - 0 2"), + "King's_Indian_Attack", + "1. Nf3 d5 2. g3" + ), + StartingPosition( + "A09", + "Réti Opening", + FEN("rnbqkbnr/ppp1pppp/8/3p4/2P5/5N2/PP1PPPPP/RNBQKB1R b KQkq - 0 2"), + "Réti_Opening", + "1. Nf3 d5 2. c4" + ) + ) + ), + Category( + "c4", + List( + StartingPosition( + "A10", + "English Opening", + FEN("rnbqkbnr/pppppppp/8/8/2P5/8/PP1PPPPP/RNBQKBNR b KQkq - 0 1"), + "English_Opening", + "1. c4", + featurable = false + ), + StartingPosition( + "A20", + "English Opening: Reversed Sicilian", + FEN("rnbqkbnr/pppp1ppp/8/4p3/2P5/8/PP1PPPPP/RNBQKBNR w KQkq - 0 2"), + "English_Opening", + "1. c4 e5" + ), + StartingPosition( + "A30", + "English Opening: Symmetrical Variation", + FEN("rnbqkbnr/pp1ppppp/8/2p5/2P5/8/PP1PPPPP/RNBQKBNR w KQkq - 0 2"), + "English_Opening", + "1. c4 c5" + ), + StartingPosition( + "A26", + "English Opening: Closed System", + FEN("r1bqk1nr/ppp2pbp/2np2p1/4p3/2P5/2NP2P1/PP2PPBP/R1BQK1NR w KQkq - 0 6"), + "English_Opening", + "1. c4 e5 2. Nc3 Nc6 3. g3 g6 4. Bg2 Bg7 5. d3 d6" + ) + ) + ), + Category( + "b3", + List( + StartingPosition( + "A01", + "Nimzo-Larsen Attack", + FEN("rnbqkbnr/pppppppp/8/8/8/1P6/P1PPPPPP/RNBQKBNR b KQkq - 0 1"), + "Larsen's_Opening", + "1. b3", + featurable = false + ) + ) + ), + Category( + "b4", + List( + StartingPosition( + "A00", + "Sokolsky Opening", + FEN("rnbqkbnr/pppppppp/8/8/1P6/8/P1PPPPPP/RNBQKBNR b KQkq - 0 1"), + "Sokolsky_Opening", + "1. b4", + featurable = false + ) + ) + ), + Category( + "f4", + List( + StartingPosition( + "A02", + "Bird's Opening", + FEN("rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR b KQkq - 0 1"), + "Bird's_Opening", + "1. f4" + ), + StartingPosition( + "A02", + "Bird's Opening: Dutch Variation", + FEN("rnbqkbnr/ppp1pppp/8/3p4/5P2/8/PPPPP1PP/RNBQKBNR w KQkq - 0 2"), + "Bird's_Opening", + "1. f4 d5" + ) + ) + ), + Category( + "g3", + List( + StartingPosition( + "A00", + "Hungarian Opening", + FEN("rnbqkbnr/pppppppp/8/8/8/6P1/PPPPPP1P/RNBQKBNR b KQkq - 0 1"), + "King's_Fianchetto_Opening", + "1. g3", + featurable = false + ) + ) + ) + ) + + val all: IndexedSeq[StartingPosition] = categories.flatMap(_.positions).toIndexedSeq + + val initial = StartingPosition("---", "Initial position", format.Forsyth.initial, "Chess", "") + + def allWithInitial = initial +: all + + lazy val featurable = new scala.util.Random(475591).shuffle(all.filter(_.featurable)) + + def randomFeaturable = featurable(scala.util.Random.nextInt(featurable.size)) + + object presets { + val halloween = StartingPosition( + "C47", + "Halloween Gambit", + FEN("r1bqkb1r/pppp1ppp/2n2n2/4N3/4P3/2N5/PPPP1PPP/R1BQKB1R b KQkq - 0 4"), + "Halloween_Gambit", + "1. e4 e5 2. Nf3 Nc6 3. Nc3 Nf6 4. Nxe5" + ) + val frankenstein = StartingPosition( + "C27", + "Frankenstein-Dracula Variation", + FEN("rnbqkb1r/pppp1ppp/8/4p3/2B1n3/2N5/PPPP1PPP/R1BQK1NR w KQkq - 0 4"), + "Frankenstein-Dracula_Variation", + "1. e4 e5 2. Nc3 Nf6 3. Bc4 Nxe4" + ) + } +} diff --git a/src/main/scala/dameo/format/FEN.scala b/src/main/scala/dameo/format/FEN.scala new file mode 100644 index 000000000..c8b6dc1ad --- /dev/null +++ b/src/main/scala/dameo/format/FEN.scala @@ -0,0 +1,31 @@ +package strategygames.dameo.format + +import strategygames.Player +import strategygames.dameo.PieceMap + +//TODO Dameo, work out the FEN structure for a Dameo game. +//What do other platforms use? +//Is there a standard for Dameo? +//Are we able to use something similar to Draughts FENs (look in there) +//Or do we want to invent our own, and have it more similar to chess? +//Either way any FEN parsing wants to be done here. Look at other game logic FEN files +final case class FEN(value: String) extends AnyVal { + + override def toString = value + + def player: Option[Player] = + value.split(':').lift(0) flatMap (_.headOption) flatMap Player.apply + + def initial = value == Forsyth.initial.value + + // TODO Dameo set + def pieces: PieceMap = Map.empty +} + +object FEN { + + def clean(source: String): FEN = FEN(source.replace("_", " ").trim) + + def fullMoveIndex: Int = 4 + +} diff --git a/src/main/scala/dameo/format/Forsyth.scala b/src/main/scala/dameo/format/Forsyth.scala new file mode 100644 index 000000000..da765ab47 --- /dev/null +++ b/src/main/scala/dameo/format/Forsyth.scala @@ -0,0 +1,171 @@ +package strategygames.dameo +package format + +import cats.implicits._ + +import scala.util.Try + +import scala.annotation.nowarn + +import strategygames.Player + +import variant.{ Dameo, Variant } + +//TODO Dameo Need to rewrite a lot of this +//Most of this has been copied out of Draughts gamelogic +//To demonstrate what goes on here, see also FEN.scala + +/** Transform a game to draughts Forsyth Edwards Notation + * https://en.wikipedia.org/wiki/Portable_Draughts_Notation Additions: Piece role G/P = Ghost man or king of + * that player, has been captured but not removed because the forced capture sequence is not finished yet + * ":Hx" = Halfmove clock: This is the number of halfmoves since a forced draw material combination appears. + * This is used to determine if a draw can be claimed. ":Fx" = Fullmove number: The number of the full move. + * It starts at 1, and is incremented after P2's move. + */ +object Forsyth { + + val initial = + FEN( + "W:W31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50:B1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20:H0:F1" + ) + val initialPieces = + FEN( + "W31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50:B1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" + ) + val initialMoveAndPieces = + FEN( + "W:W31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50:B1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" + ) + + private def parseIntOption(str: String): Option[Int] = + Try(Integer.parseInt(str)).toOption + + def <<@(variant: Variant, fen: FEN): Option[Situation] = + makeBoard(variant, fen) map { board => + val situation = Player.apply(fen.value.charAt(0)) match { + case Some(player) => Situation(board, player) + case _ => Situation(board, P1) + } + + situation withHistory { + History( + positionHashes = Array.empty + ) + } + + } + + def <<(fen: FEN): Option[Situation] = <<@(Dameo, fen) + + case class SituationPlus(situation: Situation, fullTurnCount: Int) { + + def turnCount = fullTurnCount * 2 - (if (situation.player.p1) 2 else 1) + // when we convert draughts to multiaction we should consider setting this + // we may be able to deprecate this at that point as actions.flatten.size should count plies + def plies = turnCount + + } + + def <<<@(variant: Variant, fen: FEN): Option[SituationPlus] = + <<@(variant, fen) map { sit => + val splitted = fen.value.split(':') + val fullMoveNumber = splitted find { s => s.length > 1 && s.charAt(0) == 'F' } flatMap { s => + parseIntOption(s drop 1) + } map (_ max 1 min 500) + val halfMoveClock = splitted find { s => s.length > 1 && s.charAt(0) == 'H' } flatMap { s => + parseIntOption(s drop 1) + } map (_ max 0 min 100) + SituationPlus( + halfMoveClock.map(sit.history.setHalfMoveClock).fold(sit)(sit.withHistory), + fullMoveNumber | 1 + ) + } + + def <<<(fen: FEN): Option[SituationPlus] = <<<@(Dameo, fen) + + @nowarn def makeBoard(variant: Variant, fen: FEN): Option[Board] = None + + def toAlgebraic(variant: Variant, fen: FEN): Option[FEN] = + <<<@(variant, fen) map { case parsed @ SituationPlus(situation, _) => + doExport( + Game(situation, plies = parsed.plies, turnCount = parsed.turnCount), + algebraic = true + ) + } + + def countGhosts(fen: FEN): Int = + fen.value.split(':').filter(_.nonEmpty).foldLeft(0) { (ghosts, line) => + Player.apply(line.charAt(0)).fold(ghosts) { _ => + line.drop(1).split(',').foldLeft(ghosts) { (lineGhosts, field) => + if (field.nonEmpty && "GP".indexOf(field.charAt(0)) != -1) lineGhosts + 1 else lineGhosts + } + } + } + + def countKings(fen: FEN): Int = + fen.value.split(':').filter(_.nonEmpty).foldLeft(0) { (kings, line) => + Player.apply(line.charAt(0)).fold(kings) { _ => + line.drop(1).split(',').foldLeft(kings) { (lineKings, field) => + if (field.nonEmpty && field.charAt(0) == 'K') lineKings + 1 else lineKings + } + } + } + + def >>(situation: Situation): FEN = >>(SituationPlus(situation, 1)) + + def >>(parsed: SituationPlus): FEN = parsed match { + case SituationPlus(situation, _) => + >>(Game(situation, plies = parsed.plies, turnCount = parsed.turnCount)) + } + + def >>(game: Game): FEN = doExport(game, algebraic = false) + + private def doExport(game: Game, algebraic: Boolean): FEN = FEN { + { + List( + game.player.letter.toUpper.toString, + exportBoard(game.board, algebraic), + "H" + game.halfMoveClock.toString, + "F" + game.fullTurnCount.toString + ) + } mkString ":" + } + + def exportStandardPositionTurn(board: Board, ply: Int): String = List( + Player(ply % 2 == 0).letter.toString, + exportBoard(board) + ) mkString ":" + + def exportKingMoves(board: Board) = board.history.kingMoves match { + case KingMoves(p1, p2, p1King, p2King) => + s"+$p2${p2King.fold("")(_.toString)}+$p1${p1King.fold("")(_.toString)}" + } + + @nowarn def exportBoard(board: Board, algebraic: Boolean = false): String = "" + + def boardAndPlayer(situation: Situation): String = + boardAndPlayer(situation.board, situation.player) + + def boardAndPlayer(board: Board, turnPlayer: Player): String = + s"${turnPlayer.letter.toUpper}:${exportBoard(board)}" + + @nowarn def compressedBoard(board: Board): String = "" + + def exportScanPosition(sit: Option[Situation]): String = sit.fold("")(_ => "") + + def shorten(fen: FEN): FEN = { + val fen2 = if (fen.value.endsWith(":+0+0")) fen.value.dropRight(5) else fen.value + if (fen2.endsWith(":H0:F1")) FEN(fen2.dropRight(6)) else FEN(fen2) + } + + def getFullMove(fen: FEN): Option[Int] = + fen.value.split(':') filter (s => s.length > 1 && s.charAt(0) == 'F') lift 0 flatMap parseIntOption + + def getPlayer(fen: FEN): Option[Player] = fen.value lift 0 flatMap Player.apply + + def getPly(fen: FEN): Option[Int] = + getFullMove(fen) map { fullMove => + fullMove * 2 - (if (getPlayer(fen).exists(_.p1)) 2 else 1) + } + +} diff --git a/src/main/scala/dameo/format/Uci.scala b/src/main/scala/dameo/format/Uci.scala new file mode 100644 index 000000000..adbf7f80b --- /dev/null +++ b/src/main/scala/dameo/format/Uci.scala @@ -0,0 +1,120 @@ +package strategygames.dameo +package format + +import cats.data.Validated +import cats.implicits._ + +sealed trait Uci { + + def uci: String + def shortUci: String + def piotr: String + + // def toSan: String + // def toFullSan: String + + def origDest: Option[(Pos, Pos)] + + def apply(situation: Situation): Validated[String, Move] + +} + +object Uci { + + case class Move( + orig: Pos, + dest: Pos, + promotion: Option[PromotableRole] = None, + capture: Option[Pos] = None + ) extends Uci { + + def keys = orig.key + dest.key + // TODO Dameo set these properly + def uci = orig.key + (if (capture.isEmpty) "" else "x") + dest.key + def shortUci = orig.key + (if (capture.isEmpty) "" else "x") + dest.key + + def keysPiotr = orig.piotrStr + dest.piotrStr + def piotr = keysPiotr + promotionString + + def promotionString = promotion.fold("")(_.forsyth.toString) + + def origDest = Some(orig -> dest) + + def apply(situation: Situation) = + situation.move(orig, dest, promotion, capture) + + } + + object Move { + + def apply(move: String): Option[Move] = { + move match { + case moveR(orig, capture, dest) => + ( + Pos.fromKey(orig), + Pos.fromKey(dest), + // TODO Dameo set this + capture.length == 1 + ) match { + case (Some(orig), Some(dest), capture) => + Some( + Move( + orig = orig, + dest = dest, + // TODO Dameo set this + promotion = None, + capture = if (capture) Some(orig) else None + ) + ) + case _ => None + } + case _ => None + } + } + + // TODO Dameo use capture and promotion properly + def piotr(move: String) = for { + orig <- move.headOption.flatMap(Pos.piotr) + dest <- move.lift(1).flatMap(Pos.piotr) + capture <- move.lift(2).nonEmpty.some + } yield Move(orig, dest, None, (if (capture) Some(orig) else None)) + + // TODO Dameo use capture and promotion properly + def fromStrings(origS: String, destS: String, promS: Option[String]) = for { + orig <- Pos.fromKey(origS) + dest <- Pos.fromKey(destS) + promotion = Role promotable promS + } yield Move(orig, dest, promotion, None) + + // TODO Dameo write the regex for each move. Do we need to add in promotion? + val moveR = s"^${Pos.posR}([x]?)${Pos.posR}".r + + } + + case class WithSan(uci: Uci.Move, san: String) + + def apply(move: strategygames.dameo.Move, withCaptures: Boolean) = + Uci.Move(move.orig, move.dest, move.promotion, if (withCaptures) move.capture else none) + + def combine(uci1: Uci.Move, uci2: Uci.Move) = + apply(uci1.uci + uci2.uci.drop(2)).getOrElse(Uci.Move(uci1.orig, uci2.dest)) + def combineSan(san1: String, san2: String) = + san1.substring(0, san1.indexOf('x')) + san2.substring(san2.indexOf('x')) + + def apply(move: String): Option[Uci] = Uci.Move(move) + + def piotr(move: String): Option[Uci] = Uci.Move.piotr(move) + + def readList(moves: String): Option[List[Uci]] = + moves.split(' ').toList.map(apply).sequence + + def writeList(moves: List[Uci]): String = + moves.map(_.uci) mkString " " + + def readListPiotr(moves: String): Option[List[Uci]] = + moves.split(' ').toList.map(piotr).sequence + + def writeListPiotr(moves: List[Uci]): String = + moves.map(_.piotr) mkString " " + +} diff --git a/src/main/scala/dameo/format/UciCharPair.scala b/src/main/scala/dameo/format/UciCharPair.scala new file mode 100644 index 000000000..714a0f6e4 --- /dev/null +++ b/src/main/scala/dameo/format/UciCharPair.scala @@ -0,0 +1,42 @@ +package strategygames.dameo +package format + +object UciCharPair { + + import implementation._ + import strategygames.format.{ UciCharPair => stratUciCharPair } + + def apply(uci: Uci): stratUciCharPair = + stratUciCharPair(toChar(uci.origDest.map(_._1)), toChar(uci.origDest.map(_._2))) + def apply(uci: Uci, ambiguity: Int): stratUciCharPair = + stratUciCharPair(toChar(uci.origDest.map(_._1)), ambiguity2charMap.getOrElse(ambiguity, voidChar)) + def apply(orig: Char, ambiguity: Int): stratUciCharPair = + stratUciCharPair(orig, ambiguity2charMap.getOrElse(ambiguity, voidChar)) + + def combine(uci1: Uci, uci2: Uci): stratUciCharPair = + stratUciCharPair(toChar(uci1.origDest.map(_._1)), toChar(uci2.origDest.map(_._2))) + + private[format] object implementation { + + type File = Int + + val charShift = 35 // Start at Char(35) == '#' + val voidChar = 33.toChar // '!'. We skipped Char(34) == '"'. + + val pos2charMap: Map[Pos, Char] = Pos.all + .map { pos => + pos -> (pos.hashCode + charShift).toChar + } + .to(Map) + + def toChar(pos: Option[Pos]) = + pos.map(p => pos2charMap.getOrElse(p, voidChar)).getOrElse(voidChar) + + /** Allow for 50 ambiguities per destination, should be enough + */ + val ambiguity2charMap: Map[Int, Char] = (for { + ambNr <- 1 to 50 + } yield ambNr -> (charShift + pos2charMap.size + ambNr).toChar).to(Map) + + } +} diff --git a/src/main/scala/dameo/format/UciDump.scala b/src/main/scala/dameo/format/UciDump.scala new file mode 100644 index 000000000..07522f03e --- /dev/null +++ b/src/main/scala/dameo/format/UciDump.scala @@ -0,0 +1,28 @@ +package strategygames.dameo +package format + +import scala.annotation.nowarn + +import cats.data.Validated + +import strategygames.dameo.variant.Variant +import strategygames.ActionStrs + +object UciDump { + + def apply(replay: Replay): ActionStrs = + replay.chronoActions.map(_.map(action(replay.setup.board.variant))) + + def apply( + actionStrs: ActionStrs, + initialFen: Option[FEN], + variant: Variant + ): Validated[String, ActionStrs] = + if (actionStrs.isEmpty) Validated.valid(Nil) + else Replay(actionStrs, initialFen, variant) andThen (_.valid) map apply + + def action(@nowarn _variant: Variant)(action: Action): String = action match { + case m: Move => m.toUci.shortUci + } + +} diff --git a/src/main/scala/dameo/format/pdn/Binary.scala b/src/main/scala/dameo/format/pdn/Binary.scala new file mode 100644 index 000000000..52a2286bf --- /dev/null +++ b/src/main/scala/dameo/format/pdn/Binary.scala @@ -0,0 +1,124 @@ +package strategygames.dameo +package format.pdn + +import scala.util.Try +import strategygames.ActionStrs + +object Binary { + + def writeMove(m: String) = Try(Writer ply m) + def writeMoves(ms: Iterable[String]) = Try(Writer plies ms) + + def writeActionStrs(as: ActionStrs) = Try(Writer actionStrs as) + + def readActionStrs(bs: List[Byte]) = Try(Reader actionStrs bs) + def readActionStrs(bs: List[Byte], nb: Int) = Try(Reader.actionStrs(bs, nb)) + + private object MoveType { + val IsMove = 0 + val IsCapture = 1 + val Delimiter = 3 + } + + private object Delimiter { + val str = "" + val int = 255 + } + + private object Reader { + + // If changing this, consider changing other gamelogics and also lila game maxPlies + private val maxPlies = 1000 + + def actionStrs(bs: List[Byte]): ActionStrs = actionStrs(bs, maxPlies) + def actionStrs(bs: List[Byte], nb: Int): ActionStrs = toActionStrs(intPlies(bs map toInt, nb, "x00")) + + def toActionStrs(plies: List[String]): ActionStrs = + if (plies.contains(Delimiter.str)) unflatten(plies) + else plies.map(List(_)) + + def unflatten(plies: List[String]): List[List[String]] = + if (plies.size == 0) List() + else plies.takeWhile(_ != Delimiter.str) :: unflatten(plies.dropWhile(_ != Delimiter.str).drop(1)) + + private def intPlies(bs: List[Int], pliesToGo: Int, lastUci: String): List[String] = bs match { + case _ if pliesToGo < 0 => Nil + case Nil => Nil + case b1 :: rest if moveType(b1) == MoveType.Delimiter => + Delimiter.str :: intPlies(rest, pliesToGo, "x00") + case b1 :: b2 :: rest if moveType(b1) == MoveType.IsMove => + if (pliesToGo == 0) + Nil + else + moveUci(b1, b2) :: intPlies(rest, pliesToGo - 1, "x00") + case b1 :: b2 :: rest if moveType(b1) == MoveType.IsCapture => + val newUci = captureUci(b1, b2) + if (lastUci.endsWith("x" + newUci.substring(0, newUci.indexOf('x')))) + newUci :: intPlies(rest, pliesToGo, newUci) + else if (pliesToGo == 0) + Nil + else + newUci :: intPlies(rest, pliesToGo - 1, newUci) + case x => !!(x map showByte mkString ",") + } + + // 255 => 11111111 => marker for end of turn. This makes movetype == 3 => Delimiter + + // 2 movetype + // 6 srcPos + // ---- + // 2 NOTHING + // 6 dstPos + def moveUci(b1: Int, b2: Int): String = s"${right(b1, 6)}-${right(b2, 6)}" + def captureUci(b1: Int, b2: Int): String = s"${right(b1, 6)}x${right(b2, 6)}" + + private def moveType(i: Int) = i >> 6 + + private def right(i: Int, x: Int): Int = i & lengthMasks(x) + private val lengthMasks = + Map(1 -> 0x01, 2 -> 0x03, 3 -> 0x07, 4 -> 0x0f, 5 -> 0x1f, 6 -> 0x3f, 7 -> 0x7f, 8 -> 0xff) + private def !!(msg: String) = throw new Exception("Binary reader failed: " + msg) + + // private def cut(i: Int, from: Int, to: Int): Int = right(i, from) >> to + // private def bitAt(i: Int, p: Int): Boolean = cut(i, p, p - 1) != 0 + + } + + private object Writer { + + def ply(str: String): List[Byte] = (str match { + case Delimiter.str => List(Delimiter.int) + case MoveUciR(src, dst) => moveUci(src, dst) + case CaptureUciR(src, dst) => captureUci(src, dst) + case _ => + // TODO: log? + // dameoLog("ERROR: Binary").info(s"Cannot encode $str") + Nil + }) map (_.toByte) + + def plies(strs: Iterable[String]): Array[Byte] = strs.flatMap(ply).to(Array) + + def actionStrs(strs: ActionStrs): Array[Byte] = + if (strs.size == 0 || strs.map(_.size).max == 1) plies(strs.flatten) + else plies(strs.toList.map(_.toList :+ "").flatten) + + def moveUci(src: String, dst: String) = List( + (MoveType.IsMove << 6) + src.toInt, + dst.toInt + ) + + def captureUci(src: String, dst: String) = List( + (MoveType.IsCapture << 6) + src.toInt, + dst.toInt + ) + + val fieldR = "(\\d+)" + val MoveUciR = s"$fieldR-$fieldR$$".r + val CaptureUciR = s"${fieldR}x$fieldR$$".r + + } + + @inline private def toInt(b: Byte): Int = b & 0xff + private def showByte(b: Int): String = "%08d" format (b.toBinaryString.toInt) + +} diff --git a/src/main/scala/dameo/format/pdn/Dumper.scala b/src/main/scala/dameo/format/pdn/Dumper.scala new file mode 100644 index 000000000..a855d06e4 --- /dev/null +++ b/src/main/scala/dameo/format/pdn/Dumper.scala @@ -0,0 +1,21 @@ +package strategygames.dameo +package format.pdn + +import scala.annotation.nowarn + +object Dumper { + + def apply( + @nowarn _situation: Situation, + data: strategygames.dameo.Move, + @nowarn _next: Situation + ): String = + data.orig.key + (if (data.captures) "x" else "-") + data.dest.key + + def apply(data: strategygames.dameo.Move): String = apply( + data.situationBefore, + data, + data.after situationOf !data.player + ) + +} diff --git a/src/main/scala/dameo/format/pdn/Parser.scala b/src/main/scala/dameo/format/pdn/Parser.scala new file mode 100644 index 000000000..9ec1f70c8 --- /dev/null +++ b/src/main/scala/dameo/format/pdn/Parser.scala @@ -0,0 +1,266 @@ +package strategygames.dameo +package format.pdn + +import variant.Variant + +import scala.util.parsing.combinator._ +import cats.data.Validated +import cats.data.Validated.{ invalid, valid } +import cats.implicits._ +import scala.util.Try +import scala.annotation.nowarn + +import strategygames.format.pgn.{ + Glyph, + Glyphs, + InitialPosition, + ParsedPgn => ParsedPdn, + San, + Sans, + Tag, + Tags +} + +// http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm +// https://pdn.fmjd.org/index.html +object Parser { + + // TODO As part of Draughts upgrade to multiaction we should rename all the 'Move' + // in this file to Action (like in chess.format.pgn.Parser + private case class StrMove( + san: String, + glyphs: Glyphs, + comments: List[String], + variations: List[List[StrMove]] + ) + + def full(pdn: String): Validated[String, ParsedPdn] = try { + val preprocessed = pdn.linesIterator + .map(_.trim) + .filter { + _.headOption != Some('%') + } + .mkString("\n") + .replace("[pgn]", "") + .replace("[pdn]", "") + .replace("[/pgn]", "") + .replace("[/pdn]", "") + .replace("‑", "-") + .replace("–", "-") + for { + splitted <- splitTagAndMoves(preprocessed) + tagStr = splitted._1 + moveStr = splitted._2 + preTags <- TagParser(tagStr) + parsedMoves <- MovesParser(moveStr) + init = parsedMoves._1 + strMoves = parsedMoves._2 + resultOption = parsedMoves._3 + tags = resultOption.filterNot(_ => preTags.exists(_.Result)).fold(preTags)(t => preTags + t) + sans <- objMoves(strMoves, tags.dameoVariant.getOrElse(Variant.default)) + } yield ParsedPdn(init, tags, sans) + } catch { + case _: StackOverflowError => + println(pdn) + sys error "### StackOverflowError ### in PDN parser" + } + + def sans(str: String, variant: Variant): Validated[String, Sans] = + sans( + str.split(' ').toList, + variant + ) + def sans(flatActionStrs: Iterable[String], variant: Variant): Validated[String, Sans] = + objMoves( + flatActionStrs.map { StrMove(_, Glyphs.empty, Nil, Nil) }.to(List), + variant + ) + def objMoves(strMoves: List[StrMove], variant: Variant): Validated[String, Sans] = + strMoves.map { case StrMove(san, glyphs, comments, variations) => + ( + MoveParser(san, variant) map { m => + m withComments comments withVariations { + variations + .map { v => + objMoves(v, variant).getOrElse(Sans.empty) + } + .filter(_.value.nonEmpty) + } mergeGlyphs glyphs + } + ): Validated[String, San] + }.sequence map { Sans apply _ } + + trait Logging { self: Parsers => + protected val loggingEnabled = false + protected def as[T](msg: String)(p: => Parser[T]): Parser[T] = + if (loggingEnabled) log(p)(msg) else p + } + + private object MovesParser extends RegexParsers with Logging { + + override val whiteSpace = """(\s|\t|\r?\n)+""".r + + private def cleanComments(comments: List[String]) = comments.map(_.trim).filter(_.nonEmpty) + + def apply(pdn: String): Validated[String, (InitialPosition, List[StrMove], Option[Tag])] = + parseAll(strMoves, pdn) match { + case Success((init, moves, result), _) => + valid((init, moves, result.map { r => Tag(_.Result, r) })) + case err => + invalid("Cannot parse moves: %s\n%s".format(err.toString, pdn)) + } + + def strMoves: Parser[(InitialPosition, List[StrMove], Option[String])] = as("moves") { + (commentary *) ~ (strMove *) ~ (result ?) ~ (commentary *) ^^ { + case coms ~ sans ~ res ~ _ => { + val init = InitialPosition(cleanComments(coms)) + val drawMove = res.isEmpty && sans.lastOption.exists(_.san == "1-1") + val sans2 = if (drawMove) sans.dropRight(1) else sans + val res2 = if (drawMove) "1-1".some else res + (init, sans2, res2) + } + } + } + + import MoveParser.{ fieldR, suffR } + val moveRegex = s"""($fieldR)[\\-x:]($fieldR)([x:]($fieldR))*$suffR""".r + + def strMove: Parser[StrMove] = as("move") { + ((number | commentary) *) ~> + (moveRegex ~ nagGlyphs ~ rep(commentary) ~ nagGlyphs ~ rep(variation)) <~ + (moveExtras *) ^^ { case san ~ glyphs ~ comments ~ glyphs2 ~ variations => + StrMove(san.trim(), glyphs merge glyphs2, cleanComments(comments), variations) + } + } + + def number: Parser[String] = """[1-9]\d*\.+\s*""".r + + def moveExtras: Parser[Unit] = as("moveExtras") { + commentary.^^^(()) + } + + def nagGlyphs: Parser[Glyphs] = as("nagGlyphs") { + rep(nag) ^^ { nags => + Glyphs fromList nags.flatMap { n => + Try(Integer.parseInt(n drop 1)).toOption flatMap Glyph.find + } + } + } + + def nag: Parser[String] = as("nag") { + """\$\d+""".r + } + + def variation: Parser[List[StrMove]] = as("variation") { + "(" ~> strMoves <~ ")" ^^ { case (_, sms, _) => sms } + } + + def commentary: Parser[String] = blockCommentary | inlineCommentary | fenCommentary + + def blockCommentary: Parser[String] = as("block comment") { + "{" ~> """[^\}]*""".r <~ "}" + } + + def inlineCommentary: Parser[String] = as("inline comment") { + ";" ~> """.+""".r + } + + def fenCommentary: Parser[String] = as("fen comment") { + "/FEN \"" ~> """[\w:,]*""".r <~ "\"/" + } + + val result: Parser[String] = "*" | "1/2-1/2" | "½-½" | "0.5-0.5" | "1-1" | "0-1" | "0-2" | "1-0" | "2-0" + } + + object MoveParser extends RegexParsers with Logging { + + val fieldR = """50|[1-4][0-9]|0?[1-9]|[a-h][1-8]""" + val suffR = """[\?!□⨀]{0,2}""" + private val SimpleMoveR = s"""^($fieldR)(-|x|:)($fieldR)($suffR)$$""".r + private val RepeatCaptureR = s"""^($fieldR)[x:]((?:$fieldR)(?:[x:](?:$fieldR))+)($suffR)$$""".r + + def apply(str: String, variant: Variant): Validated[String, San] = str match { + case SimpleMoveR(src, sep, dst, suff) => + stdIfValid(variant, List(src, dst), sep != "-", suff) + case RepeatCaptureR(src, rest, suff) => + stdIfValid(variant, src :: rest.split("x|:").toList, true, suff); + case _ => invalid(s"Cannot parse move: $str") + } + + @nowarn private def stdIfValid(variant: Variant, fields: List[String], capture: Boolean, suff: String) = { + invalid(s"stdIfValid not implemented for dameo") + } + + def glyphs: Parser[Glyphs] = as("glyphs") { + rep(glyph) ^^ Glyphs.fromList + } + + def glyph: Parser[Glyph] = as("glyph") { + mapParser( + Glyph.MoveAssessment.all.sortBy(_.symbol.size).map { g => g.symbol -> g }, + "glyph" + ) + } + + def exists(c: String): Parser[Boolean] = c ^^^ true | success(false) + + def mapParser[A, B](pairs: Iterable[(A, B)], name: String): Parser[B] = + pairs.foldLeft(failure(name + " not found"): Parser[B]) { case (acc, (a, b)) => + a.toString ^^^ b | acc + } + } + + object TagParser extends RegexParsers with Logging { + + def apply(pdn: String): Validated[String, Tags] = parseAll(all, pdn) match { + case f: Failure => invalid("Cannot parse tags: %s\n%s".format(f.toString, pdn)) + case Success(tags, _) => valid(Tags(tags)) + case err => invalid("Cannot parse tags: %s\n%s".format(err.toString, pdn)) + } + + def fromFullPdn(pdn: String): Validated[String, Tags] = + splitTagAndMoves(pdn) flatMap { case (tags, _) => + apply(tags) + } + + def all: Parser[List[Tag]] = as("all") { + tags <~ """(.|\n)*""".r + } + + def tags: Parser[List[Tag]] = rep(tag) + + def tag: Parser[Tag] = as("tag") { + tagName ~ tagValue ^^ { case name ~ value => + Tag(name, value) + } + } + + val tagName: Parser[String] = "[" ~> """[a-zA-Z]+""".r + + val tagValue: Parser[String] = """[^\]]+""".r <~ "]" ^^ { + _.replace("\"", "") + } + } + + // there must be a newline between the tags and the first move/comment + private def ensureTagsNewline(pdn: String): String = + """"\]\s*(\{|\d+\.)""".r.replaceAllIn(pdn, m => "\"]\n" + m.group(1)) + + // To accomodate some common PDN source that always adds a [FILENAME ""] tag on the bottom of every file + private def ensureTagsNewlineReverse(pdn: String): String = + """\[(FILENAME\s+")""".r.replaceAllIn(pdn, m => "\n[" + m.group(1)) + + private def splitTagAndMoves(pdn: String): Validated[String, (String, String)] = + ensureTagsNewlineReverse(ensureTagsNewline(pdn)).linesIterator.toList + .map(_.trim) + .filter(_.nonEmpty) + .to(List) span { line => + line lift 0 contains '[' + } match { + case (tagLines, moveLines) => // Drop any tag in last line (accomodate [FILENAME) + if (moveLines.lastOption.fold(false)(line => line.nonEmpty && line.head == '[' && line.last == ']')) + valid(tagLines.mkString("\n") -> moveLines.dropRight(1).mkString("\n")) + else valid(tagLines.mkString("\n") -> moveLines.mkString("\n")) + } + +} diff --git a/src/main/scala/dameo/format/pdn/Reader.scala b/src/main/scala/dameo/format/pdn/Reader.scala new file mode 100644 index 000000000..08ec4f144 --- /dev/null +++ b/src/main/scala/dameo/format/pdn/Reader.scala @@ -0,0 +1,100 @@ +package strategygames.dameo +package format.pdn +import strategygames.{ + Action => StratAction, + ActionStrs, + ByoyomiClock, + Clock, + Move => StratMove, + Situation => StratSituation +} +import strategygames.format.pgn.{ ParsedPgn => ParsedPdn, Sans, Tags } + +import cats.data.Validated +import scala.annotation.nowarn + +object Reader { + + sealed trait Result { + def valid: Validated[String, Replay] + } + + object Result { + case class Complete(replay: Replay) extends Result { + def valid = Validated.valid(replay) + } + case class Incomplete(replay: Replay, failure: String) extends Result { + def valid = Validated.invalid(failure) + } + } + + def full(pdn: String, tags: Tags = Tags.empty): Validated[String, Result] = + fullWithSans(pdn, identity, tags, true) + + def replayResult( + actionStrs: ActionStrs, + tags: Tags, + iteratedCapts: Boolean = false + ): Validated[String, Result] = + replayResultFromActionStrsUsingSan(actionStrs, identity, tags, iteratedCapts) + + def fullWithSans( + pdn: String, + op: Sans => Sans, + tags: Tags = Tags.empty, + iteratedCapts: Boolean = false + ): Validated[String, Result] = + Parser.full(cleanUserInput(pdn)) map { parsed => + makeReplay(makeGame(parsed.tags ++ tags), op(parsed.sans), iteratedCapts) + } + + def fullWithSans(parsed: ParsedPdn, op: Sans => Sans): Result = + makeReplay(makeGame(parsed.tags), op(parsed.sans)) + + def replayResultFromActionStrsUsingSan( + actionStrs: ActionStrs, + op: Sans => Sans, + tags: Tags, + @nowarn iteratedCapts: Boolean = false + ): Validated[String, Result] = + // Its ok to flatten actionStrs as the game is built back up again from the Situation + Parser.sans(actionStrs.flatten, tags.dameoVariant | variant.Variant.default) map { sans => + makeReplay(makeGame(tags), op(sans)) + } + + // remove invisible byte order mark + def cleanUserInput(str: String) = str.replace(s"\ufeff", "") + + // TODO: because this is primarily used in a Validation context, we should be able to + // return something that's runtime safe as well. + def dameoMove(action: StratAction) = action match { + case StratMove.Dameo(m) => m + case _ => sys.error("Invalid dameo move") + } + + private def makeReplay(game: Game, sans: Sans, @nowarn iteratedCapts: Boolean = false): Result = + sans.value.foldLeft[Result](Result.Complete(Replay(game))) { + case (Result.Complete(replay), san) => + san(StratSituation.wrap(replay.state.situation)).fold( + err => Result.Incomplete(replay, err), + action => Result.Complete(replay addAction StratAction.toDameo(action)) + ) + case (r: Result.Incomplete, _) => r + } + + private def makeGame(tags: Tags) = { + val g = Game( + variantOption = tags.dameoVariant, + fen = tags.dameoFen + ) + g.copy( + startedAtPly = g.plies, + startedAtTurn = g.turnCount, + clock = tags.clockConfig.flatMap { + case fc: Clock.Config => Some(Clock.apply(fc)) + case bc: ByoyomiClock.Config => Some(ByoyomiClock.apply(bc)) + case _ => None + } + ) + } +} diff --git a/src/main/scala/dameo/format/pdn/package.scala b/src/main/scala/dameo/format/pdn/package.scala new file mode 100644 index 000000000..1c2bbe741 --- /dev/null +++ b/src/main/scala/dameo/format/pdn/package.scala @@ -0,0 +1,171 @@ +package strategygames.dameo +package format +package pdn + +import strategygames.Player +import strategygames.format.pgn.{ Glyphs, Tag, Tags } + +import scala._ +import cats.implicits._ + +case class Pdn( + tags: Tags, + turns: List[Turn], + initial: Initial = Initial.empty +) { + + def updateTurn(fullMove: Int, f: Turn => Turn) = { + val index = fullMove - 1 + (turns lift index).fold(this) { turn => + copy(turns = turns.updated(index, f(turn))) + } + } + def updatePly(ply: Int, f: Move => Move) = { + val fullMove = (ply + 1) / 2 + val player = Player(ply % 2 == 1) + updateTurn(fullMove, _.update(player, f)) + } + def updateLastPly(f: Move => Move) = updatePly(nbPlies, f) + + def nbPlies = turns.foldLeft(0)(_ + _.count) + + def moves = turns.flatMap { t => + List(t.p1, t.p2).flatten + } + + def withEvent(title: String) = copy( + tags = tags + Tag(_.Event, title) + ) + + def render: String = { + val initStr = + if (initial.comments.nonEmpty) initial.comments.mkString("{ ", " } { ", " }\n") + else "" + val turnStr = turns mkString " " + val endStr = tags(_.Result) | "" + s"$tags\n\n$initStr$turnStr $endStr" + }.trim + "\n" + + override def toString = render +} + +case class Initial(comments: List[String] = Nil) + +object Initial { + val empty = Initial(Nil) +} + +case class Turn( + number: Int, + p1: Option[Move], + p2: Option[Move] +) { + + def update(player: Player, f: Move => Move) = player.fold( + copy(p1 = p1 map f), + copy(p2 = p2 map f) + ) + + def updateLast(f: Move => Move) = { + p2.map(m => copy(p2 = f(m).some)) orElse + p1.map(m => copy(p1 = f(m).some)) + } | this + + def isEmpty = p1.isEmpty && p2.isEmpty + + def plyOf(player: Player) = number * 2 - player.fold(1, 0) + + def count = List(p1, p2) count (_.isDefined) + + override def toString = { + val text = (p1, p2) match { + case (Some(w), Some(b)) if w.isLong => s" $w $number... $b" + case (Some(w), Some(b)) => s" $w $b" + case (Some(w), None) => s" $w" + case (None, Some(b)) => s".. $b" + case _ => "" + } + s"$number.$text" + } +} + +object Turn { + + def fromMoves(moves: List[Move], ply: Int): List[Turn] = { + moves.foldLeft((List[Turn](), ply)) { + case ((turns, p), move) if p % 2 == 1 => + (Turn((p + 1) / 2, move.some, none) :: turns) -> (p + 1) + case ((Nil, p), move) => + (Turn((p + 1) / 2, none, move.some) :: Nil) -> (p + 1) + case ((t :: tt, p), move) => + (t.copy(p2 = move.some) :: tt) -> (p + 1) + } + }._1.reverse +} + +case class Move( + san: String, + // the player who played the move + turn: Player, + comments: List[String] = Nil, + glyphs: Glyphs = Glyphs.empty, + opening: Option[String] = None, + result: Option[String] = None, + variations: List[List[Turn]] = Nil, + // time left for the p1, p2 player, after the move is made + secondsLeft: (Option[Int], Option[Int]) = (None, None) +) { + + def isLong = comments.nonEmpty || variations.nonEmpty + + private def canPrintTime = secondsLeft._1.isDefined && (secondsLeft._2.isDefined || turn.p1) + + private def clockString: Option[String] = + if (canPrintTime) + s"[%clock ${turn.fold("w", "W")}${Move.formatPdnSeconds(secondsLeft._1.get)} ${turn.fold("B", "b")}${Move + .formatPdnSeconds(secondsLeft._2.getOrElse(0))}]".some + else none + + override def toString = { + val glyphStr = glyphs.toList.map { + case glyph if glyph.id <= 6 => glyph.symbol + case glyph => s" $$${glyph.id}" + }.mkString + val commentsOrTime = + if (comments.nonEmpty || canPrintTime || opening.isDefined || result.isDefined) + List(clockString, opening, result).flatten + .:::(comments map Move.noDoubleLineBreak) + .map { text => + s" {$text}" + } + .mkString + else "" + val variationString = + if (variations.isEmpty) "" + else variations.map(_.mkString(" (", " ", ")")).mkString(" ") + s"$san$glyphStr$commentsOrTime$variationString" + } +} + +object Move { + + private val noDoubleLineBreakRegex = "(\r?\n){2,}".r + + private def noDoubleLineBreak(txt: String) = + noDoubleLineBreakRegex.replaceAllIn(txt, "\n") + + private def formatPdnSeconds(t: Int) = periodFormatter.print( + org.joda.time.Duration.standardSeconds(t).toPeriod + ) + + private[this] val periodFormatter = new org.joda.time.format.PeriodFormatterBuilder().printZeroAlways + .minimumPrintedDigits(1) + .appendHours + .appendSeparator(":") + .minimumPrintedDigits(2) + .appendMinutes + .appendSeparator(":") + .appendSeconds + .toFormatter + +} diff --git a/src/main/scala/dameo/format/pdn/parsingModel.scala b/src/main/scala/dameo/format/pdn/parsingModel.scala new file mode 100644 index 000000000..45e882341 --- /dev/null +++ b/src/main/scala/dameo/format/pdn/parsingModel.scala @@ -0,0 +1,58 @@ +package strategygames.dameo +package format.pdn + +import strategygames.{ Move => StratMove } +import strategygames.format.pgn.{ Metas, San } + +import cats.data.Validated +import cats.data.Validated.{ invalid, valid } +import cats.syntax.option._ + +case class Std( + fields: List[Pos], + capture: Boolean, + metas: Metas = Metas.empty +) extends San { + + override def apply( + situation: strategygames.Situation, + iteratedCapts: Boolean, + forbiddenUci: Option[List[String]] + ): Validated[String, strategygames.Action] = + move(situation.toDameo, iteratedCapts, forbiddenUci).map(m => StratMove.wrap(m)) + + def move( + situation: Situation, + iteratedCapts: Boolean = false, + forbiddenUci: Option[List[String]] = None, + captures: Option[List[Pos]] = None + ): Validated[String, strategygames.dameo.Move] = { + val src = fields.head + val dest = fields.last + val capturePath = if (capture) fields.tail.reverse else Nil + situation.board.pieces.foldLeft(none[strategygames.dameo.Move]) { + case (None, (pos, piece)) if piece.player == situation.player && pos == src => + val a = Actor(piece, pos, situation.board) + // TODO: technically we should check situation.hasCaptures instead of actor + val validMoves = if (a.captures.nonEmpty) a.captures else a.noncaptures + validMoves.find { m => + m.dest == dest && (!iteratedCapts || m.situationAfter.ghosts == 0) + } match { + case None if capture && iteratedCapts => + a.capturesFinal.find { m => + m.dest == dest && + captures.fold(true)(m.capture.contains) && + !forbiddenUci.fold(false)(_.contains(m.toUci.uci)) && + (capturePath.length <= 1 || m.capture.contains(capturePath)) + } + case m @ _ => m + } + case (m, _) => m + } match { + case None => invalid(s"No move found: $this\n$situation") + case Some(move) => valid(move) + } + } + + def withMetas(m: Metas) = copy(metas = m) +} diff --git a/src/main/scala/dameo/opening/Ecopening.scala b/src/main/scala/dameo/opening/Ecopening.scala new file mode 100644 index 000000000..ea48f9fea --- /dev/null +++ b/src/main/scala/dameo/opening/Ecopening.scala @@ -0,0 +1,70 @@ +package strategygames.dameo.opening +import strategygames.dameo._ + +import strategygames.ActionStrs + +import cats.syntax.option._ + +final class Ecopening( + val eco: Ecopening.ECO, + val variantGrouping: String, + val family: Ecopening.FamilyName, + val name: String, + val moves: String, + val fen: Ecopening.FEN, + val lastMoveUci: String +) extends Ordered[Ecopening] { + + lazy val moveList = moves.split(' ').toList + + def firstMove = moveList.headOption + + lazy val size = moveList.size + + lazy val formattedMoves: String = + moveList + .grouped(2) + .zipWithIndex + .map { + case (List(w, b), i) => s"${i + 1}. $w $b" + case (List(w), i) => s"${i + 1}. $w" + case _ => "" + } + .mkString(" ") + + def ecoName = s"$eco $name" + + def compare(other: Ecopening) = eco compare other.eco + + override def toString = s"$ecoName ($moves)" +} + +object Ecopening { + type FamilyName = String + type ECO = String + type FEN = String + + case class Family(name: FamilyName, ecos: List[FEN]) + def makeFamilies(ops: Iterable[Ecopening]): Map[FamilyName, Family] = + ops.foldLeft(Map.empty[FamilyName, Family]) { case (fams, op) => + fams + (op.family -> fams.get(op.family).fold(Family(op.family, List(op.eco))) { existing => + existing.copy(ecos = op.eco :: existing.ecos) + }) + } + + def fromGame(actionStrs: ActionStrs): Option[Ecopening] = + Replay + .boards( + actionStrs = actionStrs take EcopeningDB.MAX_TURNS, + initialFen = None, + variant = variant.Variant.default + ) + .toOption flatMap matchChronoBoards + + private def matchChronoBoards(boards: List[Board]): Option[Ecopening] = + boards.reverse.foldLeft(none[Ecopening]) { case (acc, board) => + acc orElse { + EcopeningDB.allByFen get format.Forsyth.exportBoard(board) + } + } +} diff --git a/src/main/scala/dameo/opening/EcopeningDB.scala b/src/main/scala/dameo/opening/EcopeningDB.scala new file mode 100644 index 000000000..90bb2e708 --- /dev/null +++ b/src/main/scala/dameo/opening/EcopeningDB.scala @@ -0,0 +1,19 @@ +package strategygames.dameo.opening + +// format: off +object EcopeningDB { + + import Ecopening._ + + val MAX_TURNS = 25 + + lazy val all = allByEco.values.toList.sorted + + lazy val allByFen: Map[FEN, Ecopening] = allByEco.map { + case (_, opening) => opening.fen -> opening + } + + lazy val allByEco: Map[ECO, Ecopening] = Map( + "A00" -> new Ecopening("A00", "dameo", "Dameo Start Pos", "Dameo Start Pos", "", "ADD INITIAL FEN HERE", ""), + ) +} diff --git a/src/main/scala/dameo/opening/FullOpening.scala b/src/main/scala/dameo/opening/FullOpening.scala new file mode 100644 index 000000000..5a94ec76e --- /dev/null +++ b/src/main/scala/dameo/opening/FullOpening.scala @@ -0,0 +1,19 @@ +package strategygames.dameo.opening + +final class FullOpening( + val eco: String, + val name: String, + val fen: String +) { + + def ecoName = s"$eco $name" + + override def toString = ecoName + + def atPly(ply: Int) = FullOpening.AtPly(this, ply) +} + +object FullOpening { + + case class AtPly(opening: FullOpening, ply: Int) +} diff --git a/src/main/scala/dameo/opening/FullOpeningDB.scala b/src/main/scala/dameo/opening/FullOpeningDB.scala new file mode 100644 index 000000000..bc0f1102b --- /dev/null +++ b/src/main/scala/dameo/opening/FullOpeningDB.scala @@ -0,0 +1,18 @@ +package strategygames.dameo.opening + +import strategygames.dameo.format.FEN +import strategygames.ActionStrs + +import scala.annotation.nowarn + +object FullOpeningDB { + + // private val SEARCH_MAX_TURNS = 40 + + @nowarn def findByFen(fen: FEN): Option[FullOpening] = None // TODO: ??? + + // assumes standard initial FEN and variant + @nowarn def search(actionStrs: ActionStrs): Option[FullOpening.AtPly] = None // TODO: ??? + + @nowarn def searchInFens(fens: Vector[FEN]): Option[FullOpening] = None // TODO: ??? +} diff --git a/src/main/scala/dameo/opening/FullOpeningPartA.scala b/src/main/scala/dameo/opening/FullOpeningPartA.scala new file mode 100644 index 000000000..2a9689bf0 --- /dev/null +++ b/src/main/scala/dameo/opening/FullOpeningPartA.scala @@ -0,0 +1,6 @@ +package strategygames.dameo.opening + +private[opening] object FullOpeningPartA { + + def db: Vector[FullOpening] = Vector() +} diff --git a/src/main/scala/dameo/opening/FullOpeningPartB.scala b/src/main/scala/dameo/opening/FullOpeningPartB.scala new file mode 100644 index 000000000..b2c9c2e0a --- /dev/null +++ b/src/main/scala/dameo/opening/FullOpeningPartB.scala @@ -0,0 +1,6 @@ +package strategygames.dameo.opening + +private[opening] object FullOpeningPartB { + + def db: Vector[FullOpening] = Vector() +} diff --git a/src/main/scala/dameo/package.scala b/src/main/scala/dameo/package.scala new file mode 100644 index 000000000..e4db52ab7 --- /dev/null +++ b/src/main/scala/dameo/package.scala @@ -0,0 +1,16 @@ +package strategygames +import ornicar.scalalib + +package object dameo extends scalalib.Common with scalalib.OrnicarOption with scalalib.OrnicarBoolean { + + val P1 = strategygames.Player.P1 + val P2 = strategygames.Player.P2 + + type Direction = Pos => Option[Pos] + type Directions = List[Direction] + + type PieceMap = Map[Pos, Piece] + + type PositionHash = Array[Byte] + +} diff --git a/src/main/scala/dameo/project/build.properties b/src/main/scala/dameo/project/build.properties new file mode 100644 index 000000000..10fd9eee0 --- /dev/null +++ b/src/main/scala/dameo/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.5.5 diff --git a/src/main/scala/dameo/variant/Dameo.scala b/src/main/scala/dameo/variant/Dameo.scala new file mode 100644 index 000000000..1145ebce0 --- /dev/null +++ b/src/main/scala/dameo/variant/Dameo.scala @@ -0,0 +1,27 @@ +package strategygames.dameo +package variant + +import strategygames.dameo._ +import strategygames.GameFamily + +case object Dameo + extends Variant( + id = 1, + key = "dameo", + name = "Dameo", + standardInitialPosition = true, + boardSize = Board.Dim8x8 + ) { + + def gameFamily: GameFamily = GameFamily.Dameo() + + def perfIcon: Char = '' + def perfId: Int = 800 + + override def baseVariant: Boolean = true + + // TODO Dameo set this + override def initialFen = format.FEN("") + + // TODO Dameo can write the variant/Variant.scala method code in here (using override) +} diff --git a/src/main/scala/dameo/variant/Variant.scala b/src/main/scala/dameo/variant/Variant.scala new file mode 100644 index 000000000..c75bd0274 --- /dev/null +++ b/src/main/scala/dameo/variant/Variant.scala @@ -0,0 +1,134 @@ +package strategygames.dameo.variant + +import cats.data.Validated +import cats.syntax.option._ + +import strategygames.dameo._ +import strategygames.dameo.format.FEN +import strategygames.{ GameFamily, Player } + +import scala.annotation.nowarn + +// Correctness depends on singletons for each variant ID +abstract class Variant private[variant] ( + val id: Int, + val key: String, + val name: String, + val standardInitialPosition: Boolean, + val boardSize: Board.BoardSize +) { + + def exotic = true + + def baseVariant: Boolean = false + def fenVariant: Boolean = false + def variableInitialFen: Boolean = true + + def hasAnalysisBoard: Boolean = false + def hasFishnet: Boolean = false + + def p1IsBetterVariant: Boolean = false + def blindModeVariant: Boolean = false + + def materialImbalanceVariant: Boolean = false + + def dropsVariant: Boolean = false + + def canOfferDraw: Boolean = true + + def repetitionEnabled: Boolean = true + + def perfId: Int + def perfIcon: Char + + // TODO Dameo set this + def initialFen: FEN = FEN("") + + def pieces: PieceMap = initialFen.pieces + + def startPlayer: Player = P1 + + // TODO Dameo implement this, possibly using Actor move generation + def validMoves(@nowarn situation: Situation): Map[Pos, List[Move]] = Map.empty + + def move( + situation: Situation, + from: Pos, + to: Pos, + promotion: Option[PromotableRole], + capture: Option[Pos] + ): Validated[String, Move] = { + // Find the move in the variant specific list of valid moves + situation.moves get from flatMap (_.find(m => m.dest == to)) toValid + s"Not a valid move: ${from}${to}${promotion}${capture}. Allowed moves: ${situation.moves}" + } + + def hasMoveEffects = false + + def addVariantEffect(move: Move): Move = move + + // TODO Dameo set this if relevant + def maxDrawingMoves(@nowarn board: Board): Option[Int] = None + + // TODO Dameo set this + def variantEnd(@nowarn situation: Situation) = false + + def specialEnd(@nowarn situation: Situation) = false + def specialDraw(@nowarn situation: Situation) = false + + // TODO Dameo set this + def winner(@nowarn situation: Situation): Option[Player] = None + + def materialImbalance(@nowarn board: Board): Int = 0 + + // TODO Dameo if there are sensible things to check put them here + def valid(@nowarn board: Board, @nowarn strict: Boolean): Boolean = false + + val roles: List[Role] = Role.all + + lazy val rolesByPdn: Map[Char, Role] = roles + .map { r => + (r.pdn, r) + } + .to(Map) + + override def toString = s"Variant($name)" + + override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] + + override def hashCode: Int = id + + def defaultRole: Role = Role.defaultRole + + def gameFamily: GameFamily +} + +object Variant { + + lazy val all: List[Variant] = List( + Dameo + ) + val byId = all map { v => + (v.id, v) + } toMap + val byKey = all map { v => + (v.key, v) + } toMap + + val default = Dameo + + def apply(id: Int): Option[Variant] = byId get id + def apply(key: String): Option[Variant] = byKey get key + def orDefault(id: Int): Variant = apply(id) | default + def orDefault(key: String): Variant = apply(key) | default + + def byName(name: String): Option[Variant] = + all find (_.name.toLowerCase == name.toLowerCase) + + def exists(id: Int): Boolean = byId contains id + + val openingSensibleVariants: Set[Variant] = Set(strategygames.dameo.variant.Dameo) + + val divisionSensibleVariants: Set[Variant] = Set() + +} diff --git a/src/main/scala/format/FEN.scala b/src/main/scala/format/FEN.scala index b724f6f59..877ac707e 100644 --- a/src/main/scala/format/FEN.scala +++ b/src/main/scala/format/FEN.scala @@ -13,6 +13,7 @@ sealed abstract class FEN(val value: String) { def toGo: strategygames.go.format.FEN def toBackgammon: strategygames.backgammon.format.FEN def toAbalone: strategygames.abalone.format.FEN + def toDameo: strategygames.dameo.format.FEN override def toString = value @@ -45,6 +46,7 @@ object FEN { def toGo = sys.error("Can't convert chess to go") def toBackgammon = sys.error("Can't convert chess to backgammon") def toAbalone = sys.error("Can't convert chess to abalone") + def toDameo = sys.error("Can't convert chess to dameo") def gameLogic = GameLogic.Chess() @@ -73,6 +75,7 @@ object FEN { def toGo = sys.error("Can't convert draughts to go") def toBackgammon = sys.error("Can't convert draughts to backgammon") def toAbalone = sys.error("Can't convert draughts to abalone") + def toDameo = sys.error("Can't convert draughts to dameo") def gameLogic = GameLogic.Draughts() @@ -103,6 +106,7 @@ object FEN { def toGo = sys.error("Can't convert fairysf to go") def toBackgammon = sys.error("Can't convert fairysf to backgammon") def toAbalone = sys.error("Can't convert fairysf to abalone") + def toDameo = sys.error("Can't convert fairysf to dameo") def gameLogic = GameLogic.FairySF() @@ -131,6 +135,7 @@ object FEN { def toGo = sys.error("Can't convert samurai to go") def toBackgammon = sys.error("Can't convert samurai to backgammon") def toAbalone = sys.error("Can't convert samurai to abalone") + def toDameo = sys.error("Can't convert samurai to dameo") def gameLogic = GameLogic.Samurai() @@ -159,6 +164,7 @@ object FEN { def toGo = sys.error("Can't convert togyzkumalak to go") def toBackgammon = sys.error("Can't convert togyzkumalak to backgammon") def toAbalone = sys.error("Can't convert togyzkumalak to abalone") + def toDameo = sys.error("Can't convert togyzkumalak to dameo") def gameLogic = GameLogic.Togyzkumalak() @@ -187,6 +193,7 @@ object FEN { def toGo = f def toBackgammon = sys.error("Can't convert go to backgammon") def toAbalone = sys.error("Can't convert go to abalone") + def toDameo = sys.error("Can't convert go to dameo") def gameLogic = GameLogic.Go() @@ -215,6 +222,7 @@ object FEN { def toGo = sys.error("Can't convert backgammon to go") def toBackgammon = f def toAbalone = sys.error("Can't convert backgammon to abalone") + def toDameo = sys.error("Can't convert backgammon to dameo") def gameLogic = GameLogic.Backgammon() @@ -243,6 +251,7 @@ object FEN { def toGo = sys.error("Can't convert abalone to go") def toBackgammon = sys.error("Can't convert abalone to backgammon") def toAbalone = f + def toDameo = sys.error("Can't convert abalone to dameo") def gameLogic = GameLogic.Abalone() @@ -261,6 +270,35 @@ object FEN { } + final case class Dameo(f: strategygames.dameo.format.FEN) extends FEN(f.value) { + + def toChess = sys.error("Can't convert dameo to chess") + def toDraughts = sys.error("Can't convert dameo to draughts") + def toFairySF = sys.error("Can't convert dameo to fairysf") + def toSamurai = sys.error("Can't convert dameo to samurai") + def toTogyzkumalak = sys.error("Can't convert dameo to togyzkumalak") + def toGo = sys.error("Can't convert dameo to go") + def toBackgammon = sys.error("Can't convert dameo to backgammon") + def toAbalone = sys.error("Can't convert dameo to abalone") + def toDameo = f + + def gameLogic = GameLogic.Dameo() + + def fullMove: Option[Int] = sys.error("There is no fullMove in dameo") + + def player: Option[Player] = f.player + + def ply: Option[Int] = sys.error("There is no ply in dameo") + + def initial: Boolean = f.initial + + def chessFen: Option[strategygames.chess.format.FEN] = None + + def player1Score = sys.error("There is no player1 score in dameo") + def player2Score = sys.error("There is no player2 score in dameo") + + } + def wrap(fen: strategygames.chess.format.FEN) = Chess(fen) def wrap(fen: strategygames.draughts.format.FEN) = Draughts(fen) def wrap(fen: strategygames.fairysf.format.FEN) = FairySF(fen) @@ -269,6 +307,7 @@ object FEN { def wrap(fen: strategygames.go.format.FEN) = Go(fen) def wrap(fen: strategygames.backgammon.format.FEN) = Backgammon(fen) def wrap(fen: strategygames.abalone.format.FEN) = Abalone(fen) + def wrap(fen: strategygames.dameo.format.FEN) = Dameo(fen) def apply(lib: GameLogic, value: String): FEN = lib match { case GameLogic.Draughts() => FEN.Draughts(strategygames.draughts.format.FEN(value)) @@ -279,6 +318,7 @@ object FEN { case GameLogic.Togyzkumalak() => FEN.Togyzkumalak(strategygames.togyzkumalak.format.FEN(value)) case GameLogic.Backgammon() => FEN.Backgammon(strategygames.backgammon.format.FEN(value)) case GameLogic.Abalone() => FEN.Abalone(strategygames.abalone.format.FEN(value)) + case GameLogic.Dameo() => FEN.Dameo(strategygames.dameo.format.FEN(value)) } def apply(v: Variant, value: String): FEN = apply(v.gameLogic, value) @@ -295,6 +335,8 @@ object FEN { Backgammon(strategygames.backgammon.format.FEN(source.replace("_", " ").trim)) case GameLogic.Abalone() => Abalone(strategygames.abalone.format.FEN(source.replace("_", " ").trim)) + case GameLogic.Dameo() => + Dameo(strategygames.dameo.format.FEN(source.replace("_", " ").trim)) } def fishnetFen(variant: Variant)(fen: FEN): FEN = variant match { diff --git a/src/main/scala/format/Forsyth.scala b/src/main/scala/format/Forsyth.scala index 6654e6674..9e98caeeb 100644 --- a/src/main/scala/format/Forsyth.scala +++ b/src/main/scala/format/Forsyth.scala @@ -17,6 +17,7 @@ object Forsyth { case GameLogic.Go() => FEN.Go(go.format.Forsyth.initial) case GameLogic.Backgammon() => FEN.Backgammon(backgammon.format.Forsyth.initial) case GameLogic.Abalone() => FEN.Abalone(abalone.format.Forsyth.initial) + case GameLogic.Dameo() => FEN.Dameo(dameo.format.Forsyth.initial) } def <<@(lib: GameLogic, variant: Variant, fen: FEN): Option[Situation] = @@ -37,6 +38,8 @@ object Forsyth { backgammon.format.Forsyth.<<@(variant, fen).map(Situation.Backgammon) case (GameLogic.Abalone(), Variant.Abalone(variant), FEN.Abalone(fen)) => abalone.format.Forsyth.<<@(variant, fen).map(Situation.Abalone) + case (GameLogic.Dameo(), Variant.Dameo(variant), FEN.Dameo(fen)) => + dameo.format.Forsyth.<<@(variant, fen).map(Situation.Dameo) case _ => sys.error("Mismatched gamelogic types 14") } @@ -52,6 +55,8 @@ object Forsyth { backgammon.format.Forsyth.<<(fen).map(Situation.Backgammon) case (GameLogic.Abalone(), FEN.Abalone(fen)) => abalone.format.Forsyth.<<(fen).map(Situation.Abalone) + case (GameLogic.Dameo(), FEN.Dameo(fen)) => + dameo.format.Forsyth.<<(fen).map(Situation.Dameo) case _ => sys.error("Mismatched gamelogic types 15") } @@ -96,6 +101,10 @@ object Forsyth { abalone.format.Forsyth .<<<@(variant, fen) .map(sp => SituationPlus(Situation.Abalone(sp.situation), sp.fullTurnCount)) + case (GameLogic.Dameo(), Variant.Dameo(variant), FEN.Dameo(fen)) => + dameo.format.Forsyth + .<<<@(variant, fen) + .map(sp => SituationPlus(Situation.Dameo(sp.situation), sp.fullTurnCount)) case _ => sys.error("Mismatched gamelogic types 16") } @@ -132,6 +141,10 @@ object Forsyth { abalone.format.Forsyth .<<<(fen) .map(sp => SituationPlus(Situation.Abalone(sp.situation), sp.fullTurnCount)) + case (GameLogic.Dameo(), FEN.Dameo(fen)) => + dameo.format.Forsyth + .<<<(fen) + .map(sp => SituationPlus(Situation.Dameo(sp.situation), sp.fullTurnCount)) case _ => sys.error("Mismatched gamelogic types 17") } @@ -186,6 +199,12 @@ object Forsyth { abalone.format.Forsyth.SituationPlus(situation, parsed.fullTurnCount) ) ) + case (GameLogic.Dameo(), Situation.Dameo(situation)) => + FEN.Dameo( + dameo.format.Forsyth.>>( + dameo.format.Forsyth.SituationPlus(situation, parsed.fullTurnCount) + ) + ) case _ => sys.error("Mismatched gamelogic types 19") } @@ -200,6 +219,7 @@ object Forsyth { case (GameLogic.Backgammon(), Game.Backgammon(game)) => FEN.Backgammon(backgammon.format.Forsyth.>>(game)) case (GameLogic.Abalone(), Game.Abalone(game)) => FEN.Abalone(abalone.format.Forsyth.>>(game)) + case (GameLogic.Dameo(), Game.Dameo(game)) => FEN.Dameo(dameo.format.Forsyth.>>(game)) case _ => sys.error("Mismatched gamelogic types 20") } @@ -221,6 +241,8 @@ object Forsyth { backgammon.format.Forsyth.exportBoard(board) case (GameLogic.Abalone(), Board.Abalone(board)) => abalone.format.Forsyth.exportBoard(board) + case (GameLogic.Dameo(), Board.Dameo(board)) => + dameo.format.Forsyth.exportBoard(board) case _ => sys.error("Mismatched gamelogic types 21") } @@ -245,6 +267,8 @@ object Forsyth { backgammon.format.Forsyth.boardAndPlayer(board, turnPlayer) case (GameLogic.Abalone(), Board.Abalone(board)) => abalone.format.Forsyth.boardAndPlayer(board, turnPlayer) + case (GameLogic.Dameo(), Board.Dameo(board)) => + dameo.format.Forsyth.boardAndPlayer(board, turnPlayer) case _ => sys.error("Mismatched gamelogic types 22") } diff --git a/src/main/scala/format/Uci.scala b/src/main/scala/format/Uci.scala index 294db2b1f..7292e8103 100644 --- a/src/main/scala/format/Uci.scala +++ b/src/main/scala/format/Uci.scala @@ -26,6 +26,7 @@ sealed trait Uci { def toGo: go.format.Uci def toBackgammon: backgammon.format.Uci def toAbalone: abalone.format.Uci + def toDameo: dameo.format.Uci } @@ -55,6 +56,9 @@ object Uci { sealed trait Abalone { def unwrap: abalone.format.Uci } + sealed trait Dameo { + def unwrap: dameo.format.Uci + } sealed abstract class Move( val orig: Pos, @@ -96,6 +100,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a chess UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a chess UCI") def toAbalone = sys.error("Can't make a abalone UCI from a chess UCI") + def toDameo = sys.error("Can't make a dameo UCI from a chess UCI") } final case class DraughtsMove(m: draughts.format.Uci.Move) @@ -122,6 +127,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a draughts UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a draughts UCI") def toAbalone = sys.error("Can't make a abalone UCI from a draughts UCI") + def toDameo = sys.error("Can't make a dameo UCI from a draughts UCI") } final case class FairySFMove(m: fairysf.format.Uci.Move) @@ -144,6 +150,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a fairy UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a fairysf UCI") def toAbalone = sys.error("Can't make a abalone UCI from a fairysf UCI") + def toDameo = sys.error("Can't make a dameo UCI from a fairysf UCI") } final case class SamuraiMove(m: samurai.format.Uci.Move) @@ -165,6 +172,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a samurai UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a samurai UCI") def toAbalone = sys.error("Can't make a abalone UCI from a samurai UCI") + def toDameo = sys.error("Can't make a dameo UCI from a samurai UCI") } final case class TogyzkumalakMove(m: togyzkumalak.format.Uci.Move) @@ -186,6 +194,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a togyzkumalak UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a togyzkumalak UCI") def toAbalone = sys.error("Can't make a abalone UCI from a togyzkumalak UCI") + def toDameo = sys.error("Can't make a dameo UCI from a togyzkumalak UCI") } final case class BackgammonMove(m: backgammon.format.Uci.Move) @@ -211,6 +220,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = m def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } final case class AbaloneMove(m: abalone.format.Uci.Move) @@ -232,6 +242,34 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a abalone UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a abalone UCI") def toAbalone = m + def toDameo = sys.error("Can't make a dameo UCI from a abalone UCI") + } + + final case class DameoMove(m: dameo.format.Uci.Move) + extends Move( + Pos.Dameo(m.orig), + Pos.Dameo(m.dest), + m.promotion.map(Role.DameoPromotableRole), + m.capture match { + case Some(capture) => Some(List(Pos.Dameo(capture))) + case None => None + } + ) + with Dameo { + def gameLogic = GameLogic.Dameo() + def uci = m.uci + def shortUci = m.shortUci + def fishnetUci = m.uci + val unwrap = m + def toDraughts = sys.error("Can't make a draughts UCI from a dameo UCI") + def toChess = sys.error("Can't make a chess UCI from a dameo UCI") + def toFairySF = sys.error("Can't make a fairysf UCI from a dameo UCI") + def toSamurai = sys.error("Can't make a samurai UCI from a dameo UCI") + def toTogyzkumalak = sys.error("Can't make a togyzkumalak UCI from a dameo UCI") + def toGo = sys.error("Can't make a go UCI from a dameo UCI") + def toBackgammon = sys.error("Can't make a backgammon UCI from a dameo UCI") + def toAbalone = sys.error("Can't make a abalone UCI from a dameo UCI") + def toDameo = m } sealed abstract class Drop( @@ -262,6 +300,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a chess UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a chess UCI") def toAbalone = sys.error("Can't make a abalone UCI from a chess UCI") + def toDameo = sys.error("Can't make a dameo UCI from a chess UCI") } final case class FairySFDrop(d: fairysf.format.Uci.Drop) @@ -284,6 +323,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a fairysf UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a fairysf UCI") def toAbalone = sys.error("Can't make a abalone UCI from a fairysf UCI") + def toDameo = sys.error("Can't make a dameo UCI from a fairysf UCI") } final case class GoDrop(d: go.format.Uci.Drop) @@ -306,6 +346,7 @@ object Uci { def toGo = d def toBackgammon = sys.error("Can't make a backgammon UCI from a go UCI") def toAbalone = sys.error("Can't make a abalone UCI from a go UCI") + def toDameo = sys.error("Can't make a dameo UCI from a go UCI") } final case class BackgammonDrop(d: backgammon.format.Uci.Drop) @@ -332,6 +373,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = d def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class Lift( @@ -359,6 +401,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = l def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class Pass() extends Uci { @@ -382,6 +425,7 @@ object Uci { def toGo = p def toBackgammon = sys.error("Can't make a backgammon UCI from a go UCI") def toAbalone = sys.error("Can't make a abalone UCI from a go UCI") + def toDameo = sys.error("Can't make a dameo UCI from a go UCI") } sealed abstract class SelectSquares( @@ -411,6 +455,7 @@ object Uci { def toGo = ss def toBackgammon = sys.error("Can't make a backgammon UCI from a go UCI") def toAbalone = sys.error("Can't make a abalone UCI from a go UCI") + def toDameo = sys.error("Can't make a dameo UCI from a go UCI") } sealed abstract class DiceRoll( @@ -440,6 +485,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a chess UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a chess UCI") def toAbalone = sys.error("Can't make a abalone UCI from a chess UCI") + def toDameo = sys.error("Can't make a dameo UCI from a chess UCI") } final case class BackgammonDiceRoll(dr: backgammon.format.Uci.DiceRoll) @@ -463,6 +509,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = dr def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class DoRoll() extends Uci { @@ -486,6 +533,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a chess UCI") def toBackgammon = sys.error("Can't make a backgammon UCI from a chess UCI") def toAbalone = sys.error("Can't make a abalone UCI from a chess UCI") + def toDameo = sys.error("Can't make a dameo UCI from a chess UCI") } final case class BackgammonDoRoll(dr: backgammon.format.Uci.DoRoll) extends DoRoll() with Backgammon { @@ -505,6 +553,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = dr def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class Undo() extends Uci { @@ -528,6 +577,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = u def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class EndTurn() extends Uci { @@ -551,6 +601,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = et def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } sealed abstract class CubeAction( @@ -580,6 +631,7 @@ object Uci { def toGo = sys.error("Can't make a go UCI from a backgammon UCI") def toBackgammon = ca def toAbalone = sys.error("Can't make a abalone UCI from a backgammon UCI") + def toDameo = sys.error("Can't make a dameo UCI from a backgammon UCI") } def wrap(uci: chess.format.Uci): Uci = uci match { @@ -627,6 +679,10 @@ object Uci { case m: abalone.format.Uci.Move => AbaloneMove(m) } + def wrap(uci: dameo.format.Uci): Uci = uci match { + case m: dameo.format.Uci.Move => DameoMove(m) + } + object Move { private def draughtsCaptures(captures: Option[List[Pos]]): Option[List[draughts.Pos]] = @@ -657,6 +713,12 @@ object Uci { case None => None } + private def dameoCaptures(captures: Option[List[Pos]]): Option[dameo.Pos] = + captures.flatMap(_.headOption match { + case Some(Pos.Dameo(c)) => Some(c) + case _ => None + }) + def apply( lib: GameLogic, orig: Pos, @@ -721,6 +783,15 @@ object Uci { dest ) ) + case (GameLogic.Dameo(), Pos.Dameo(orig), Pos.Dameo(dest)) => + DameoMove( + dameo.format.Uci.Move.apply( + orig, + dest, + promotion.map(_.toDameo), + dameoCaptures(capture) + ) + ) case _ => sys.error("Mismatched gamelogic types 23") } @@ -732,6 +803,7 @@ object Uci { case GameLogic.Togyzkumalak() => togyzkumalak.format.Uci.Move(move).map(TogyzkumalakMove) case GameLogic.Backgammon() => backgammon.format.Uci.Move(move).map(BackgammonMove) case GameLogic.Abalone() => abalone.format.Uci.Move(move).map(AbaloneMove) + case GameLogic.Dameo() => dameo.format.Uci.Move(move).map(DameoMove) case _ => sys.error("Invalid lib gf and move combo for Uci") } @@ -743,6 +815,7 @@ object Uci { case GameLogic.Togyzkumalak() => togyzkumalak.format.Uci.Move.piotr(move).map(TogyzkumalakMove) case GameLogic.Backgammon() => backgammon.format.Uci.Move.piotr(move).map(BackgammonMove) case GameLogic.Abalone() => abalone.format.Uci.Move.piotr(move).map(AbaloneMove) + case GameLogic.Dameo() => dameo.format.Uci.Move.piotr(move).map(DameoMove) case _ => sys.error("Invalid lib gf and move combo for piotr") } @@ -764,6 +837,7 @@ object Uci { case GameLogic.Go() => None case GameLogic.Backgammon() => backgammon.format.Uci.Move.fromStrings(origS, destS).map(BackgammonMove) case GameLogic.Abalone() => abalone.format.Uci.Move.fromStrings(origS, destS).map(AbaloneMove) + case GameLogic.Dameo() => dameo.format.Uci.Move.fromStrings(origS, destS, promS).map(DameoMove) } } @@ -775,6 +849,7 @@ object Uci { case GameLogic.Samurai() => None case GameLogic.Togyzkumalak() => None case GameLogic.Abalone() => None + case GameLogic.Dameo() => None case GameLogic.Chess() => chess.format.Uci.Drop.fromStrings(roleS, posS).map(ChessDrop) case GameLogic.FairySF() => @@ -803,6 +878,7 @@ object Uci { case GameLogic.Backgammon() => backgammon.format.Uci.Lift.fromStrings(posS).map(BackgammonLift) case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -815,6 +891,7 @@ object Uci { case GameLogic.Togyzkumalak() => None case GameLogic.Backgammon() => None case GameLogic.Abalone() => None + case GameLogic.Dameo() => None case GameLogic.Chess() => None case GameLogic.FairySF() => None case GameLogic.Go() => GoPass(go.format.Uci.Pass()).some @@ -831,6 +908,7 @@ object Uci { case GameLogic.Togyzkumalak() => None case GameLogic.Backgammon() => None case GameLogic.Abalone() => None + case GameLogic.Dameo() => None case GameLogic.Chess() => None case GameLogic.FairySF() => None case GameLogic.Go() => @@ -863,6 +941,7 @@ object Uci { case GameLogic.Backgammon() => BackgammonDiceRoll(backgammon.format.Uci.DiceRoll.fromStrings(dice)).some case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -879,6 +958,7 @@ object Uci { case GameLogic.Go() => None case GameLogic.Backgammon() => BackgammonDoRoll(backgammon.format.Uci.DoRoll()).some case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -895,6 +975,7 @@ object Uci { case GameLogic.Go() => None case GameLogic.Backgammon() => BackgammonUndo(backgammon.format.Uci.Undo()).some case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -911,6 +992,7 @@ object Uci { case GameLogic.Go() => None case GameLogic.Backgammon() => BackgammonEndTurn(backgammon.format.Uci.EndTurn()).some case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -928,6 +1010,7 @@ object Uci { case GameLogic.Backgammon() => backgammon.format.Uci.CubeAction.fromStrings(interaction).map(BackgammonCubeAction) case GameLogic.Abalone() => None + case GameLogic.Dameo() => None } } @@ -982,6 +1065,12 @@ object Uci { w.san ) + final case class DameoWithSan(w: dameo.format.Uci.WithSan) + extends WithSan( + wrap(w.uci), + w.san + ) + object WithSan { def apply(lib: GameLogic, uci: Uci, san: String): WithSan = (lib, uci) match { @@ -1000,6 +1089,8 @@ object Uci { Uci.BackgammonWithSan(backgammon.format.Uci.WithSan(u.unwrap, san)) case (GameLogic.Abalone(), u: Uci.Abalone) => Uci.AbaloneWithSan(abalone.format.Uci.WithSan(u.unwrap, san)) + case (GameLogic.Dameo(), Uci.DameoMove(uci)) => + Uci.DameoWithSan(dameo.format.Uci.WithSan(uci, san)) case _ => sys.error("Mismatched gamelogic types 24") } @@ -1021,6 +1112,8 @@ object Uci { BackgammonMove(backgammon.format.Uci(move)) case (GameLogic.Abalone(), strategygames.Move.Abalone(move)) => AbaloneMove(abalone.format.Uci(move)) + case (GameLogic.Dameo(), strategygames.Move.Dameo(move)) => + DameoMove(dameo.format.Uci(move, withCaptures)) case _ => sys.error("Mismatched gamelogic types 25") } @@ -1035,6 +1128,7 @@ object Uci { case (GameLogic.Backgammon(), strategygames.Drop.Backgammon(drop)) => BackgammonDrop(backgammon.format.Uci(drop)) case (GameLogic.Abalone(), _) => sys.error("Drop not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("Drop not implemented for abalone") case _ => sys.error(s"Drop not implemented for ${lib}") } @@ -1048,6 +1142,7 @@ object Uci { case (GameLogic.Backgammon(), strategygames.Lift.Backgammon(lift)) => BackgammonLift(backgammon.format.Uci(lift)) case (GameLogic.Abalone(), _) => sys.error("Lift not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("Lift not implemented for abalone") case _ => sys.error(s"Lift not implemented for ${lib}") } @@ -1060,6 +1155,7 @@ object Uci { case (GameLogic.Go(), strategygames.Pass.Go(pass)) => GoPass(go.format.Uci(pass)) case (GameLogic.Backgammon(), _) => sys.error("Pass not implemented for backgammon") case (GameLogic.Abalone(), _) => sys.error("Pass not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("Pass not implemented for dameo") } def apply(lib: GameLogic, selectSquares: strategygames.SelectSquares) = (lib, selectSquares) match { @@ -1072,6 +1168,7 @@ object Uci { GoSelectSquares(go.format.Uci(selectSquares)) case (GameLogic.Backgammon(), _) => sys.error("SelectSquares not implemented for backgammon") case (GameLogic.Abalone(), _) => sys.error("SelectSquares not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("SelectSquares not implemented for dameo") } def apply(lib: GameLogic, diceRoll: strategygames.DiceRoll) = (lib, diceRoll) match { @@ -1085,6 +1182,7 @@ object Uci { case (GameLogic.Backgammon(), strategygames.DiceRoll.Backgammon(diceRoll)) => BackgammonDiceRoll(backgammon.format.Uci(diceRoll)) case (GameLogic.Abalone(), _) => sys.error("DiceRoll not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("DiceRoll not implemented for dameo") case _ => sys.error(s"DiceRoll not implemented for ${lib}") } @@ -1098,6 +1196,7 @@ object Uci { case (GameLogic.Backgammon(), strategygames.EndTurn.Backgammon(endTurn)) => BackgammonEndTurn(backgammon.format.Uci(endTurn)) case (GameLogic.Abalone(), _) => sys.error("EndTurn not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("EndTurn not implemented for dameo") } def apply(lib: GameLogic, cubeAction: strategygames.CubeAction) = (lib, cubeAction) match { @@ -1110,6 +1209,7 @@ object Uci { case (GameLogic.Backgammon(), strategygames.CubeAction.Backgammon(cubeAction)) => BackgammonCubeAction(backgammon.format.Uci(cubeAction)) case (GameLogic.Abalone(), _) => sys.error("CubeAction not implemented for abalone") + case (GameLogic.Dameo(), _) => sys.error("CubeAction not implemented for dameo") case _ => sys.error(s"CubeAction not implemented for ${lib}") } @@ -1122,6 +1222,7 @@ object Uci { case GameLogic.Go() => go.format.Uci(action).map(wrap) case GameLogic.Backgammon() => backgammon.format.Uci(action).map(wrap) case GameLogic.Abalone() => abalone.format.Uci(action).map(wrap) + case GameLogic.Dameo() => dameo.format.Uci(action).map(wrap) } def apply(v: Variant, action: String): Option[Uci] = @@ -1136,6 +1237,7 @@ object Uci { case GameLogic.Go() => go.format.Uci.piotr(action).map(wrap) case GameLogic.Backgammon() => backgammon.format.Uci.piotr(action).map(wrap) case GameLogic.Abalone() => abalone.format.Uci.piotr(action).map(wrap) + case GameLogic.Dameo() => dameo.format.Uci.piotr(action).map(wrap) } def readList(lib: GameLogic, gf: GameFamily, actions: String): Option[List[Uci]] = lib match { @@ -1147,6 +1249,7 @@ object Uci { case GameLogic.Go() => go.format.Uci.readList(actions).map(_.map(wrap)) case GameLogic.Backgammon() => backgammon.format.Uci.readList(actions).map(_.map(wrap)) case GameLogic.Abalone() => abalone.format.Uci.readList(actions).map(_.map(wrap)) + case GameLogic.Dameo() => dameo.format.Uci.readList(actions).map(_.map(wrap)) } def writeList(actions: List[Uci]): String = diff --git a/src/main/scala/format/UciCharPair.scala b/src/main/scala/format/UciCharPair.scala index f269171f0..59a48c6c4 100644 --- a/src/main/scala/format/UciCharPair.scala +++ b/src/main/scala/format/UciCharPair.scala @@ -20,6 +20,7 @@ object UciCharPair { case (GameLogic.Backgammon(), uci: Uci.Backgammon) => strategygames.backgammon.format.UciCharPair(uci.unwrap) case (GameLogic.Abalone(), uci: Uci.Abalone) => strategygames.abalone.format.UciCharPair(uci.unwrap) + case (GameLogic.Dameo(), uci: Uci.Dameo) => strategygames.dameo.format.UciCharPair(uci.unwrap) case _ => sys.error("Mismatched gamelogic and UciCharPair") } diff --git a/src/main/scala/format/UciDump.scala b/src/main/scala/format/UciDump.scala index 11cad5037..4293fd9b5 100644 --- a/src/main/scala/format/UciDump.scala +++ b/src/main/scala/format/UciDump.scala @@ -30,6 +30,8 @@ object UciDump { strategygames.backgammon.format.UciDump(actionStrs, initialFen.map(_.toBackgammon), variant) case (GameLogic.Abalone(), Variant.Abalone(variant)) => strategygames.abalone.format.UciDump(actionStrs, initialFen.map(_.toAbalone), variant) + case (GameLogic.Dameo(), Variant.Dameo(variant)) => + strategygames.dameo.format.UciDump(actionStrs, initialFen.map(_.toDameo), variant) case _ => sys.error("Mismatched gamelogic types 12") } @@ -58,6 +60,8 @@ object UciDump { strategygames.backgammon.format.UciDump.action(variant)(a) case (GameLogic.Abalone(), Variant.Abalone(variant), Move.Abalone(a)) => strategygames.abalone.format.UciDump.action(variant)(a) + case (GameLogic.Dameo(), Variant.Dameo(variant), Move.Dameo(a)) => + strategygames.dameo.format.UciDump.action(variant)(a) case _ => sys.error("Mismatched gamelogic types 13") } diff --git a/src/main/scala/format/pgn/Binary.scala b/src/main/scala/format/pgn/Binary.scala index c20f299e1..0fbd26301 100644 --- a/src/main/scala/format/pgn/Binary.scala +++ b/src/main/scala/format/pgn/Binary.scala @@ -13,6 +13,7 @@ object Binary { case GameLogic.Go() => strategygames.go.format.pgn.Binary.writeMoves(ms) case GameLogic.Backgammon() => strategygames.backgammon.format.pgn.Binary.writeMoves(ms) case GameLogic.Abalone() => strategygames.abalone.format.pgn.Binary.writeMoves(ms) + case GameLogic.Dameo() => strategygames.dameo.format.pdn.Binary.writeMoves(ms) } def writeActionStrs(gf: GameFamily, ms: ActionStrs) = gf.gameLogic match { @@ -24,6 +25,7 @@ object Binary { case GameLogic.Go() => strategygames.go.format.pgn.Binary.writeActionStrs(ms) case GameLogic.Backgammon() => strategygames.backgammon.format.pgn.Binary.writeActionStrs(ms) case GameLogic.Abalone() => strategygames.abalone.format.pgn.Binary.writeActionStrs(ms) + case GameLogic.Dameo() => strategygames.dameo.format.pdn.Binary.writeActionStrs(ms) } def readActionStrs(gl: GameLogic, bs: List[Byte]) = gl match { @@ -35,6 +37,7 @@ object Binary { case GameLogic.Go() => strategygames.go.format.pgn.Binary.readActionStrs(bs) case GameLogic.Backgammon() => strategygames.backgammon.format.pgn.Binary.readActionStrs(bs) case GameLogic.Abalone() => strategygames.abalone.format.pgn.Binary.readActionStrs(bs) + case GameLogic.Dameo() => strategygames.dameo.format.pdn.Binary.readActionStrs(bs) } def readActionStrs(gl: GameLogic, bs: List[Byte], nb: Int) = gl match { @@ -46,6 +49,7 @@ object Binary { case GameLogic.Go() => strategygames.go.format.pgn.Binary.readActionStrs(bs, nb) case GameLogic.Backgammon() => strategygames.backgammon.format.pgn.Binary.readActionStrs(bs, nb) case GameLogic.Abalone() => strategygames.abalone.format.pgn.Binary.readActionStrs(bs, nb) + case GameLogic.Dameo() => strategygames.dameo.format.pdn.Binary.readActionStrs(bs, nb) } } diff --git a/src/main/scala/format/pgn/Dumper.scala b/src/main/scala/format/pgn/Dumper.scala index 89d3aece0..404e1b987 100644 --- a/src/main/scala/format/pgn/Dumper.scala +++ b/src/main/scala/format/pgn/Dumper.scala @@ -28,6 +28,8 @@ object Dumper { backgammon.format.pgn.Dumper(data) case (GameLogic.Abalone(), StratMove.Abalone(data)) => abalone.format.pgn.Dumper(data) + case (GameLogic.Dameo(), StratMove.Dameo(data)) => + dameo.format.pdn.Dumper(data) case _ => sys.error("Mismatched gamelogic types 31") } diff --git a/src/main/scala/format/pgn/Reader.scala b/src/main/scala/format/pgn/Reader.scala index c12dc38d2..fa9158bac 100644 --- a/src/main/scala/format/pgn/Reader.scala +++ b/src/main/scala/format/pgn/Reader.scala @@ -10,6 +10,7 @@ import strategygames.togyzkumalak.format.pgn.{ Reader => TogyzkumalakReader } import strategygames.go.format.pgn.{ Reader => GoReader } import strategygames.backgammon.format.pgn.{ Reader => BackgammonReader } import strategygames.abalone.format.pgn.{ Reader => AbaloneReader } +import strategygames.dameo.format.pdn.{ Reader => DameoReader } import cats.data.Validated @@ -68,6 +69,12 @@ object Reader { case class AbaloneIncomplete(replay: abalone.Replay, failure: String) extends Result { def valid = Validated.invalid(failure) } + case class DameoComplete(replay: dameo.Replay) extends Result { + def valid = Validated.valid(Replay.Dameo(replay)) + } + case class DameoIncomplete(replay: dameo.Replay, failure: String) extends Result { + def valid = Validated.invalid(failure) + } def wrap(result: ChessReader.Result) = result match { case ChessReader.Result.Complete(replay) => Result.ChessComplete(replay) @@ -111,6 +118,11 @@ object Reader { case AbaloneReader.Result.Incomplete(replay, failure) => Result.AbaloneIncomplete(replay, failure) } + def wrap(result: DameoReader.Result) = result match { + case DameoReader.Result.Complete(replay) => Result.DameoComplete(replay) + case DameoReader.Result.Incomplete(replay, failure) => Result.DameoIncomplete(replay, failure) + } + } def fullWithSans( @@ -129,6 +141,7 @@ object Reader { case GameLogic.Go() => GoReader.fullWithSans(pgn, op, tags).map(Result.wrap) case GameLogic.Backgammon() => BackgammonReader.fullWithSans(pgn, op, tags).map(Result.wrap) case GameLogic.Abalone() => AbaloneReader.fullWithSans(pgn, op, tags).map(Result.wrap) + case GameLogic.Dameo() => DameoReader.fullWithSans(pgn, op, tags, iteratedCapts).map(Result.wrap) } // TODO Merge the following two functions by refactoring Sans and integrating to other libs @@ -158,6 +171,10 @@ object Reader { sys.error("Sans not implemented for backgammon") case GameLogic.Abalone() => sys.error("Sans not implemented for abalone") + case GameLogic.Dameo() => + DameoReader + .replayResultFromActionStrsUsingSan(actionStrs, op, tags) + .map(Result.wrap) } def replayResultFromActionStrs( @@ -187,6 +204,10 @@ object Reader { BackgammonReader.replayResultFromActionStrs(actionStrs, op, tags).map(Result.wrap) case GameLogic.Abalone() => AbaloneReader.replayResultFromActionStrs(actionStrs, op, tags).map(Result.wrap) + case GameLogic.Dameo() => + sys.error( + "replayResultFromActionStrs not implemented for dameo. Use replayResultFromActionStrsUsingSan" + ) } } diff --git a/src/main/scala/format/pgn/tagModel.scala b/src/main/scala/format/pgn/tagModel.scala index 2f7822b03..19ae1639b 100644 --- a/src/main/scala/format/pgn/tagModel.scala +++ b/src/main/scala/format/pgn/tagModel.scala @@ -87,6 +87,11 @@ case class Tags(value: List[Tag]) extends AnyVal { strategygames.abalone.variant.Variant byName _ } + def dameoVariant: Option[strategygames.dameo.variant.Variant] = + apply(_.Variant).map(_.toLowerCase).flatMap { + strategygames.dameo.variant.Variant byName _ + } + // TODO: this will need to be tested. We'll want to look at the _actual_ values that // come in via these tags and ensure that the order we look at them is appropriate // what a mess this function is. @@ -113,6 +118,10 @@ case class Tags(value: List[Tag]) extends AnyVal { .orElse( abaloneVariant .map(strategygames.variant.Variant.Abalone) + .orElse( + dameoVariant + .map(strategygames.variant.Variant.Dameo) + ) ) ) ) @@ -139,6 +148,8 @@ case class Tags(value: List[Tag]) extends AnyVal { apply(_.FEN).map(strategygames.backgammon.format.FEN.apply) def abaloneFen: Option[abalone.format.FEN] = apply(_.FEN).map(strategygames.abalone.format.FEN.apply) + def dameoFen: Option[dameo.format.FEN] = apply(_.FEN).map(strategygames.dameo.format.FEN.apply) + def fen: Option[format.FEN] = variant match { case Some(strategygames.variant.Variant.Draughts(_)) => draughtsFen.map(format.FEN.Draughts) @@ -148,6 +159,7 @@ case class Tags(value: List[Tag]) extends AnyVal { case Some(strategygames.variant.Variant.Go(_)) => goFen.map(format.FEN.Go) case Some(strategygames.variant.Variant.Backgammon(_)) => backgammonFen.map(format.FEN.Backgammon) case Some(strategygames.variant.Variant.Abalone(_)) => abaloneFen.map(format.FEN.Abalone) + case Some(strategygames.variant.Variant.Dameo(_)) => dameoFen.map(format.FEN.Dameo) case Some(strategygames.variant.Variant.Chess(_)) | None => chessFen.map(format.FEN.Chess) case Some(_) => sys.error("invalid variant type for fen") } diff --git a/src/main/scala/opening/Ecopening.scala b/src/main/scala/opening/Ecopening.scala index 10ce1fe71..3c03183ff 100644 --- a/src/main/scala/opening/Ecopening.scala +++ b/src/main/scala/opening/Ecopening.scala @@ -198,4 +198,26 @@ object Ecopening { lazy val moveList = f.moves.split(' ').toList } + final case class Dameo(f: strategygames.dameo.opening.Ecopening) + extends Ecopening( + f.eco, + f.variantGrouping, + f.family, + f.name, + f.moves, + f.fen, + f.lastMoveUci + ) { + override def toString = f.toString() + + def ecoName = s"${f.eco} ${f.name}" + def compare(other: Ecopening) = f.eco compare other.eco + def possibleContinuation(other: Ecopening) = + (f.variantGrouping == other.variantGrouping) && + ((f.lastMoveUci == "" && other.size == 1) || ((f.size + 1 == other.size) && (f.moveList == other.moveList.reverse.tail.reverse))) && + other.lastMoveUci != "" + lazy val size = f.moveList.size + lazy val moveList = f.moves.split(' ').toList + } + } diff --git a/src/main/scala/opening/EcopeningDB.scala b/src/main/scala/opening/EcopeningDB.scala index 52919142b..d69bdcc25 100644 --- a/src/main/scala/opening/EcopeningDB.scala +++ b/src/main/scala/opening/EcopeningDB.scala @@ -15,6 +15,7 @@ object EcopeningDB { case GameLogic.Go() => strategygames.go.opening.EcopeningDB.allByEco.map{ case (a,b) => a -> Ecopening.Go(b)} case GameLogic.Backgammon() => strategygames.backgammon.opening.EcopeningDB.allByEco.map{ case (a,b) => a -> Ecopening.Backgammon(b)} case GameLogic.Abalone() => strategygames.abalone.opening.EcopeningDB.allByEco.map{ case (a,b) => a -> Ecopening.Abalone(b)} + case GameLogic.Dameo() => strategygames.dameo.opening.EcopeningDB.allByEco.map{ case (a,b) => a -> Ecopening.Dameo(b)} case _ => sys.error("Mismatched gamelogic types ecopening db") } @@ -27,6 +28,7 @@ object EcopeningDB { case GameLogic.Go() => strategygames.go.opening.EcopeningDB.allByFen.map{ case (a,b) => a -> Ecopening.Go(b)} case GameLogic.Backgammon() => strategygames.backgammon.opening.EcopeningDB.allByFen.map{ case (a,b) => a -> Ecopening.Backgammon(b)} case GameLogic.Abalone() => strategygames.abalone.opening.EcopeningDB.allByFen.map{ case (a,b) => a -> Ecopening.Abalone(b)} + case GameLogic.Dameo() => strategygames.dameo.opening.EcopeningDB.allByFen.map{ case (a,b) => a -> Ecopening.Dameo(b)} case _ => sys.error("Mismatched gamelogic types ecopening db") } @@ -39,6 +41,7 @@ object EcopeningDB { case GameLogic.Go() => strategygames.go.opening.EcopeningDB.all.map(Ecopening.Go) case GameLogic.Backgammon() => strategygames.backgammon.opening.EcopeningDB.all.map(Ecopening.Backgammon) case GameLogic.Abalone() => strategygames.abalone.opening.EcopeningDB.all.map(Ecopening.Abalone) + case GameLogic.Dameo() => strategygames.dameo.opening.EcopeningDB.all.map(Ecopening.Dameo) case _ => sys.error("Mismatched gamelogic types ecopening db") } diff --git a/src/main/scala/opening/FullOpening.scala b/src/main/scala/opening/FullOpening.scala index 371983c12..84102e380 100644 --- a/src/main/scala/opening/FullOpening.scala +++ b/src/main/scala/opening/FullOpening.scala @@ -107,6 +107,17 @@ object FullOpening { } + final case class Dameo(f: strategygames.dameo.opening.FullOpening) + extends FullOpening( + f.eco, + f.name, + f.fen + ) { + + override def toString = f.toString() + + } + case class AtPly(opening: FullOpening, ply: Int) } diff --git a/src/main/scala/opening/FullOpeningDB.scala b/src/main/scala/opening/FullOpeningDB.scala index a2f6b90c9..3eb0e1380 100644 --- a/src/main/scala/opening/FullOpeningDB.scala +++ b/src/main/scala/opening/FullOpeningDB.scala @@ -54,6 +54,12 @@ object FullOpeningDB { .map( FullOpening.Abalone ) + case (GameLogic.Dameo(), FEN.Dameo(fen)) => + strategygames.dameo.opening.FullOpeningDB + .findByFen(fen) + .map( + FullOpening.Dameo + ) case _ => sys.error("Mismatched gamelogic types full opening db") } @@ -92,6 +98,10 @@ object FullOpeningDB { strategygames.abalone.opening.FullOpeningDB .search(actionStrs) .map(fo => FullOpening.AtPly(FullOpening.Abalone(fo.opening), fo.ply)) + case GameLogic.Dameo() => + strategygames.dameo.opening.FullOpeningDB + .search(actionStrs) + .map(fo => FullOpening.AtPly(FullOpening.Dameo(fo.opening), fo.ply)) } private def draughtsFENs(fens: Vector[FEN]): Vector[strategygames.draughts.format.FEN] = @@ -158,6 +168,14 @@ object FullOpeningDB { } ) + private def dameoFENs(fens: Vector[FEN]): Vector[strategygames.dameo.format.FEN] = + fens.flatMap(f => + f match { + case f: FEN.Dameo => Some(f.f) + case _ => None + } + ) + def searchInFens(lib: GameLogic, fens: Vector[FEN]): Option[FullOpening] = lib match { case GameLogic.Draughts() => strategygames.draughts.opening.FullOpeningDB @@ -207,6 +225,12 @@ object FullOpeningDB { abaloneFENs(fens) ) .map(FullOpening.Abalone) + case GameLogic.Dameo() => + strategygames.dameo.opening.FullOpeningDB + .searchInFens( + dameoFENs(fens) + ) + .map(FullOpening.Dameo) } } diff --git a/src/main/scala/variant/Variant.scala b/src/main/scala/variant/Variant.scala index 5c635d008..22fa56dd5 100644 --- a/src/main/scala/variant/Variant.scala +++ b/src/main/scala/variant/Variant.scala @@ -25,6 +25,7 @@ abstract class Variant( def toGo: go.variant.Variant def toBackgammon: backgammon.variant.Variant def toAbalone: abalone.variant.Variant + def toDameo: dameo.variant.Variant def pieces: PieceMap @@ -133,6 +134,7 @@ object Variant { def toGo = sys.error("Can't convert chess to go") def toBackgammon = sys.error("Can't convert chess to backgammon") def toAbalone = sys.error("Can't convert chess to abalone") + def toDameo = sys.error("Can't convert chess to dameo") def pieces: PieceMap = v.pieces.map { case (pos, piece) => (Pos.Chess(pos), (Piece.Chess(piece), 1)) } @@ -249,6 +251,7 @@ object Variant { def toGo = sys.error("Can't convert draughts to go") def toBackgammon = sys.error("Can't convert draughts to backgammon") def toAbalone = sys.error("Can't convert draughts to abalone") + def toDameo = sys.error("Can't convert draughts to dameo") def pieces: PieceMap = v.pieces.map { case (pos, piece) => (Pos.Draughts(pos), (Piece.Draughts(piece), 1)) } @@ -364,6 +367,7 @@ object Variant { def toGo = sys.error("Can't convert fairysf to go") def toBackgammon = sys.error("Can't convert fairysf to backgammon") def toAbalone = sys.error("Can't convert fairysf to abalone") + def toDameo = sys.error("Can't convert fairysf to dameo") def pieces: PieceMap = v.pieces.map { case (pos, piece) => (Pos.FairySF(pos), (Piece.FairySF(piece), 1)) } @@ -478,6 +482,7 @@ object Variant { def toGo = sys.error("Can't convert samurai to go") def toBackgammon = sys.error("Can't convert samurai to backgammon") def toAbalone = sys.error("Can't convert samurai to abalone") + def toDameo = sys.error("Can't convert samurai to dameo") def pieces: PieceMap = v.pieces.map { case (pos, (piece, count)) => (Pos.Samurai(pos), (Piece.Samurai(piece), count)) @@ -589,6 +594,7 @@ object Variant { def toGo = sys.error("Can't convert togyzkumalak to go") def toBackgammon = sys.error("Can't convert togyzkumalak to backgammon") def toAbalone = sys.error("Can't convert togyzkumalak to abalone") + def toDameo = sys.error("Can't convert togyzkumalak to dameo") def pieces: PieceMap = v.pieces.map { case (pos, (piece, count)) => (Pos.Togyzkumalak(pos), (Piece.Togyzkumalak(piece), count)) @@ -700,6 +706,7 @@ object Variant { def toGo = v def toBackgammon = sys.error("Can't convert go to backgammon") def toAbalone = sys.error("Can't convert go to abalone") + def toDameo = sys.error("Can't convert go to dameo") def pieces: PieceMap = v.pieces.map { case (pos, piece) => (Pos.Go(pos), (Piece.Go(piece), 1)) } @@ -812,6 +819,7 @@ object Variant { def toGo = sys.error("Can't convert backgammon to go") def toBackgammon = v def toAbalone = sys.error("Can't convert backgammon to abalone") + def toDameo = sys.error("Can't convert backgammon to dameo") def pieces: PieceMap = v.pieces.map { case (pos, (piece, count)) => (Pos.Backgammon(pos), (Piece.Backgammon(piece), count)) @@ -921,6 +929,7 @@ object Variant { def toGo = sys.error("Can't convert abalone to go") def toBackgammon = sys.error("Can't convert abalone to backgammon") def toAbalone = v + def toDameo = sys.error("Can't convert abalone to dameo") def pieces: PieceMap = v.pieces.map { case (pos, piece) => (Pos.Abalone(pos), (Piece.Abalone(piece), 1)) } @@ -1014,6 +1023,118 @@ object Variant { def playerColors: Map[Player, String] = gameFamily.playerColors } + case class Dameo(v: dameo.variant.Variant) + extends Variant( + id = v.id, + key = v.key, + fishnetKey = v.key, + name = v.name, + standardInitialPosition = v.standardInitialPosition + ) { + + def toChess = sys.error("Can't convert dameo to chess") + def toDraughts = sys.error("Can't convert dameo to draughts") + def toFairySF = sys.error("Can't convert dameo to fairysf") + def toSamurai = sys.error("Can't convert dameo to samurai") + def toTogyzkumalak = sys.error("Can't convert dameo to togyzkumalak") + def toGo = sys.error("Can't convert dameo to go") + def toBackgammon = sys.error("Can't convert dameo to backgammon") + def toAbalone = sys.error("Can't convert dameo to abalone") + def toDameo = v + + def pieces: PieceMap = + v.pieces.map { case (pos, piece) => (Pos.Dameo(pos), (Piece.Dameo(piece), 1)) } + + def standardVariant: Boolean = false + def fromPositionVariant: Boolean = false + def exoticChessVariant: Boolean = false + def frisianVariant: Boolean = false + def draughts64Variant: Boolean = false + + def exotic: Boolean = v.exotic + + def baseVariant: Boolean = v.baseVariant + def fenVariant: Boolean = v.fenVariant + def variableInitialFen: Boolean = v.variableInitialFen + + def hasAnalysisBoard: Boolean = v.hasAnalysisBoard + def hasFishnet: Boolean = v.hasFishnet + + def p1IsBetterVariant: Boolean = v.p1IsBetterVariant + def blindModeVariant: Boolean = v.blindModeVariant + + def materialImbalanceVariant: Boolean = v.materialImbalanceVariant + + def dropsVariant: Boolean = false + def onlyDropsVariant: Boolean = false + def hasDetachedPocket: Boolean = false + def hasGameScore: Boolean = true + + def canOfferDraw: Boolean = v.canOfferDraw + def ignoreSubmitAction: Boolean = false + + def perfId: Int = v.perfId + def perfIcon: Char = v.perfIcon + + def initialFen: FEN = FEN.Dameo(v.initialFen) + def initialFens: List[FEN] = List(initialFen) + def startPlayer: Player = v.startPlayer + + def recalcStartPlayerForStats: Boolean = false + + def isValidPromotion(promotion: Option[PromotableRole]): Boolean = false + + def checkmate(situation: Situation): Boolean = situation match { + case Situation.Dameo(_) => false + case _ => sys.error("Not passed Dameo objects") + } + + // stalemate not referenced in dameo + def stalemateIsDraw: Boolean = true + + def useRuleOfGinOnInsufficientMaterial: Boolean = false + + def winner(situation: Situation): Option[Player] = situation match { + case Situation.Dameo(situation) => v.winner(situation) + case _ => sys.error("Not passed Dameo objects") + } + + @nowarn def specialEnd(situation: Situation): Boolean = situation match { + case Situation.Dameo(situation) => v.specialEnd(situation) + case _ => sys.error("Not passed Dameo objects") + } + + @nowarn def specialDraw(situation: Situation): Boolean = situation match { + case Situation.Dameo(situation) => v.specialDraw(situation) + case _ => sys.error("Not passed Dameo objects") + } + + def hasMoveEffects: Boolean = v.hasMoveEffects + + def addVariantEffect(move: Move): Move = move match { + case Move.Dameo(move) => Move.Dameo(v.addVariantEffect(move)) + case _ => sys.error("Not passed Dameo objects") + } + def valid(board: Board, strict: Boolean): Boolean = board match { + case Board.Dameo(board) => v.valid(board, strict) + case _ => sys.error("Not passed Dameo objects") + } + + val roles: List[Role] = v.roles.map(Role.DameoRole) + + override def equals(that: Any): Boolean = that match { + case Dameo(v2) => v2.equals(v) + case _ => false + } + + def chessVariant: chess.variant.Variant = sys.error("Unimplemented for Dameo") + def gameLogic: GameLogic = GameLogic.Dameo() + def gameFamily: GameFamily = v.gameFamily + + def playerNames: Map[Player, String] = gameFamily.playerNames + def playerColors: Map[Player, String] = gameFamily.playerColors + } + def all: List[Variant] = chess.variant.Variant.all.map(Chess) ::: draughts.variant.Variant.all.map(Draughts) ::: @@ -1022,7 +1143,8 @@ object Variant { togyzkumalak.variant.Variant.all.map(Togyzkumalak) ::: go.variant.Variant.all.map(Go) ::: backgammon.variant.Variant.all.map(Backgammon) ::: - abalone.variant.Variant.all.map(Abalone) + abalone.variant.Variant.all.map(Abalone) ::: + dameo.variant.Variant.all.map(Dameo) def byId = all map { v => (v.id, v) } toMap @@ -1037,6 +1159,7 @@ object Variant { case GameLogic.Go() => go.variant.Variant.all.map(Go) case GameLogic.Backgammon() => backgammon.variant.Variant.all.map(Backgammon) case GameLogic.Abalone() => abalone.variant.Variant.all.map(Abalone) + case GameLogic.Dameo() => dameo.variant.Variant.all.map(Dameo) } def byId(lib: GameLogic) = all(lib) map { v => @@ -1056,6 +1179,7 @@ object Variant { case GameLogic.Go() => Go(go.variant.Variant.default) case GameLogic.Backgammon() => Backgammon(backgammon.variant.Variant.default) case GameLogic.Abalone() => Abalone(abalone.variant.Variant.default) + case GameLogic.Dameo() => Dameo(dameo.variant.Variant.default) } def apply(lib: GameLogic, id: Int): Option[Variant] = byId(lib) get id @@ -1079,6 +1203,7 @@ object Variant { case GameLogic.Go() => go.variant.Variant.openingSensibleVariants.map(Go) case GameLogic.Backgammon() => backgammon.variant.Variant.openingSensibleVariants.map(Backgammon) case GameLogic.Abalone() => abalone.variant.Variant.openingSensibleVariants.map(Abalone) + case GameLogic.Dameo() => dameo.variant.Variant.openingSensibleVariants.map(Dameo) } def divisionSensibleVariants(lib: GameLogic): Set[Variant] = lib match { @@ -1090,6 +1215,7 @@ object Variant { case GameLogic.Go() => go.variant.Variant.divisionSensibleVariants.map(Go) case GameLogic.Backgammon() => backgammon.variant.Variant.divisionSensibleVariants.map(Backgammon) case GameLogic.Abalone() => abalone.variant.Variant.divisionSensibleVariants.map(Abalone) + case GameLogic.Dameo() => dameo.variant.Variant.divisionSensibleVariants.map(Dameo) } def libStandard(lib: GameLogic): Variant = lib match { @@ -1101,6 +1227,7 @@ object Variant { case GameLogic.Go() => Variant.Go(go.variant.Go19x19) case GameLogic.Backgammon() => Variant.Backgammon(backgammon.variant.Backgammon) case GameLogic.Abalone() => Variant.Abalone(abalone.variant.Abalone) + case GameLogic.Dameo() => Variant.Dameo(dameo.variant.Dameo) } // todo all games will be allowed from position (go has 3 variants already!) @@ -1115,6 +1242,7 @@ object Variant { case GameLogic.Go() => Variant.Go(go.variant.Go19x19) case GameLogic.Backgammon() => Variant.Backgammon(backgammon.variant.Backgammon) case GameLogic.Abalone() => Variant.Abalone(abalone.variant.Abalone) + case GameLogic.Dameo() => Variant.Dameo(dameo.variant.Dameo) } def wrap(v: chess.variant.Variant) = Chess(v) @@ -1125,5 +1253,6 @@ object Variant { def wrap(v: go.variant.Variant) = Go(v) def wrap(v: backgammon.variant.Variant) = Backgammon(v) def wrap(v: abalone.variant.Variant) = Abalone(v) + def wrap(v: dameo.variant.Variant) = Dameo(v) } From fc7bc76a585cc01719215cff5334c21e3585c266 Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Sat, 8 Mar 2025 19:28:58 +0000 Subject: [PATCH 2/3] Use capturePos function for dameo --- src/main/scala/dameo/Pos.scala | 8 +++++--- src/main/scala/dameo/Replay.scala | 16 +++++++++++----- src/main/scala/dameo/format/Uci.scala | 5 ++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main/scala/dameo/Pos.scala b/src/main/scala/dameo/Pos.scala index ec8161906..d5884bef5 100644 --- a/src/main/scala/dameo/Pos.scala +++ b/src/main/scala/dameo/Pos.scala @@ -2,6 +2,8 @@ package strategygames.dameo import scala.math.{ abs, max, min } +import scala.annotation.nowarn + // Matches with: https://github.com/Mind-Sports-Games/lila/blob/incoming-prs/ui/chess/src/piotr.ts object Piotr { val lookup: Map[Int, Char] = Map( @@ -190,9 +192,9 @@ object Pos { b <- piotr(piotrs(1)) } yield s"${a.key}${b.key}" - //TODO Dameo - write this - //Works out the pos between the orig and dest that has been jumped over (i.e. where the capture took place - def capturePos(orig: Pos, dest: Pos): Option[Pos] = None + // TODO Dameo - write this + // Works out the pos between the orig and dest that has been jumped over (i.e. where the capture took place + @nowarn def capturePos(orig: Pos, dest: Pos): Option[Pos] = None val A1 = new Pos(0) val B1 = new Pos(1) diff --git a/src/main/scala/dameo/Replay.scala b/src/main/scala/dameo/Replay.scala index f0aa35d4c..c84421a43 100644 --- a/src/main/scala/dameo/Replay.scala +++ b/src/main/scala/dameo/Replay.scala @@ -67,7 +67,7 @@ object Replay { } def replayMove(before: Game, orig: Pos, dest: Pos, capture: Option[Pos], endTurn: Boolean): Move = - // TODO Dameo set this + // TODO Dameo set this. Probably need to pass through promotion as well? Move( piece = before.situation.board.pieces(orig), orig = orig, @@ -88,10 +88,16 @@ object Replay { var state = init var errors = "" - def replayMoveFromUci(orig: Option[Pos], dest: Option[Pos], capture: Option[Pos]): (Game, Move) = + def replayMoveFromUci(orig: Option[Pos], dest: Option[Pos], capture: Boolean): (Game, Move) = (orig, dest) match { case (Some(orig), Some(dest)) => { - val move = replayMove(state, orig, dest, capture, true) + val move = replayMove( + state, + orig, + dest, + if (capture) Pos.capturePos(orig, dest) else None, + true + ) state = state(move) (state, move) } @@ -103,13 +109,13 @@ object Replay { } val gameWithActions: List[(Game, Move)] = - // can flatten as specific EndTurn action marks turn end + // TODO check that using flatten works for Dameo actionStrs.flatten.toList.map { case Uci.Move.moveR(orig, capture, dest) => replayMoveFromUci( Pos.fromKey(orig), Pos.fromKey(dest), - if (capture.length == 1) Pos.fromKey(orig) else None + capture.length == 1 ) case (action: String) => sys.error(s"Invalid action for replay: $action") diff --git a/src/main/scala/dameo/format/Uci.scala b/src/main/scala/dameo/format/Uci.scala index adbf7f80b..d2d8a13b0 100644 --- a/src/main/scala/dameo/format/Uci.scala +++ b/src/main/scala/dameo/format/Uci.scala @@ -53,7 +53,6 @@ object Uci { ( Pos.fromKey(orig), Pos.fromKey(dest), - // TODO Dameo set this capture.length == 1 ) match { case (Some(orig), Some(dest), capture) => @@ -63,7 +62,7 @@ object Uci { dest = dest, // TODO Dameo set this promotion = None, - capture = if (capture) Some(orig) else None + capture = if (capture) Pos.capturePos(orig, dest) else None ) ) case _ => None @@ -77,7 +76,7 @@ object Uci { orig <- move.headOption.flatMap(Pos.piotr) dest <- move.lift(1).flatMap(Pos.piotr) capture <- move.lift(2).nonEmpty.some - } yield Move(orig, dest, None, (if (capture) Some(orig) else None)) + } yield Move(orig, dest, None, (if (capture) Pos.capturePos(orig, dest) else None)) // TODO Dameo use capture and promotion properly def fromStrings(origS: String, destS: String, promS: Option[String]) = for { From bd20438b8cea9d01459f9ae47363d8696633816e Mon Sep 17 00:00:00 2001 From: Matt Tucker Date: Tue, 11 Mar 2025 14:23:25 +0000 Subject: [PATCH 3/3] Make SG more compatible for adding a new game logic. Fix Dameo Roles --- src/main/scala/GameCollection.scala | 19 ++++++-- src/main/scala/Pos.scala | 20 ++++++++ src/main/scala/Role.scala | 14 ++++++ src/main/scala/chess/Role.scala | 10 +++- src/main/scala/dameo/Role.scala | 58 +++++++++++------------ src/main/scala/format/pgn/Reader.scala | 64 ++++++++++++++++++-------- src/main/scala/variant/Variant.scala | 4 +- 7 files changed, 134 insertions(+), 55 deletions(-) diff --git a/src/main/scala/GameCollection.scala b/src/main/scala/GameCollection.scala index a7741a9d7..5959d263c 100644 --- a/src/main/scala/GameCollection.scala +++ b/src/main/scala/GameCollection.scala @@ -59,7 +59,16 @@ object GameLogic { } def all: List[GameLogic] = - List(Chess(), Draughts(), FairySF(), Samurai(), Togyzkumalak(), Go(), Backgammon(), Abalone(), Dameo()) + List( + Chess(), + Draughts(), + FairySF(), + Samurai(), + Togyzkumalak(), + Go(), + Backgammon(), + Abalone() + ) // , Dameo()) // TODO: I'm sure there is a better scala way of doing this def apply(id: Int): GameLogic = id match { @@ -518,8 +527,8 @@ object GameFamily { Go(), Backgammon(), BreakthroughTroyka(), - Abalone(), - Dameo() + Abalone() // , + // Dameo() ) // TODO: I'm sure there is a better scala way of doing this @@ -684,8 +693,8 @@ object GameGroup { Go(), Backgammon(), BreakthroughTroyka(), - Abalone(), - Dameo() + Abalone() // , + // Dameo() ) def medley: List[GameGroup] = all.filter(_.medley) diff --git a/src/main/scala/Pos.scala b/src/main/scala/Pos.scala index 9bf840738..cf3a46ba8 100644 --- a/src/main/scala/Pos.scala +++ b/src/main/scala/Pos.scala @@ -17,6 +17,8 @@ sealed abstract class Pos { def toInt: Int + def gameLogic: GameLogic + override def toString = key def all: List[Pos] @@ -33,6 +35,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index + def gameLogic: GameLogic = GameLogic.Chess() + lazy val all: List[Pos] = chess.Pos.all.map(Chess) } @@ -46,6 +50,8 @@ object Pos { // TODO: not sure this is appropriate, but I don't see why not? lazy val toInt: Int = p.fieldNumber + def gameLogic: GameLogic = GameLogic.Draughts() + // TODO: this only handl 8x8 boards. we should include 10x10 as well. // Not sure if we need a separate type, probably? lazy val all: List[Pos] = draughts.Pos64.all.map(Draughts) @@ -61,6 +67,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index // todo where is this used? Should be 4 for fairy boards? + def gameLogic: GameLogic = GameLogic.FairySF() + lazy val all: List[Pos] = chess.Pos.all.map(Chess) } @@ -73,6 +81,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index + def gameLogic: GameLogic = GameLogic.Samurai() + lazy val all: List[Pos] = samurai.Pos.all.map(Samurai) } @@ -85,6 +95,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index + def gameLogic: GameLogic = GameLogic.Togyzkumalak() + lazy val all: List[Pos] = togyzkumalak.Pos.all.map(Togyzkumalak) } @@ -97,6 +109,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 5) + p.rank.index // todo where is this used? + def gameLogic: GameLogic = GameLogic.Go() + lazy val all: List[Pos] = go.Pos.all.map(Go) } @@ -109,6 +123,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index + def gameLogic: GameLogic = GameLogic.Backgammon() + lazy val all: List[Pos] = backgammon.Pos.all.map(Backgammon) } @@ -121,6 +137,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 3) + p.rank.index + def gameLogic: GameLogic = GameLogic.Abalone() + lazy val all: List[Pos] = abalone.Pos.all.map(Abalone) } @@ -133,6 +151,8 @@ object Pos { lazy val toInt: Int = (p.file.index << 4) + p.rank.index + def gameLogic: GameLogic = GameLogic.Dameo() + lazy val all: List[Pos] = dameo.Pos.all.map(Dameo) } diff --git a/src/main/scala/Role.scala b/src/main/scala/Role.scala index 848075558..148e08a6f 100644 --- a/src/main/scala/Role.scala +++ b/src/main/scala/Role.scala @@ -2,6 +2,7 @@ package strategygames sealed trait Role { val gameLogic: GameLogic + val gameFamily: GameFamily val forsyth: Char // draughts.Role.pdn will be referred to by pgn from this point val pgn: Char @@ -31,6 +32,7 @@ object Role { final case class ChessRole(r: chess.Role) extends Role { val gameLogic = GameLogic.Chess() + val gameFamily = r.gameFamily val forsyth = r.forsyth val pgn = r.pgn val binaryInt = r.binaryInt @@ -43,6 +45,7 @@ object Role { final case class DraughtsRole(r: draughts.Role) extends Role { lazy val gameLogic = GameLogic.Draughts() + lazy val gameFamily = GameFamily.Draughts() lazy val forsyth = r.forsyth lazy val pgn = r.pdn lazy val binaryInt = r.binaryInt @@ -55,6 +58,7 @@ object Role { final case class FairySFRole(r: fairysf.Role) extends Role { lazy val gameLogic = GameLogic.FairySF() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -67,6 +71,7 @@ object Role { final case class SamuraiRole(r: samurai.Role) extends Role { lazy val gameLogic = GameLogic.Samurai() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -79,6 +84,7 @@ object Role { final case class TogyzkumalakRole(r: togyzkumalak.Role) extends Role { lazy val gameLogic = GameLogic.Togyzkumalak() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -91,6 +97,7 @@ object Role { final case class GoRole(r: go.Role) extends Role { lazy val gameLogic = GameLogic.Go() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -103,6 +110,7 @@ object Role { final case class BackgammonRole(r: backgammon.Role) extends Role { lazy val gameLogic = GameLogic.Backgammon() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -115,6 +123,7 @@ object Role { final case class AbaloneRole(r: abalone.Role) extends Role { lazy val gameLogic = GameLogic.Abalone() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -127,6 +136,7 @@ object Role { final case class DameoRole(r: dameo.Role) extends Role { lazy val gameLogic = GameLogic.Dameo() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pdn lazy val binaryInt = r.binaryInt @@ -139,6 +149,7 @@ object Role { final case class ChessPromotableRole(r: chess.PromotableRole) extends PromotableRole { lazy val gameLogic = GameLogic.Chess() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -160,6 +171,7 @@ object Role { final case class DraughtsPromotableRole(r: draughts.PromotableRole) extends PromotableRole { lazy val gameLogic = GameLogic.Draughts() + lazy val gameFamily = GameFamily.Draughts() lazy val forsyth = r.forsyth lazy val pgn = r.pdn lazy val binaryInt = r.binaryInt @@ -181,6 +193,7 @@ object Role { final case class FairySFPromotableRole(r: fairysf.PromotableRole) extends PromotableRole { lazy val gameLogic = GameLogic.FairySF() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pgn lazy val binaryInt = r.binaryInt @@ -202,6 +215,7 @@ object Role { final case class DameoPromotableRole(r: dameo.PromotableRole) extends PromotableRole { lazy val gameLogic = GameLogic.Dameo() + lazy val gameFamily = r.gameFamily lazy val forsyth = r.forsyth lazy val pgn = r.pdn lazy val binaryInt = r.binaryInt diff --git a/src/main/scala/chess/Role.scala b/src/main/scala/chess/Role.scala index 298d3fa4e..860fcb5aa 100644 --- a/src/main/scala/chess/Role.scala +++ b/src/main/scala/chess/Role.scala @@ -1,6 +1,6 @@ package strategygames.chess -import strategygames.{ P1, P2, Player } +import strategygames.{ GameFamily, P1, P2, Player } sealed trait Role { val forsyth: Char @@ -12,6 +12,7 @@ sealed trait Role { val binaryInt: Int val hashInt: Int val storable: Boolean + val gameFamily: GameFamily val dirs: Directions def dir(from: Pos, to: Pos): Option[Direction] final def -(player: Player) = Piece(player, this) @@ -29,6 +30,7 @@ case object King extends PromotableRole { val projection = false val binaryInt = 1 val hashInt = 5 + val gameFamily: GameFamily = GameFamily.Chess() val storable = false } @@ -39,6 +41,7 @@ case object Queen extends PromotableRole { val projection = true val binaryInt = 2 val hashInt = 4 + val gameFamily: GameFamily = GameFamily.Chess() val storable = true } case object Rook extends PromotableRole { @@ -53,6 +56,7 @@ case object Rook extends PromotableRole { val projection = true val binaryInt = 3 val hashInt = 3 + val gameFamily: GameFamily = GameFamily.Chess() val storable = true } case object Bishop extends PromotableRole { @@ -69,6 +73,7 @@ case object Bishop extends PromotableRole { val projection = true val binaryInt = 5 val hashInt = 2 + val gameFamily: GameFamily = GameFamily.Chess() val storable = true } case object Knight extends PromotableRole { @@ -87,6 +92,7 @@ case object Knight extends PromotableRole { val projection = false val binaryInt = 4 val hashInt = 1 + val gameFamily: GameFamily = GameFamily.Chess() val storable = true } case object Pawn extends Role { @@ -96,6 +102,7 @@ case object Pawn extends Role { val projection = false val binaryInt = 6 val hashInt = 0 + val gameFamily: GameFamily = GameFamily.Chess() val storable = true } case object LOAChecker extends Role { @@ -105,6 +112,7 @@ case object LOAChecker extends Role { val projection = false val binaryInt = 8 val hashInt = 6 + val gameFamily: GameFamily = GameFamily.LinesOfAction() val storable = false } diff --git a/src/main/scala/dameo/Role.scala b/src/main/scala/dameo/Role.scala index 9256795ee..cbd033864 100644 --- a/src/main/scala/dameo/Role.scala +++ b/src/main/scala/dameo/Role.scala @@ -4,17 +4,17 @@ import strategygames.{ GameFamily, P1, P2, Player } sealed trait Role { val forsyth: Char - val forsythUpper: Char = forsyth.toUpper - lazy val pdn: Char = forsyth - lazy val name = toString - lazy val groundName = s"${forsyth}-piece" + lazy val forsythUpper: Char = forsyth.toUpper + lazy val pdn: Char = forsyth + lazy val name = toString + lazy val groundName = s"${forsyth}-piece" val binaryInt: Int - val hashInt: Int = binaryInt + lazy val hashInt: Int = binaryInt val valueOf: Option[Int] - lazy val gameFamily: GameFamily = GameFamily.Dameo() - final def -(player: Player) = Piece(player, this) - final def p1 = this - P1 - final def p2 = this - P2 + val gameFamily: GameFamily = GameFamily.Dameo() + final def -(player: Player) = Piece(player, this) + final def p1 = this - P1 + final def p2 = this - P2 } sealed trait PromotableRole extends Role @@ -23,30 +23,30 @@ sealed trait PromotableRole extends Role case object King extends PromotableRole { val forsyth = 'k' val binaryInt = 1 - val valueOf = Some(2) + val valueOf = Some(2) } case object Man extends Role { val forsyth = 'm' val binaryInt = 2 - val valueOf = Some(1) + val valueOf = Some(1) } case object GhostMan extends Role { val forsyth = 'g' val binaryInt = 4 - val valueOf = Some(0) + val valueOf = Some(0) } case object GhostKing extends Role { val forsyth = 'p' val binaryInt = 3 - val valueOf = Some(0) + val valueOf = Some(0) } object Role { - val all: List[Role] = List(King, Man) + val all: List[Role] = List(King, Man) val allPromotable: List[PromotableRole] = List(King) def defaultRole: Role = Man @@ -54,43 +54,43 @@ object Role { def allByGameFamily(gf: GameFamily): List[Role] = all.filter(r => r.gameFamily == gf) - val allByForsyth: Map[Char, Role] = all map { r => + val allByForsyth: Map[Char, Role] = all map { r => (r.forsyth, r) } toMap - def allByForsyth(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => + def allByForsyth(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => (r.forsyth, r) } toMap - val allByPdn: Map[Char, Role] = all map { r => + val allByPdn: Map[Char, Role] = all map { r => (r.pdn, r) } toMap - def allByPdn(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => + def allByPdn(gf: GameFamily): Map[Char, Role] = allByGameFamily(gf) map { r => (r.pdn, r) } toMap - val allByName: Map[String, Role] = all map { r => + val allByName: Map[String, Role] = all map { r => (r.name, r) } toMap - def allByName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => + def allByName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => (r.name, r) } toMap - val allByGroundName: Map[String, Role] = all map { r => + val allByGroundName: Map[String, Role] = all map { r => (r.groundName, r) } toMap - def allByGroundName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => + def allByGroundName(gf: GameFamily): Map[String, Role] = allByGameFamily(gf) map { r => (r.groundName, r) } toMap - val allByBinaryInt: Map[Int, Role] = all map { r => + val allByBinaryInt: Map[Int, Role] = all map { r => (r.binaryInt, r) } toMap - def allByBinaryInt(gf: GameFamily): Map[Int, Role] = allByGameFamily(gf) map { r => + def allByBinaryInt(gf: GameFamily): Map[Int, Role] = allByGameFamily(gf) map { r => (r.binaryInt, r) } toMap - val allByHashInt: Map[Int, Role] = all map { r => + val allByHashInt: Map[Int, Role] = all map { r => (r.hashInt, r) } toMap - val allPromotableByName: Map[String, PromotableRole] = allPromotable.map(r => (r.toString, r)) toMap - val allPromotableByForsyth: Map[Char, PromotableRole] = allPromotable.map(r => (r.forsyth, r)) toMap - val allPromotableByPdn: Map[Char, PromotableRole] = allPromotable.map(r => (r.pdn, r)) toMap - val allPromotableByGroundName: Map[String, PromotableRole] = + val allPromotableByName: Map[String, PromotableRole] = allPromotable.map(r => (r.toString, r)) toMap + val allPromotableByForsyth: Map[Char, PromotableRole] = allPromotable.map(r => (r.forsyth, r)) toMap + val allPromotableByPdn: Map[Char, PromotableRole] = allPromotable.map(r => (r.pdn, r)) toMap + val allPromotableByGroundName: Map[String, PromotableRole] = allPromotable map { r => (r.groundName, r) } toMap diff --git a/src/main/scala/format/pgn/Reader.scala b/src/main/scala/format/pgn/Reader.scala index fa9158bac..9fb483245 100644 --- a/src/main/scala/format/pgn/Reader.scala +++ b/src/main/scala/format/pgn/Reader.scala @@ -18,62 +18,90 @@ object Reader { sealed trait Result { def valid: Validated[String, Replay] + def evenIncomplete: Replay } object Result { case class ChessComplete(replay: chess.Replay) extends Result { - def valid = Validated.valid(Replay.Chess(replay)) + private def r = Replay.Chess(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class ChessIncomplete(replay: chess.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Chess(replay) } case class DraughtsComplete(replay: draughts.Replay) extends Result { - def valid = Validated.valid(Replay.Draughts(replay)) + private def r = Replay.Draughts(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class DraughtsIncomplete(replay: draughts.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Draughts(replay) } case class FairySFComplete(replay: fairysf.Replay) extends Result { - def valid = Validated.valid(Replay.FairySF(replay)) + private def r = Replay.FairySF(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class FairySFIncomplete(replay: fairysf.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.FairySF(replay) } case class SamuraiComplete(replay: samurai.Replay) extends Result { - def valid = Validated.valid(Replay.Samurai(replay)) + private def r = Replay.Samurai(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class SamuraiIncomplete(replay: samurai.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Samurai(replay) } case class TogyzkumalakComplete(replay: togyzkumalak.Replay) extends Result { - def valid = Validated.valid(Replay.Togyzkumalak(replay)) + private def r = Replay.Togyzkumalak(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class TogyzkumalakIncomplete(replay: togyzkumalak.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Togyzkumalak(replay) } case class GoComplete(replay: go.Replay) extends Result { - def valid = Validated.valid(Replay.Go(replay)) + private def r = Replay.Go(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class GoIncomplete(replay: go.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Go(replay) } case class BackgammonComplete(replay: backgammon.Replay) extends Result { - def valid = Validated.valid(Replay.Backgammon(replay)) + private def r = Replay.Backgammon(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class BackgammonIncomplete(replay: backgammon.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Backgammon(replay) } case class AbaloneComplete(replay: abalone.Replay) extends Result { - def valid = Validated.valid(Replay.Abalone(replay)) + private def r = Replay.Abalone(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class AbaloneIncomplete(replay: abalone.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Abalone(replay) } case class DameoComplete(replay: dameo.Replay) extends Result { - def valid = Validated.valid(Replay.Dameo(replay)) + private def r = Replay.Dameo(replay) + def valid = Validated.valid(r) + def evenIncomplete = r } case class DameoIncomplete(replay: dameo.Replay, failure: String) extends Result { - def valid = Validated.invalid(failure) + def valid = Validated.invalid(failure) + def evenIncomplete = Replay.Dameo(replay) } def wrap(result: ChessReader.Result) = result match { diff --git a/src/main/scala/variant/Variant.scala b/src/main/scala/variant/Variant.scala index fc1dd7d59..e22b548f3 100644 --- a/src/main/scala/variant/Variant.scala +++ b/src/main/scala/variant/Variant.scala @@ -1143,8 +1143,8 @@ object Variant { togyzkumalak.variant.Variant.all.map(Togyzkumalak) ::: go.variant.Variant.all.map(Go) ::: backgammon.variant.Variant.all.map(Backgammon) ::: - abalone.variant.Variant.all.map(Abalone) ::: - dameo.variant.Variant.all.map(Dameo) + abalone.variant.Variant.all.map(Abalone)// ::: + //dameo.variant.Variant.all.map(Dameo) def byId = all map { v => (v.id, v) } toMap