First implementation attempt inspired by Synesso/scala-chess
parent
0dc35f3cbc
commit
0fcb728a3f
|
@ -0,0 +1,23 @@
|
|||
package lila
|
||||
|
||||
object Cli {
|
||||
|
||||
def main(args: Array[String]) {
|
||||
|
||||
args.headOption flatMap makeEnv map { env ⇒
|
||||
args.tail.toList match {
|
||||
case "fixtures" :: Nil ⇒ fixtures(env)
|
||||
case _ => sys error "Invalid command"
|
||||
}
|
||||
} err "Invalid environment"
|
||||
}
|
||||
|
||||
def makeEnv(name: String) = name match {
|
||||
case "test" ⇒ Env.test some
|
||||
case _ ⇒ none
|
||||
}
|
||||
|
||||
def fixtures(env: Env) {
|
||||
println("Load %s fixtures" format env.name)
|
||||
}
|
||||
}
|
|
@ -4,18 +4,35 @@ import repo._
|
|||
import com.mongodb.casbah.MongoConnection
|
||||
import com.mongodb.casbah.commons.conversions.scala._
|
||||
|
||||
class Env(configuration: Map[String, Any]) {
|
||||
final class Env(configuration: Env.Settings, val name: String = "default") {
|
||||
|
||||
def gameRepo = new GameRepo(mongodb("game2"))
|
||||
lazy val gameRepo = new GameRepo(mongodb("game2"))
|
||||
|
||||
private def mongoConnection = MongoConnection(
|
||||
private lazy val mongodb = MongoConnection(
|
||||
get[String]("mongo.host"),
|
||||
get[Int]("mongo.port")
|
||||
)
|
||||
|
||||
private def mongodb = mongoConnection(
|
||||
get[String]("mongo.dbname")
|
||||
)
|
||||
)(get[String]("mongo.dbname"))
|
||||
|
||||
private def get[A](key: String) = configuration(key).asInstanceOf[A]
|
||||
|
||||
def ~(settings: Env.Settings) = new Env(
|
||||
configuration ++ settings,
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
type Settings = Map[String, Any]
|
||||
|
||||
private val defaults = Map(
|
||||
"mongo.host" -> "127.0.0.1",
|
||||
"mongo.port" -> 27017,
|
||||
"mongo.dbname" -> "lichess"
|
||||
)
|
||||
|
||||
def test = new Env(defaults ++ Map(
|
||||
"mongo.dbname" -> "lichess_test"
|
||||
), "test")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package lila
|
||||
package format
|
||||
|
||||
import model.Game
|
||||
|
||||
trait Format {
|
||||
|
||||
def <<(source: String): Game
|
||||
|
||||
def >>(game: Game): String
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package lila
|
||||
package format
|
||||
|
||||
import model._
|
||||
|
||||
object Visual extends Format {
|
||||
|
||||
def <<(source: String): Game = Game("fromsource")
|
||||
|
||||
def >>(game: Game): String = ""
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package lila
|
||||
package model
|
||||
|
||||
//case class Board(pieces: Map[Pos, Piece], taken: List[Piece]) {
|
||||
|
||||
//def this() = this(Map(), Nil)
|
||||
|
||||
/**
|
||||
* Place a piece on the board (at a position)
|
||||
* @return a new board
|
||||
*/
|
||||
//def place(p: Piece) = {
|
||||
//case class Placement() {
|
||||
//def at(s: Symbol): Board = at(position(s))
|
||||
|
||||
//def at(destination: Pos): Board = new Board(pieces(destination) = p, taken)
|
||||
//}
|
||||
//new Placement
|
||||
//}
|
||||
|
||||
/**
|
||||
* Take (capture) the piece at the given position
|
||||
* @return a new board
|
||||
*/
|
||||
//def take(p: Pos) = new Board((pieces - p), (pieces(p) :: taken))
|
||||
|
||||
/**
|
||||
* Move the piece at the given position (to a new position)
|
||||
* @return a new board
|
||||
*/
|
||||
//def move(orig: Pos) = {
|
||||
//case class Movement() {
|
||||
//def to(dest: Pos) = {
|
||||
//if (pieces contains dest) throw new IllegalMoveException("Cannot move to occupied " + dest)
|
||||
//else pieces.get(orig).map(piece => new Board((pieces - orig)(dest) = piece, taken))
|
||||
//.getOrElse {throw new IllegalMoveException("No piece at " + orig + " to move")}
|
||||
//}
|
||||
//}
|
||||
//new Movement
|
||||
//}
|
||||
|
||||
/**
|
||||
* Promote the piece at the given position to a new role
|
||||
* @return a new board
|
||||
*/
|
||||
//def promote(p: Pos) = {
|
||||
//case class Promotion() {
|
||||
//def to(r: Role): Board = r match {
|
||||
//case King => throw new IllegalPromotionException("Cannot promote to King")
|
||||
//case _ => pieces.get(p).map(piece => piece.role match {
|
||||
//case Pawn => new Board(pieces(p) = Piece(piece.colour, r), taken)
|
||||
//case _ => throw new IllegalPromotionException("Can only promote pawns")
|
||||
//}).getOrElse {throw new IllegalPromotionException("No piece at " + p + " to promote")}
|
||||
//}
|
||||
//}
|
||||
//new Promotion
|
||||
//}
|
||||
|
||||
/**
|
||||
* Finds all positions which contain a threat to the given colour (at a given position).
|
||||
* Note, will not identify threats to position from en passant (as this behaviour is not required).
|
||||
* @return the positions of threat
|
||||
*/
|
||||
//def threatsTo(colour: Colour) = {
|
||||
//case class Threat() {
|
||||
//def at(s: Symbol): List[Pos] = at(position(s))
|
||||
|
||||
//def at(pos: Pos): List[Pos] = {
|
||||
//def expand(direction: Seq[Option[Pos] => Option[Pos]]): Option[Pos] = {
|
||||
//def next(from: Option[Pos]): Option[Pos] = {
|
||||
//val nextPose = direction.foldLeft(from) {(acc, next) => next(acc)}
|
||||
//if (nextPose.isEmpty) return None
|
||||
//if (pieces.contains(nextPose.get)) Some(nextPose.get) else next(nextPose)
|
||||
//}
|
||||
//next(Some(pos))
|
||||
//}
|
||||
|
||||
//def opposing(r: Role) = {
|
||||
//case class OpposingRoleCheck() {
|
||||
//def at(p: Pos) = pieces.get(p).map(_.equals(Piece(opposite of colour, r))).getOrElse(false)
|
||||
//}
|
||||
//new OpposingRoleCheck
|
||||
//}
|
||||
|
||||
//val forward = if (White.equals(colour)) ^ _ else v _
|
||||
//val pawns: List[Pos] = Set(forward(pos < 1), forward(pos > 1)).filter(_.isDefined).foldLeft(Nil: List[Pos]) {
|
||||
//(acc, next) =>
|
||||
//if (opposing(Pawn).at(next.get)) next.get :: acc else acc
|
||||
//}
|
||||
//val rankFileVectors: List[Pos] = (expand(Seq(< _)) :: expand(Seq(^ _)) :: expand(Seq(> _)) :: expand(Seq(v _)) :: Nil)
|
||||
//.filter(_.isDefined).foldLeft(Nil: List[Pos]) {
|
||||
//(acc, next) =>
|
||||
//if (opposing(Rook).at(next.get) || opposing(Queen).at(next.get)) next.get :: acc else acc
|
||||
//}
|
||||
//val diagonalVectors: List[Pos] = (expand(Seq(< _, ^ _)) :: expand(Seq(> _, ^ _)) :: expand(Seq(> _, v _)) :: expand(Seq(< _, v _)) :: Nil)
|
||||
//.filter(_.isDefined).foldLeft(Nil: List[Pos]) {
|
||||
//(acc, next) =>
|
||||
//if (opposing(Bishop).at(next.get) || opposing(Queen).at(next.get)) next.get :: acc else acc
|
||||
//}
|
||||
//val knights: List[Pos] = {
|
||||
//val options = radialBasedPoss(pos, List(-2, -1, 1, 2), (rank, file) => Math.abs(rank) != Math.abs(file))
|
||||
//options.toList.filter(opposing(Knight).at(_))
|
||||
//}
|
||||
//val kings: List[Pos] = {
|
||||
//val options = radialBasedPoss(pos, -1 to 1, (rank, file) => (rank != 0 || file != 0))
|
||||
//options.toList.filter(opposing(King).at(_))
|
||||
//}
|
||||
|
||||
//pawns ::: rankFileVectors ::: diagonalVectors ::: knights ::: kings
|
||||
//}
|
||||
//}
|
||||
//new Threat
|
||||
//}
|
||||
|
||||
/**
|
||||
* Layout the board for a new game.
|
||||
* @return a new board
|
||||
*/
|
||||
//def reset = {
|
||||
//val lineUp = Rook :: Knight :: Bishop :: Queen :: King :: Bishop :: Knight :: Rook :: Nil
|
||||
//val pairs = for (rank <- 1 :: 2 :: 7 :: 8 :: Nil; file <- 1 to 8) yield (position(rank, file),
|
||||
//rank match {
|
||||
//case 1 => Piece(White, lineUp(file - 1))
|
||||
//case 2 => Piece(White, Pawn)
|
||||
//case 7 => Piece(Black, Pawn)
|
||||
//case 8 => Piece(Black, lineUp(file - 1))
|
||||
//})
|
||||
//new Board(pairs.foldLeft(Map.empty[Pos, Piece]) {(acc, next) => acc(next._1) = next._2}, Nil)
|
||||
//}
|
||||
//}
|
|
@ -6,5 +6,7 @@ import com.mongodb.casbah.Imports._
|
|||
|
||||
case class Game(
|
||||
|
||||
@Key("_id") id: String
|
||||
@Key("_id") id: String,
|
||||
|
||||
players: List[Player] = Nil
|
||||
)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package lila
|
||||
package model
|
||||
|
||||
import com.novus.salat.annotations._
|
||||
import com.mongodb.casbah.Imports._
|
|
@ -0,0 +1,14 @@
|
|||
package lila
|
||||
package model
|
||||
|
||||
import com.novus.salat.annotations._
|
||||
import com.mongodb.casbah.Imports._
|
||||
|
||||
case class Player(
|
||||
|
||||
id: String,
|
||||
|
||||
color: String,
|
||||
|
||||
ps: String
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
package lila
|
||||
package model
|
||||
|
||||
import scala.math.{ abs, min, max }
|
||||
|
||||
object Pos {
|
||||
|
||||
val values: Set[Int] = (1 to 8) toSet
|
||||
|
||||
val all: Map[String, Pos] = {
|
||||
for {
|
||||
x <- 1 to 8
|
||||
y <- 1 to 8
|
||||
} yield (xToString(x) + y.toString, Pos(x, y))
|
||||
} toMap
|
||||
|
||||
def apply(s: Symbol): Pos = apply(s.name)
|
||||
def apply(k: String): Pos = apply(keyToPair(k))
|
||||
def apply(xy: Pair[Int, Int]): Pos = Pos(xy._1, xy._2)
|
||||
|
||||
def ^(p: Option[Pos]): Option[Pos] = p.flatMap(_ ^ 1)
|
||||
def >(p: Option[Pos]): Option[Pos] = p.flatMap(_ > 1)
|
||||
def v(p: Option[Pos]): Option[Pos] = p.flatMap(_ v 1)
|
||||
def <(p: Option[Pos]): Option[Pos] = p.flatMap(_ < 1)
|
||||
def noop(p: Option[Pos]) = p
|
||||
def vectorBasedPoss(from: Pos, directions: List[List[Option[Pos] ⇒ Option[Pos]]]) = {
|
||||
def expand(direction: Seq[Option[Pos] ⇒ Option[Pos]]): List[Pos] = {
|
||||
def next(acc: List[Pos]): List[Pos] = {
|
||||
val seed: Option[Pos] = Some(acc.headOption.getOrElse(from))
|
||||
val candidate = direction.foldLeft(seed) { (intermediate, step) ⇒ step(intermediate) }
|
||||
candidate match {
|
||||
case Some(p) ⇒ next(p :: acc)
|
||||
case None ⇒ acc
|
||||
}
|
||||
}
|
||||
next(Nil).reverse
|
||||
}
|
||||
directions.foldLeft(Nil: List[List[Pos]]) { (acc, next) ⇒ expand(next) :: acc }.filter(l ⇒ !l.isEmpty)
|
||||
}
|
||||
def radialBasedPoss(from: Pos, offsets: Iterable[Int], filter: (Int, Int) ⇒ Boolean) = {
|
||||
(for (y ← offsets; x ← offsets; if (filter(y, x))) yield (from ^ y).flatMap(_ < x)).filter(_.isDefined).map(_.get)
|
||||
}
|
||||
|
||||
private def keyToPair(k: String): Pair[Int, Int] =
|
||||
(k.charAt(0).asDigit - 9, k.charAt(1).asDigit)
|
||||
|
||||
def xToString(x: Int) = (96 + x).toChar.toString
|
||||
}
|
||||
|
||||
case class Pos private (x: Int, y: Int) {
|
||||
|
||||
if (!Pos.values(x) || !Pos.values(y))
|
||||
throw new RuntimeException("Invalid position " + (x, y))
|
||||
|
||||
def ^(n: Int): Option[Pos] = if (Pos.values.contains(y + n)) Some(Pos(y + n, x)) else None
|
||||
def >(n: Int): Option[Pos] = if (Pos.values.contains(x + n)) Some(Pos(y, x + n)) else None
|
||||
def v(n: Int): Option[Pos] = this ^ (n * -1)
|
||||
def <(n: Int): Option[Pos] = this > (n * -1)
|
||||
def -?(other: Pos) = y == other.y
|
||||
def |?(other: Pos) = x == other.x
|
||||
def /?(other: Pos) = abs(x - other.x) == abs(y - other.y)
|
||||
def *?(other: Pos) = this.-?(other) || this.|?(other) || this./?(other)
|
||||
def ^^(n: Int): List[Pos] = expand(n, List(Some(this)), Pos.^).filter(_.isDefined).map(_.get).reverse
|
||||
def >>(n: Int): List[Pos] = expand(n, List(Some(this)), Pos.>).filter(_.isDefined).map(_.get).reverse
|
||||
def vv(n: Int): List[Pos] = expand(n, List(Some(this)), Pos.v).filter(_.isDefined).map(_.get).reverse
|
||||
def <<(n: Int): List[Pos] = expand(n, List(Some(this)), Pos.<).filter(_.isDefined).map(_.get).reverse
|
||||
def xToString = Pos xToString x
|
||||
def yToString = y.toString
|
||||
override def toString = xToString + yToString
|
||||
private def expand(i: Int, accumulator: List[Option[Pos]], direct: Option[Pos] ⇒ Option[Pos]): List[Option[Pos]] = {
|
||||
if (i > 0 && accumulator.head.isDefined) expand(i - 1, direct(accumulator.head) :: accumulator, direct) else accumulator
|
||||
}
|
||||
}
|
|
@ -9,4 +9,6 @@ import com.mongodb.casbah.MongoCollection
|
|||
import com.mongodb.casbah.Imports._
|
||||
|
||||
class GameRepo(collection: MongoCollection) extends SalatDAO[Game, String](collection) {
|
||||
|
||||
def game(id: String) = findOneByID(id)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package lila
|
||||
|
||||
import org.specs2.mutable._
|
||||
import org.specs2.specification._
|
||||
|
||||
import format.Visual
|
||||
import model.Game
|
||||
|
||||
class FormatVisualTest extends Specification {
|
||||
|
||||
val f = Visual
|
||||
|
||||
"The visual formatter" should {
|
||||
"import and export is non destructive" in {
|
||||
forall(examples) { example =>
|
||||
(f >> (f << example)) mustEqual example
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val examples = Seq("""
|
||||
rnbqkp r
|
||||
pppppppp
|
||||
|
||||
|
||||
|
||||
P n
|
||||
PPPP P
|
||||
RNBQK R""", """
|
||||
k
|
||||
P
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
K
|
||||
""", """
|
||||
rnbqkbnr
|
||||
pppppppp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RK NR
|
||||
""", """
|
||||
bqkb r
|
||||
p ppp pp
|
||||
pr
|
||||
P p
|
||||
QnB
|
||||
PP N
|
||||
P PPP
|
||||
RN K R
|
||||
""", """
|
||||
r k nr
|
||||
pp n ppp
|
||||
p p
|
||||
q
|
||||
b P B
|
||||
P N Q P
|
||||
PP BPP
|
||||
R K R
|
||||
"""
|
||||
)
|
||||
}
|
|
@ -34,7 +34,8 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
|
|||
resolvers := Seq(
|
||||
codahale, typesafe, iliaz, sonatype
|
||||
),
|
||||
shellPrompt := ShellPrompt.buildShellPrompt
|
||||
shellPrompt := ShellPrompt.buildShellPrompt,
|
||||
scalacOptions := Seq("-deprecation", "-unchecked")
|
||||
)
|
||||
|
||||
val main = PlayProject("app", appVersion, Seq(), mainLang = SCALA) dependsOn lila
|
||||
|
|
Loading…
Reference in New Issue