From 90946a29b721742d52d9ebec462379b4680be5a7 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Sun, 26 Feb 2012 20:23:14 +0100 Subject: [PATCH] Don't castle if the enemy threatens part of the king trip --- src/main/scala/model/Actor.scala | 48 ++++++++++++++++----------- src/main/scala/model/Board.scala | 11 ++++-- src/main/scala/model/Pos.scala | 3 ++ src/test/scala/model/CastleTest.scala | 11 ++++++ 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/main/scala/model/Actor.scala b/src/main/scala/model/Actor.scala index aeac593d55..16fb5eccaa 100644 --- a/src/main/scala/model/Actor.scala +++ b/src/main/scala/model/Actor.scala @@ -3,6 +3,7 @@ package model import Pos.makePos import scalaz.Success +import scala.math.{ min, max } case class Actor(piece: Piece, pos: Pos, board: Board) { @@ -62,13 +63,15 @@ case class Actor(piece: Piece, pos: Pos, board: Board) { def is(c: Color) = c == piece.color def is(p: Piece) = p == piece - def threatens(to: Pos): Boolean = enemies(to) && ((piece.role match { + def threatens(to: Pos): Boolean = enemies(to) && threats(to) + + lazy val threats: Set[Pos] = piece.role match { case Pawn ⇒ pawnDir(pos) map { next ⇒ - List(next.left, next.right) flatten - } getOrElse Nil - case role if role.longRange ⇒ longRangePoss(role.dirs) - case role ⇒ (role.dirs map { d ⇒ d(pos) }).flatten - }) contains to) + Set(next.left, next.right) flatten + } getOrElse Set.empty + case role if role.longRange ⇒ longRangePoss(role.dirs) toSet + case role ⇒ (role.dirs map { d ⇒ d(pos) }).flatten toSet + } private def kingSafety(implications: Implications): Implications = implications filterNot { @@ -78,20 +81,27 @@ case class Actor(piece: Piece, pos: Pos, board: Board) { } private def castle: Implications = { - def on(side: Side): Option[Implication] = for { - kingPos ← board kingPosOf color - if history canCastle color on side - tripToRook = side.tripToRook(kingPos, board) - rookPos ← tripToRook.lastOption - if board(rookPos) == Some(color.rook) - newKingPos ← makePos(side.castledKingX, kingPos.y) - newRookPos ← makePos(side.castledRookX, rookPos.y) - b1 ← board take rookPos - b2 ← b1.move(kingPos, newKingPos) - b3 ← b2.place(color.rook, newRookPos) - } yield (newKingPos, b3 updateHistory (_ withoutCastles color)) - List(on(KingSide), on(QueenSide)).flatten toMap + if (history.canCastle(color).any) { + val enemyThreats = board threatsOf !color + def on(side: Side): Option[Implication] = for { + kingPos ← board kingPosOf color + if history canCastle color on side + tripToRook = side.tripToRook(kingPos, board) + rookPos ← tripToRook.lastOption + if board(rookPos) == Some(color.rook) + newKingPos ← makePos(side.castledKingX, kingPos.y) + securedPoss = kingPos <-> newKingPos + if (enemyThreats & securedPoss.toSet).isEmpty + newRookPos ← makePos(side.castledRookX, rookPos.y) + b1 ← board take rookPos + b2 ← b1.move(kingPos, newKingPos) + b3 ← b2.place(color.rook, newRookPos) + } yield (newKingPos, b3 updateHistory (_ withoutCastles color)) + + List(on(KingSide), on(QueenSide)).flatten toMap + } + else Map.empty } private def preventsCastle(implications: Implications) = diff --git a/src/main/scala/model/Board.scala b/src/main/scala/model/Board.scala index 39eb96d7cf..c6e1534a3f 100644 --- a/src/main/scala/model/Board.scala +++ b/src/main/scala/model/Board.scala @@ -19,9 +19,10 @@ case class Board(pieces: Map[Pos, Piece], history: History) { case (pos, piece) ⇒ (pos, Actor(piece, pos, this)) } - lazy val colorActors: Map[Color, Iterable[Actor]] = actors.values groupBy (_.color) + lazy val colorActors: Map[Color, List[Actor]] = + actors.values groupBy (_.color) mapValues (_.toList) - def actorsOf(c: Color) = colorActors get c getOrElse Nil + def actorsOf(c: Color): List[Actor] = colorActors get c getOrElse Nil def actorAt(at: Pos): Valid[Actor] = actors get at toSuccess ("No piece on " + at) @@ -33,6 +34,10 @@ case class Board(pieces: Map[Pos, Piece], history: History) { def movesFrom(from: Pos): Valid[Set[Pos]] = actorAt(from) map (_.moves) + def threatsOf(c: Color): Set[Pos] = actorsOf(c).toSet flatMap { actor: Actor => + actor.threats + } + def seq(actions: Board ⇒ Valid[Board]*): Valid[Board] = actions.foldLeft(success(this): Valid[Board])(_ flatMap _) @@ -96,7 +101,7 @@ case class Board(pieces: Map[Pos, Piece], history: History) { def withHistory(h: History): Board = copy(history = h) - def updateHistory(f: History => History) = copy(history = f(history)) + def updateHistory(f: History ⇒ History) = copy(history = f(history)) def as(c: Color) = Situation(this, c) diff --git a/src/main/scala/model/Pos.scala b/src/main/scala/model/Pos.scala index d2812ac692..b1554abb16 100644 --- a/src/main/scala/model/Pos.scala +++ b/src/main/scala/model/Pos.scala @@ -29,6 +29,9 @@ sealed case class Pos private (x: Int, y: Int) extends Ordered[Pos] { def ?<(other: Pos) = x < other.x def ?>(other: Pos) = x > other.x + def <->(other: Pos): Iterable[Pos] = + min(x, other.x) to max(x, other.x) map { makePos(_, y) } flatten + def xToString = Pos xToString x def yToString = y.toString diff --git a/src/test/scala/model/CastleTest.scala b/src/test/scala/model/CastleTest.scala index 283c987479..d37be3b6f4 100644 --- a/src/test/scala/model/CastleTest.scala +++ b/src/test/scala/model/CastleTest.scala @@ -195,6 +195,17 @@ PPPPPPPP board place Black.rook at C3 flatMap (_ movesFrom E1) must bePoss(D1, D2, E2, F2) } } + "chess 960" in { + "far kingside" in { + val board: Board = """BK R""" + "rook threat" in { + board place Black.rook at F3 flatMap (_ movesFrom B1) must bePoss(A2, B2, C2, C1) + } + "enemy king threat" in { + board place Black.king at E2 flatMap (_ movesFrom B1) must bePoss(A2, B2, C2, C1) + } + } + } } "threat on rook does not prevent castling" in { "king side" in {