Skip to content

Commit

Permalink
Merge pull request #222 from Mind-Sports-Games/pla-1142-fix-rule-of-g…
Browse files Browse the repository at this point in the history
…in-for-hyper-backgammon

Pla 1142 fix rule of gin for hyper backgammon
  • Loading branch information
msomatt authored Feb 19, 2025
2 parents b88914d + f747a0b commit bd83a95
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/main/scala/backgammon/Board.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ case class Board(
onlySafeOnePointPieces(Player.P1) ||
onlySafeOnePointPieces(Player.P2)

def hasDoubledCube: Boolean = !(cubeData.map(_.value).getOrElse(1) == 1)

lazy val actors: Map[Pos, (Actor, Int)] = pieces map { case (pos, (piece, count)) =>
(pos, (Actor(piece, pos, this), count))
}
Expand Down
3 changes: 1 addition & 2 deletions src/main/scala/backgammon/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ case class Game(

def applyLift(lift: Lift): Game = {
val newSituation = lift.situationAfter
// if the lift triggers the end of the game we want to end the turn
val switchPlayer = situation.player != newSituation.player || situation.end
val switchPlayer = situation.player != newSituation.player

copy(
situation = newSituation,
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/backgammon/Lift.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ case class Lift(

def player = situationBefore.player

def situationAfter =
Situation(finalizeAfter, player)
def situationAfter = {
val s = Situation(finalizeAfter, player)
// s.end needs to be called on a situation but who is set as player is irrelevant as
// s.end doesn't check player. but this enables us to know who the player in the
// situationAfter should be
if (s.end) s.copy(player = !player)
else s
}

def finalizeAfter: Board = after updateHistory { h =>
h.copy(
Expand Down
20 changes: 3 additions & 17 deletions src/main/scala/backgammon/Situation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,20 +182,9 @@ case class Situation(board: Board, player: Player) {
else None

def resignStatus(player: Player): Status.type => Status =
if (board.racePosition)
if (board.variant.backgammonPosition(this, player)) _.ResignBackgammon
else if (board.variant.gammonPosition(this, player)) _.ResignGammon
else _.Resign
else if (board.history.score(player) == 0 && board.variant.key != "hyper") _.ResignBackgammon
else _.Resign
board.variant.resignStatus(this, player)

def outOfTimeStatus: Status.type => Status =
if (board.racePosition)
if (board.variant.backgammonPosition(this, player)) _.OutoftimeBackgammon
else if (board.variant.gammonPosition(this, player)) _.OutoftimeGammon
else _.Outoftime
else if (board.history.score(player) == 0 && board.variant.key != "hyper") _.OutoftimeBackgammon
else _.Outoftime
def outOfTimeStatus: Status.type => Status = board.variant.outOfTimeStatus(this)

// only works when we are not mid turn and have not rolled dice
def maxTurnsFromEnd(player: Player): Option[Int] =
Expand Down Expand Up @@ -323,10 +312,7 @@ case class Situation(board: Board, player: Player) {
.contains(false)

def insufficientMaterialStatus: Status.type => Status =
if (board.variant.key == "hyper") _.RuleOfGin // TODO fix correctly when using doubling cube
else if (opponentHasInsufficientMaterialForBackgammon) _.GinBackgammon
else if (opponentHasInsufficientMaterialForGammon) _.GinGammon
else _.RuleOfGin
board.variant.insufficientMaterialStatus(this)

def pointValue(player: Option[Player]): Option[Int] = board.variant.pointValue(this, player)

Expand Down
43 changes: 33 additions & 10 deletions src/main/scala/backgammon/variant/Hyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package strategygames.backgammon
package variant

import strategygames.backgammon._
import strategygames.{ GameFamily, Player }
import strategygames.{ GameFamily, Player, Status }

case object Hyper
extends Variant(
Expand All @@ -20,22 +20,45 @@ case object Hyper

override def numStartingPiecesPerPlayer: Int = 3

// gammon and backgammon only count when the cube has been doubled in this variant
// gammon and backgammon only count when the cube has been doubled in Hyper (Jacoby Rule)
override def gammonWin(situation: Situation) =
if (situation.board.cubeData.map(_.value).getOrElse(1) == 1) false
else super.gammonWin(situation)
if (situation.board.hasDoubledCube) super.gammonWin(situation)
else false

override def gammonPosition(situation: Situation, player: Player) =
if (situation.board.cubeData.map(_.value).getOrElse(1) == 1) false
else super.gammonPosition(situation, player)
if (situation.board.hasDoubledCube) super.gammonPosition(situation, player)
else false

override def backgammonWin(situation: Situation) =
if (situation.board.cubeData.map(_.value).getOrElse(1) == 1) false
else super.backgammonWin(situation)
if (situation.board.hasDoubledCube) super.backgammonWin(situation)
else false

override def backgammonPosition(situation: Situation, player: Player) =
if (situation.board.cubeData.map(_.value).getOrElse(1) == 1) false
else super.backgammonPosition(situation, player)
if (situation.board.hasDoubledCube) super.backgammonPosition(situation, player)
else false

override def resignStatus(situation: Situation, player: Player): Status.type => Status =
if (situation.board.racePosition)
if (backgammonPosition(situation, player)) _.ResignBackgammon
else if (gammonPosition(situation, player)) _.ResignGammon
else _.Resign
else if (situation.board.history.score(player) == 0 && situation.board.hasDoubledCube) _.ResignBackgammon
else _.Resign

override def outOfTimeStatus(situation: Situation): Status.type => Status =
if (situation.board.racePosition)
if (backgammonPosition(situation, situation.player)) _.OutoftimeBackgammon
else if (gammonPosition(situation, situation.player)) _.OutoftimeGammon
else _.Outoftime
else if (situation.board.history.score(situation.player) == 0 && situation.board.hasDoubledCube)
_.OutoftimeBackgammon
else _.Outoftime

override def insufficientMaterialStatus(situation: Situation): Status.type => Status =
if (!situation.board.hasDoubledCube) _.RuleOfGin
else if (situation.opponentHasInsufficientMaterialForBackgammon) _.GinBackgammon
else if (situation.opponentHasInsufficientMaterialForGammon) _.GinGammon
else _.RuleOfGin

override def baseVariant: Boolean = false

Expand Down
21 changes: 21 additions & 0 deletions src/main/scala/backgammon/variant/Variant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,27 @@ abstract class Variant private[variant] (
)
)

def resignStatus(situation: Situation, player: Player): Status.type => Status =
if (situation.board.racePosition)
if (backgammonPosition(situation, player)) _.ResignBackgammon
else if (gammonPosition(situation, player)) _.ResignGammon
else _.Resign
else if (situation.board.history.score(player) == 0) _.ResignBackgammon
else _.Resign

def outOfTimeStatus(situation: Situation): Status.type => Status =
if (situation.board.racePosition)
if (backgammonPosition(situation, situation.player)) _.OutoftimeBackgammon
else if (gammonPosition(situation, situation.player)) _.OutoftimeGammon
else _.Outoftime
else if (situation.board.history.score(situation.player) == 0) _.OutoftimeBackgammon
else _.Outoftime

def insufficientMaterialStatus(situation: Situation): Status.type => Status =
if (situation.opponentHasInsufficientMaterialForBackgammon) _.GinBackgammon
else if (situation.opponentHasInsufficientMaterialForGammon) _.GinGammon
else _.RuleOfGin

def winner(situation: Situation): Option[Player] =
if (cubeRejected(situation)) situation.board.cubeData.fold(None: Option[Player])(_.owner)
else if (specialEnd(situation)) {
Expand Down

0 comments on commit bd83a95

Please sign in to comment.