diff --git a/app/Env.scala b/app/Env.scala index 43eb707b2b..fadc296da9 100644 --- a/app/Env.scala +++ b/app/Env.scala @@ -136,5 +136,5 @@ object Env { def video = lila.video.Env.current def playban = lila.playban.Env.current def shutup = lila.shutup.Env.current - def coach = lila.coach.Env.current + def insight = lila.insight.Env.current } diff --git a/app/controllers/Coach.scala b/app/controllers/Insight.scala similarity index 79% rename from app/controllers/Coach.scala rename to app/controllers/Insight.scala index 3b626dae70..dd2c553074 100644 --- a/app/controllers/Coach.scala +++ b/app/controllers/Insight.scala @@ -6,9 +6,9 @@ import play.api.libs.json.Json import play.api.mvc._ import views._ -object Coach extends LilaController { +object Insight extends LilaController { - private def env = Env.coach + private def env = Env.insight def refresh(username: String) = Open { implicit ctx => Accessible(username) { user => @@ -18,23 +18,23 @@ object Coach extends LilaController { def index(username: String) = Open { implicit ctx => Accessible(username) { user => - import lila.coach.CoachApi.UserStatus._ + import lila.insight.InsightApi.UserStatus._ env.api.userStatus(user).map { - case NoGame => Ok(html.coach.noGame(user)) - case Empty => Ok(html.coach.empty(user)) - case s => Ok(html.coach.index(user, env.jsonView.stringifiedUi, s == Stale)) + case NoGame => Ok(html.insight.noGame(user)) + case Empty => Ok(html.insight.empty(user)) + case s => Ok(html.insight.index(user, env.jsonView.stringifiedUi, s == Stale)) } } } def json(username: String) = OpenBody(BodyParsers.parse.json) { implicit ctx => - import lila.coach.JsonQuestion, JsonQuestion._ + import lila.insight.JsonQuestion, JsonQuestion._ Accessible(username) { user => ctx.body.body.validate[JsonQuestion].fold( err => BadRequest(Json.obj("error" -> err.toString)).fuccess, qJson => qJson.question.fold(BadRequest.fuccess) { q => env.api.ask(q, user) map - lila.coach.Chart.fromAnswer map + lila.insight.Chart.fromAnswer map env.jsonView.chart.apply map { Ok(_) } } ) @@ -47,7 +47,7 @@ object Coach extends LilaController { case Some(u) => env.share.grant(u, ctx.me) flatMap { case true => f(u) case false if isGranted(_.UserSpy) => f(u) - case false => fuccess(Forbidden(html.coach.forbidden(u))) + case false => fuccess(Forbidden(html.insight.forbidden(u))) } } diff --git a/app/views/account/pref.scala.html b/app/views/account/pref.scala.html index 95f4fc31f6..f4e5748e03 100644 --- a/app/views/account/pref.scala.html +++ b/app/views/account/pref.scala.html @@ -101,8 +101,8 @@ @base.radios(form("message"), translatedMessageChoices)
  • -

    Share your coach data

    - @base.radios(form("coachShare"), lila.pref.Pref.CoachShare.choices) +

    Share your insights data

    + @base.radios(form("insightShare"), lila.pref.Pref.InsightShare.choices)
  • } diff --git a/app/views/coach/answer.scala.html b/app/views/coach/answer.scala.html deleted file mode 100644 index 762169b298..0000000000 --- a/app/views/coach/answer.scala.html +++ /dev/null @@ -1,6 +0,0 @@ -@(answer: lila.coach.Answer[_], u: User)(implicit ctx: Context) - -@coach.layout(u, title = s"${u.username} coach") { - -@answer -} diff --git a/app/views/coach/empty.scala.html b/app/views/coach/empty.scala.html deleted file mode 100644 index c6d1a2c919..0000000000 --- a/app/views/coach/empty.scala.html +++ /dev/null @@ -1,16 +0,0 @@ -@(u: User)(implicit ctx: Context) - -@coach.layout(u, -title = s"${u.username} coach", -moreJs = jsTag("coach-refresh.js")) { - -
    -
    -

    @u.username's coach

    -
    -


    -

    @userLink(u) has not used the coach yet!

    -

    - @refreshForm(u, s"Start ${u.username} coach") -
    -} diff --git a/app/views/coach/forbidden.scala.html b/app/views/coach/forbidden.scala.html deleted file mode 100644 index 2dfd07397d..0000000000 --- a/app/views/coach/forbidden.scala.html +++ /dev/null @@ -1,6 +0,0 @@ -@(u: User)(implicit ctx: Context) - -@coach.layout(u, title = s"${u.username} coach data is protected") { - -You cannot see @userLink(u) coach data! -} diff --git a/app/views/coach/noGame.scala.html b/app/views/coach/noGame.scala.html deleted file mode 100644 index befdc91a72..0000000000 --- a/app/views/coach/noGame.scala.html +++ /dev/null @@ -1,6 +0,0 @@ -@(u: User)(implicit ctx: Context) - -@coach.layout(u, title = s"${u.username} has not played a rated game yet!") { - -Before using the coach, @userLink(u) has to play at least one rated game. -} diff --git a/app/views/insight/empty.scala.html b/app/views/insight/empty.scala.html new file mode 100644 index 0000000000..9c96eb5e0c --- /dev/null +++ b/app/views/insight/empty.scala.html @@ -0,0 +1,16 @@ +@(u: User)(implicit ctx: Context) + +@insight.layout(u, +title = s"${u.username} chess insights", +moreJs = jsTag("insight-refresh.js")) { + +
    +
    +

    @u.username chess insights

    +
    +


    +

    @userLink(u) has no chess insights yet!

    +

    + @refreshForm(u, s"Generate ${u.username} chess insights") +
    +} diff --git a/app/views/insight/forbidden.scala.html b/app/views/insight/forbidden.scala.html new file mode 100644 index 0000000000..25e52a750c --- /dev/null +++ b/app/views/insight/forbidden.scala.html @@ -0,0 +1,6 @@ +@(u: User)(implicit ctx: Context) + +@insight.layout(u, title = s"${u.username} chess insights are protected") { + +You cannot see @userLink(u) chess insights! +} diff --git a/app/views/coach/index.scala.html b/app/views/insight/index.scala.html similarity index 51% rename from app/views/coach/index.scala.html rename to app/views/insight/index.scala.html index c70e9f1874..9b0b7a247f 100644 --- a/app/views/coach/index.scala.html +++ b/app/views/insight/index.scala.html @@ -3,13 +3,13 @@ @moreJs = { @highchartsLatestTag @jsAt("vendor/multiple-select/multiple-select.js") -@jsAt(s"compiled/lichess.coach${isProd??(".min")}.js") +@jsAt(s"compiled/lichess.insight${isProd??(".min")}.js") @if(stale) { -@jsTag("coach-refresh.js") +@jsTag("insight-refresh.js") } @embedJs { $(function() { -LichessCoach(document.getElementById('coach'), { +LichessInsight(document.getElementById('insight'), { ui: @Html(ui), i18n: @jsI18n(), userId: "@u.id" @@ -19,19 +19,19 @@ userId: "@u.id" } @moreCss = { -@cssTag("coach.css") +@cssTag("insight.css") @cssVendorTag("multiple-select/multiple-select.css") } -@coach.layout(u, -title = s"${u.username} coach", +@insight.layout(u, +title = s"${u.username} chess insights", moreJs = moreJs, moreCss = moreCss) { -
    +
    @if(stale) { -
    -

    There are new games to analyse!

    - @refreshForm(u, "Update the coach") +
    +

    There are new games to learn from!

    + @refreshForm(u, "Update insights")
    } } diff --git a/app/views/coach/jsI18n.scala.html b/app/views/insight/jsI18n.scala.html similarity index 100% rename from app/views/coach/jsI18n.scala.html rename to app/views/insight/jsI18n.scala.html diff --git a/app/views/coach/layout.scala.html b/app/views/insight/layout.scala.html similarity index 100% rename from app/views/coach/layout.scala.html rename to app/views/insight/layout.scala.html diff --git a/app/views/insight/noGame.scala.html b/app/views/insight/noGame.scala.html new file mode 100644 index 0000000000..ff21384118 --- /dev/null +++ b/app/views/insight/noGame.scala.html @@ -0,0 +1,6 @@ +@(u: User)(implicit ctx: Context) + +@insight.layout(u, title = s"${u.username} has not played a rated game yet!") { + +Before using chess insights, @userLink(u) has to play at least one rated game. +} diff --git a/app/views/coach/refreshForm.scala.html b/app/views/insight/refreshForm.scala.html similarity index 71% rename from app/views/coach/refreshForm.scala.html rename to app/views/insight/refreshForm.scala.html index d403431d60..db74647bd8 100644 --- a/app/views/coach/refreshForm.scala.html +++ b/app/views/insight/refreshForm.scala.html @@ -1,6 +1,6 @@ @(u: User, action: String)(implicit ctx: Context) -
    +
    Now crunshing data just for you! diff --git a/conf/base.conf b/conf/base.conf index 4a0673f0e4..ac316987e6 100644 --- a/conf/base.conf +++ b/conf/base.conf @@ -547,13 +547,13 @@ importer { delay = 50 milliseconds } mobile.app.version = "1.3.0" -coach { +insight { mongodb { uri = "mongodb://127.0.0.1:27037/lichess" mongo-async-driver = {} } collection { - entry = coach_entry + entry = insight } } simulation { diff --git a/conf/routes b/conf/routes index a42f3e3fae..ad5de2db83 100644 --- a/conf/routes +++ b/conf/routes @@ -42,10 +42,10 @@ GET /@/:username/followers controllers.Relation.followers(username: GET /@/:username/suggestions controllers.Relation.suggest(username: String) GET /rel/blocks controllers.Relation.blocks -# Coach -POST /coach/refresh/:username controllers.Coach.refresh(username: String) -GET /coach/:username controllers.Coach.index(username: String) -POST /coach/data/:username controllers.Coach.json(username: String) +# Insight +POST /insights/refresh/:username controllers.Insight.refresh(username: String) +POST /insights/data/:username controllers.Insight.json(username: String) +GET /@/:username/insights controllers.Insight.index(username: String) # User GET /@/:username/opponents controllers.User.opponents(username: String) diff --git a/modules/coach/src/main/Share.scala b/modules/coach/src/main/Share.scala deleted file mode 100644 index 0e8e272966..0000000000 --- a/modules/coach/src/main/Share.scala +++ /dev/null @@ -1,18 +0,0 @@ -package lila.coach - -import lila.pref.Pref -import lila.user.User - -final class Share( - getPref: String => Fu[Pref], - areFriends: (String, String) => Fu[Boolean]) { - - def grant(coached: User, to: Option[User]): Fu[Boolean] = getPref(coached.id) flatMap { pref => - pref.coachShare match { - case _ if to.contains(coached) => fuccess(true) - case Pref.CoachShare.EVERYBODY => fuccess(true) - case Pref.CoachShare.FRIENDS => to ?? { t => areFriends(coached.id, t.id) } - case Pref.CoachShare.NOBODY => fuccess(false) - } - } -} diff --git a/modules/coach/src/main/package.scala b/modules/coach/src/main/package.scala deleted file mode 100644 index d048d95a6d..0000000000 --- a/modules/coach/src/main/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package lila - -package object coach extends PackageObject with WithPlay diff --git a/modules/coach_old/src/main/Aggregator.scala b/modules/coach_old/src/main/Aggregator.scala deleted file mode 100644 index 7939fdcfc8..0000000000 --- a/modules/coach_old/src/main/Aggregator.scala +++ /dev/null @@ -1,95 +0,0 @@ -package lila.coach - -import akka.actor._ -import akka.pattern.ask -import org.joda.time.DateTime -import play.api.libs.iteratee._ -import play.api.libs.json.Json -import reactivemongo.bson._ - -import lila.db.api._ -import lila.db.BSON._ -import lila.db.Implicits._ -import lila.game.BSONHandlers.gameBSONHandler -import lila.game.tube.gameTube -import lila.game.{ Game, Query } -import lila.hub.Sequencer -import lila.user.User - -final class Aggregator(api: StatApi, sequencer: ActorRef) { - - private implicit val timeout = makeTimeout.minutes(5) - - def apply(user: User): Funit = { - val p = scala.concurrent.Promise[Unit]() - sequencer ! Sequencer.work(compute(user), p.some) - p.future - } - - private def compute(user: User): Funit = api.fetchLast(user) flatMap { - case None => fromScratch(user) - case Some(p) => api.remove(p) >> computeFrom(user, p.from) - } - - private def fromScratch(user: User): Funit = - fetchFirstGame(user) flatMap { - _.?? { g => computeFrom(user, g.createdAt) } - } - - private def gameQuery(user: User) = Query.user(user.id) ++ Query.rated ++ Query.finished - private val maxGames = 5 * 1000 - - private def fetchFirstGame(user: User): Fu[Option[Game]] = - if (user.count.rated == 0) fuccess(none) - else { - (user.count.rated >= maxGames) ?? - pimpQB($query(gameQuery(user))).sort(Query.sortCreated).skip(maxGames - 1).one[Game] - } orElse - pimpQB($query(gameQuery(user))).sort(Query.sortChronological).one[Game] - - private def computeFrom(user: User, from: DateTime): Funit = - lila.common.Chronometer.log(s"aggregator:${user.username}") { - { - pimpQB($query(gameQuery(user) ++ Json.obj(Game.BSONFields.createdAt -> $gte($date(from))))) - .sort(Query.sortChronological) - .cursor[Game]() - .enumerate(maxGames, stopOnError = true) &> - richPovEnumeratee(user) |>>> - Iteratee.foldM[Option[RichPov], Periods.Computation](Periods.initComputation(user.id, api.insert)) { - case (comp, Some(p)) => try { - comp aggregate p - } - catch { - case e: Exception => - e.printStackTrace - logwarn(s"[StatApi] game ${p.pov.game.id} $e"); fuccess(comp) - } - // case (comp, Some(p)) => comp aggregate p - case (comp, _) => logwarn("[StatApi] invalid pov"); fuccess(comp) - } - }.map(_.run) - } - - private def richPovEnumeratee(user: User) = - Enumeratee.mapM[lila.game.Game].apply[Option[RichPov]] { game => - lila.game.Pov.ofUserId(game, user.id) ?? { pov => - lila.game.GameRepo.initialFen(game) zip - (game.metadata.analysed ?? lila.analyse.AnalysisRepo.doneById(game.id)) map { - case (fen, an) => - val division = chess.Replay.boards( - moveStrs = game.pgnMoves, - initialFen = fen, - variant = game.variant - ).toOption.fold(chess.Division.empty)(chess.Divider.apply) - RichPov( - pov = pov, - initialFen = fen, - analysis = an, - division = division, - accuracy = an.flatMap { lila.analyse.Accuracy(pov, _, division) }, - moveAccuracy = an.map { lila.analyse.Accuracy.diffsList(pov, _) } - ).some - } - } - } -} diff --git a/modules/coach_old/src/main/BSONHandlers.scala b/modules/coach_old/src/main/BSONHandlers.scala deleted file mode 100644 index a34e9b3704..0000000000 --- a/modules/coach_old/src/main/BSONHandlers.scala +++ /dev/null @@ -1,91 +0,0 @@ -package lila.coach - -import reactivemongo.bson._ -import reactivemongo.bson.Macros - -import lila.db.BSON._ -import lila.db.Implicits._ -import lila.rating.PerfType - -private[coach] object BSONHandlers { - - import Results.{ BestWin } - import PerfResults.{ BestRating, StatusScores, OutcomeStatuses, PerfResultsMap } - import Openings.OpeningsMap - import GameSections.Section - - private implicit val intMapHandler = MapValue.MapHandler[Int] - - private implicit val NbSumBSONHandler = new BSONHandler[BSONArray, NbSum] { - def read(arr: BSONArray) = NbSum( - nb = arr.getAs[Int](0) err "NbSum missing nb", - sum = arr.getAs[Int](1) err "NbSum missing sum") - def write(x: NbSum) = BSONArray(x.nb, x.sum) - } - private implicit val StatusScoresBSONHandler = new BSONHandler[BSONDocument, StatusScores] { - def read(doc: BSONDocument): StatusScores = StatusScores { - intMapHandler read doc mapKeys { k => - parseIntOption(k) flatMap chess.Status.apply - } collect { case (Some(k), v) => k -> v } - } - def write(x: StatusScores) = intMapHandler write x.m.mapKeys(_.id.toString) - } - implicit val MoveBSONHandler = Macros.handler[Move] - implicit val TrimmedMovesBSONHandler = new BSONHandler[BSONArray, TrimmedMoves] { - def read(a: BSONArray) = TrimmedMoves { - a.values.collect { - case BSONInteger(i) => i - }.grouped(5).foldLeft(Vector.empty[Move]) { - case (acc, i) => - acc :+ Move(i(0), NbSum(i(1), i(2)), NbSum(i(3), i(4))) - } - } - def write(x: TrimmedMoves) = BSONArray { - x.moves.toStream.flatMap { m => - List(m.nb, m.acpl.nb, m.acpl.sum, m.time.nb, m.time.sum) - } map BSONInteger.apply - } - } - implicit val ColorMovesBSONHandler = Macros.handler[ColorMoves] - - implicit val PerfResultsOutcomeStatusesBSONHandler = Macros.handler[OutcomeStatuses] - implicit val ResultsBestWinBSONHandler = Macros.handler[BestWin] - implicit val PerfResultsBestRatingBSONHandler = Macros.handler[BestRating] - implicit val SectionBSONHandler = Macros.handler[Section] - implicit val GameSectionsBSONHandler = Macros.handler[GameSections] - implicit val ResultsBSONHandler = Macros.handler[Results] - implicit val PerfResultsBSONHandler = Macros.handler[PerfResults] - - private val perfResultsMapHandler = Map.MapHandler[PerfResults] - private implicit val PerfResultsMapBSONHandler = new BSONHandler[BSONDocument, PerfResultsMap] { - def read(doc: BSONDocument): PerfResultsMap = PerfResultsMap { - perfResultsMapHandler read doc mapKeys PerfType.apply collect { case (Some(k), v) => k -> v } - } - def write(x: PerfResultsMap) = perfResultsMapHandler write x.m.mapKeys(_.key) - } - - private val resultsMapHandler = Map.MapHandler[Results] - private implicit val OpeningsMapBSONHandler = new BSONHandler[BSONDocument, OpeningsMap] { - def read(doc: BSONDocument): OpeningsMap = OpeningsMap(resultsMapHandler read doc) - def write(x: OpeningsMap) = resultsMapHandler write x.m - } - implicit val OpeningsBSONHandler = Macros.handler[Openings] - implicit val ColorResultsBSONHandler = Macros.handler[ColorResults] - - implicit val UserStatBSONHandler = new lila.db.BSON[UserStat] { - def reads(r: lila.db.BSON.Reader) = { - UserStat( - colorResults = r.getO[ColorResults]("colorResults") | ColorResults.empty, - openings = r.getO[Openings]("openings") | Openings.empty, - results = r.getO[PerfResults]("results") | PerfResults.empty, - perfResults = r.getO[PerfResults.PerfResultsMap]("perfResults") | PerfResults.emptyPerfResultsMap - ) - } - def writes(w: lila.db.BSON.Writer, o: UserStat) = BSONDocument( - "colorResults" -> o.colorResults, - "openings" -> o.openings, - "results" -> o.results, - "perfResults" -> o.perfResults) - } - implicit val PeriodBSONHandler = Macros.handler[Period] -} diff --git a/modules/coach_old/src/main/ColorResults.scala b/modules/coach_old/src/main/ColorResults.scala deleted file mode 100644 index e19cd8fe96..0000000000 --- a/modules/coach_old/src/main/ColorResults.scala +++ /dev/null @@ -1,25 +0,0 @@ -package lila.coach - -case class ColorResults(white: Results, black: Results) { - - def apply(c: chess.Color) = c.fold(white, black) - - def merge(o: ColorResults) = ColorResults( - white = white merge o.white, - black = black merge o.black) -} - -object ColorResults { - - val empty = ColorResults(Results.empty, Results.empty) - - case class Computation(white: Results.Computation, black: Results.Computation) { - - def aggregate(p: RichPov) = copy( - white = if (p.pov.color.white) white aggregate p else white, - black = if (p.pov.color.black) black aggregate p else black) - - def run = ColorResults(white = white.run, black = black.run) - } - val emptyComputation = Computation(Results.emptyComputation, Results.emptyComputation) -} diff --git a/modules/coach_old/src/main/Ecopening.scala b/modules/coach_old/src/main/Ecopening.scala deleted file mode 100644 index f9be49995d..0000000000 --- a/modules/coach_old/src/main/Ecopening.scala +++ /dev/null @@ -1,54 +0,0 @@ -package lila.coach - -case class Ecopening( - eco: Ecopening.ECO, - family: Ecopening.FamilyName, - name: String, - moves: String, - fen: Ecopening.FEN, - lastMoveUci: String) { - - 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(" ") - - override def toString = s"$eco $name ($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(game: lila.game.Game): Option[Ecopening] = - if (game.playable || game.turns < 4 || game.fromPosition || game.variant.exotic) none - else chess.Replay.boards( - moveStrs = game.pgnMoves take EcopeningDB.MAX_MOVES, - initialFen = none, - variant = chess.variant.Standard - ).toOption flatMap matchChronoBoards - - private def matchChronoBoards(boards: List[chess.Board]): Option[Ecopening] = - boards.reverse.foldLeft(none[Ecopening]) { - case (acc, board) => acc orElse { - EcopeningDB.allByFen get chess.format.Forsyth.exportBoard(board) - } - } -} diff --git a/modules/coach_old/src/main/EcopeningDB.scala b/modules/coach_old/src/main/EcopeningDB.scala deleted file mode 100644 index 35e85a9522..0000000000 --- a/modules/coach_old/src/main/EcopeningDB.scala +++ /dev/null @@ -1,514 +0,0 @@ -package lila.coach - -private[coach] object EcopeningDB { - - import Ecopening._ - - val MAX_MOVES = 25 - - lazy val allByFen: Map[FEN, Ecopening] = allByEco.map { - case (_, opening) => opening.fen -> opening - } - - lazy val allByEco: Map[ECO, Ecopening] = Map( - "A00" -> Ecopening("A00", "Uncommon Opening", "Uncommon Opening", "g4, a3, h3, etc.", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", ""), - "A01" -> Ecopening("A01", "Nimzovich-Larsen Attack", "Nimzovich-Larsen Attack", "b3", "rnbqkbnr/pppppppp/8/8/8/1P6/P1PPPPPP/RNBQKBNR", "b2b3"), - "A02" -> Ecopening("A02", "Bird's Opening", "Bird's Opening", "f4", "rnbqkbnr/pppppppp/8/8/5P2/8/PPPPP1PP/RNBQKBNR", "f2f4"), - "A03" -> Ecopening("A03", "Bird's Opening", "Bird's Opening", "f4 d5", "rnbqkbnr/ppp1pppp/8/3p4/5P2/8/PPPPP1PP/RNBQKBNR", "d7d5"), - "A04" -> Ecopening("A04", "Reti Opening", "Reti Opening", "Nf3", "rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R", "g1f3"), - "A05" -> Ecopening("A05", "Reti Opening", "Reti Opening", "Nf3 Nf6", "rnbqkb1r/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKB1R", "g8f6"), - "A06" -> Ecopening("A06", "Reti Opening", "Reti Opening", "Nf3 d5", "rnbqkbnr/ppp1pppp/8/3p4/8/5N2/PPPPPPPP/RNBQKB1R", "d7d5"), - "A07" -> Ecopening("A07", "King's Indian Attack", "King's Indian Attack", "Nf3 d5 g3", "rnbqkbnr/ppp1pppp/8/3p4/8/5NP1/PPPPPP1P/RNBQKB1R", "g2g3"), - "A08" -> Ecopening("A08", "King's Indian Attack", "King's Indian Attack", "Nf3 d5 g3 c5 Bg2", "rnbqkbnr/pp2pppp/8/2pp4/8/5NP1/PPPPPPBP/RNBQK2R", "f1g2"), - "A09" -> Ecopening("A09", "Reti Opening", "Reti Opening", "Nf3 d5 c4", "rnbqkbnr/ppp1pppp/8/3p4/2P5/5N2/PP1PPPPP/RNBQKB1R", "c2c4"), - "A10" -> Ecopening("A10", "English", "English", "c4", "rnbqkbnr/pppppppp/8/8/2P5/8/PP1PPPPP/RNBQKBNR", "c2c4"), - "A11" -> Ecopening("A11", "English", "English, Caro-Kann Defensive System", "c4 c6", "rnbqkbnr/pp1ppppp/2p5/8/2P5/8/PP1PPPPP/RNBQKBNR", "c7c6"), - "A12" -> Ecopening("A12", "English", "English with b3", "c4 c6 Nf3 d5 b3", "rnbqkbnr/pp2pppp/2p5/3p4/2P5/1P3N2/P2PPPPP/RNBQKB1R", "b2b3"), - "A13" -> Ecopening("A13", "English", "English", "c4 e6", "rnbqkbnr/pppp1ppp/4p3/8/2P5/8/PP1PPPPP/RNBQKBNR", "e7e6"), - "A14" -> Ecopening("A14", "English", "English", "c4 e6 Nf3 d5 g3 Nf6 Bg2 Be7 O-O", "rnbqk2r/ppp1bppp/4pn2/3p4/2P5/5NP1/PP1PPPBP/RNBQ1RK1", "e1g1"), - "A15" -> Ecopening("A15", "English", "English", "c4 Nf6", "rnbqkb1r/pppppppp/5n2/8/2P5/8/PP1PPPPP/RNBQKBNR", "g8f6"), - "A16" -> Ecopening("A16", "English", "English", "c4 Nf6 Nc3", "rnbqkb1r/pppppppp/5n2/8/2P5/2N5/PP1PPPPP/R1BQKBNR", "b1c3"), - "A17" -> Ecopening("A17", "English", "English", "c4 Nf6 Nc3 e6", "rnbqkb1r/pppp1ppp/4pn2/8/2P5/2N5/PP1PPPPP/R1BQKBNR", "e7e6"), - "A18" -> Ecopening("A18", "English", "English, Mikenas-Carls", "c4 Nf6 Nc3 e6 e4", "rnbqkb1r/pppp1ppp/4pn2/8/2P1P3/2N5/PP1P1PPP/R1BQKBNR", "e2e4"), - "A19" -> Ecopening("A19", "English", "English, Mikenas-Carls, Sicilian Variation", "c4 Nf6 Nc3 e6 e4 c5", "rnbqkb1r/pp1p1ppp/4pn2/2p5/2P1P3/2N5/PP1P1PPP/R1BQKBNR", "c7c5"), - "A20" -> Ecopening("A20", "English", "English", "c4 e5", "rnbqkbnr/pppp1ppp/8/4p3/2P5/8/PP1PPPPP/RNBQKBNR", "e7e5"), - "A21" -> Ecopening("A21", "English", "English", "c4 e5 Nc3", "rnbqkbnr/pppp1ppp/8/4p3/2P5/2N5/PP1PPPPP/R1BQKBNR", "b1c3"), - "A22" -> Ecopening("A22", "English", "English", "c4 e5 Nc3 Nf6", "rnbqkb1r/pppp1ppp/5n2/4p3/2P5/2N5/PP1PPPPP/R1BQKBNR", "g8f6"), - "A23" -> Ecopening("A23", "English", "English, Bremen System, Keres Variation", "c4 e5 Nc3 Nf6 g3 c6", "rnbqkb1r/pp1p1ppp/2p2n2/4p3/2P5/2N3P1/PP1PPP1P/R1BQKBNR", "c7c6"), - "A24" -> Ecopening("A24", "English", "English, Bremen System with ...g6", "c4 e5 Nc3 Nf6 g3 g6", "rnbqkb1r/pppp1p1p/5np1/4p3/2P5/2N3P1/PP1PPP1P/R1BQKBNR", "g7g6"), - "A25" -> Ecopening("A25", "English", "English", "c4 e5 Nc3 Nc6", "r1bqkbnr/pppp1ppp/2n5/4p3/2P5/2N5/PP1PPPPP/R1BQKBNR", "b8c6"), - "A26" -> Ecopening("A26", "English", "English", "c4 e5 Nc3 Nc6 g3 g6 Bg2 Bg7 d3 d6", "r1bqk1nr/ppp2pbp/2np2p1/4p3/2P5/2NP2P1/PP2PPBP/R1BQK1NR", "d7d6"), - "A27" -> Ecopening("A27", "English", "English, Three Knights System", "c4 e5 Nc3 Nc6 Nf3", "r1bqkbnr/pppp1ppp/2n5/4p3/2P5/2N2N2/PP1PPPPP/R1BQKB1R", "g1f3"), - "A28" -> Ecopening("A28", "English", "English", "c4 e5 Nc3 Nc6 Nf3 Nf6", "r1bqkb1r/pppp1ppp/2n2n2/4p3/2P5/2N2N2/PP1PPPPP/R1BQKB1R", "g8f6"), - "A29" -> Ecopening("A29", "English", "English, Four Knights, Kingside Fianchetto", "c4 e5 Nc3 Nc6 Nf3 Nf6 g3", "r1bqkb1r/pppp1ppp/2n2n2/4p3/2P5/2N2NP1/PP1PPP1P/R1BQKB1R", "g2g3"), - "A30" -> Ecopening("A30", "English", "English, Symmetrical", "c4 c5", "rnbqkbnr/pp1ppppp/8/2p5/2P5/8/PP1PPPPP/RNBQKBNR", "c7c5"), - "A31" -> Ecopening("A31", "English", "English, Symmetrical, Benoni Formation", "c4 c5 Nf3 Nf6 d4", "rnbqkb1r/pp1ppppp/5n2/2p5/2PP4/5N2/PP2PPPP/RNBQKB1R", "d2d4"), - "A32" -> Ecopening("A32", "English", "English, Symmetrical Variation", "c4 c5 Nf3 Nf6 d4 cxd4 Nxd4 e6", "rnbqkb1r/pp1p1ppp/4pn2/8/2PN4/8/PP2PPPP/RNBQKB1R", "e7e6"), - "A33" -> Ecopening("A33", "English", "English, Symmetrical", "c4 c5 Nf3 Nf6 d4 cxd4 Nxd4 e6 Nc3 Nc6", "r1bqkb1r/pp1p1ppp/2n1pn2/8/2PN4/2N5/PP2PPPP/R1BQKB1R", "b8c6"), - "A34" -> Ecopening("A34", "English", "English, Symmetrical", "c4 c5 Nc3", "rnbqkbnr/pp1ppppp/8/2p5/2P5/2N5/PP1PPPPP/R1BQKBNR", "b1c3"), - "A35" -> Ecopening("A35", "English", "English, Symmetrical", "c4 c5 Nc3 Nc6", "r1bqkbnr/pp1ppppp/2n5/2p5/2P5/2N5/PP1PPPPP/R1BQKBNR", "b8c6"), - "A36" -> Ecopening("A36", "English", "English", "c4 c5 Nc3 Nc6 g3", "r1bqkbnr/pp1ppppp/2n5/2p5/2P5/2N3P1/PP1PPP1P/R1BQKBNR", "g2g3"), - "A37" -> Ecopening("A37", "English", "English, Symmetrical", "c4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 Nf3", "r1bqk1nr/pp1pppbp/2n3p1/2p5/2P5/2N2NP1/PP1PPPBP/R1BQK2R", "g1f3"), - "A38" -> Ecopening("A38", "English", "English, Symmetrical", "c4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 Nf3 Nf6", "r1bqk2r/pp1pppbp/2n2np1/2p5/2P5/2N2NP1/PP1PPPBP/R1BQK2R", "g8f6"), - "A39" -> Ecopening("A39", "English", "English, Symmetrical, Main line with d4", "c4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 Nf3 Nf6 O-O O-O d4", "r1bq1rk1/pp1pppbp/2n2np1/2p5/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "d2d4"), - "A40" -> Ecopening("A40", "Queen's Pawn Game", "Queen's Pawn Game", "d4", "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR", "d2d4"), - "A41" -> Ecopening("A41", "Queen's Pawn Game", "Queen's Pawn Game (with ...d6)", "d4 d6", "rnbqkbnr/ppp1pppp/3p4/8/3P4/8/PPP1PPPP/RNBQKBNR", "d7d6"), - "A42" -> Ecopening("A42", "Modern Defence", "Modern Defence, Averbakh System", "d4 d6 c4 g6 Nc3 Bg7 e4", "rnbqk1nr/ppp1ppbp/3p2p1/8/2PPP3/2N5/PP3PPP/R1BQKBNR", "e2e4"), - "A43" -> Ecopening("A43", "Old Benoni", "Old Benoni", "d4 c5", "rnbqkbnr/pp1ppppp/8/2p5/3P4/8/PPP1PPPP/RNBQKBNR", "c7c5"), - "A44" -> Ecopening("A44", "Old Benoni", "Old Benoni Defence", "d4 c5 d5 e5", "rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR", "e7e5"), - "A45" -> Ecopening("A45", "Queen's Pawn Game", "Queen's Pawn Game", "d4 Nf6", "rnbqkb1r/pppppppp/5n2/8/3P4/8/PPP1PPPP/RNBQKBNR", "g8f6"), - "A46" -> Ecopening("A46", "Queen's Pawn Game", "Queen's Pawn Game", "d4 Nf6 Nf3", "rnbqkb1r/pppppppp/5n2/8/3P4/5N2/PPP1PPPP/RNBQKB1R", "g1f3"), - "A47" -> Ecopening("A47", "Queen's Indian", "Queen's Indian", "d4 Nf6 Nf3 b6", "rnbqkb1r/p1pppppp/1p3n2/8/3P4/5N2/PPP1PPPP/RNBQKB1R", "b7b6"), - "A48" -> Ecopening("A48", "King's Indian", "King's Indian", "d4 Nf6 Nf3 g6", "rnbqkb1r/pppppp1p/5np1/8/3P4/5N2/PPP1PPPP/RNBQKB1R", "g7g6"), - "A49" -> Ecopening("A49", "King's Indian", "King's Indian, Fianchetto without c4", "d4 Nf6 Nf3 g6 g3", "rnbqkb1r/pppppp1p/5np1/8/3P4/5NP1/PPP1PP1P/RNBQKB1R", "g2g3"), - "A50" -> Ecopening("A50", "Queen's Pawn Game", "Queen's Pawn Game", "d4 Nf6 c4", "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR", "c2c4"), - "A51" -> Ecopening("A51", "Budapest Gambit", "Budapest Gambit", "d4 Nf6 c4 e5", "rnbqkb1r/pppp1ppp/5n2/4p3/2PP4/8/PP2PPPP/RNBQKBNR", "e7e5"), - "A52" -> Ecopening("A52", "Budapest Gambit", "Budapest Gambit", "d4 Nf6 c4 e5 dxe5 Ng4", "rnbqkb1r/pppp1ppp/8/4P3/2P3n1/8/PP2PPPP/RNBQKBNR", "f6g4"), - "A53" -> Ecopening("A53", "Old Indian", "Old Indian", "d4 Nf6 c4 d6", "rnbqkb1r/ppp1pppp/3p1n2/8/2PP4/8/PP2PPPP/RNBQKBNR", "d7d6"), - "A54" -> Ecopening("A54", "Old Indian", "Old Indian, Ukrainian Variation, 4.Nf3", "d4 Nf6 c4 d6 Nc3 e5 Nf3", "rnbqkb1r/ppp2ppp/3p1n2/4p3/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "g1f3"), - "A55" -> Ecopening("A55", "Old Indian", "Old Indian, Main line", "d4 Nf6 c4 d6 Nc3 e5 Nf3 Nbd7 e4", "r1bqkb1r/pppn1ppp/3p1n2/4p3/2PPP3/2N2N2/PP3PPP/R1BQKB1R", "e2e4"), - "A56" -> Ecopening("A56", "Benoni", "Benoni Defence", "d4 Nf6 c4 c5", "rnbqkb1r/pp1ppppp/5n2/2p5/2PP4/8/PP2PPPP/RNBQKBNR", "c7c5"), - "A57" -> Ecopening("A57", "Benko Gambit", "Benko Gambit", "d4 Nf6 c4 c5 d5 b5", "rnbqkb1r/p2ppppp/5n2/1ppP4/2P5/8/PP2PPPP/RNBQKBNR", "b7b5"), - "A58" -> Ecopening("A58", "Benko Gambit", "Benko Gambit", "d4 Nf6 c4 c5 d5 b5 cxb5 a6 bxa6", "rnbqkb1r/3ppppp/P4n2/2pP4/8/8/PP2PPPP/RNBQKBNR", "b5a6"), - "A59" -> Ecopening("A59", "Benko Gambit", "Benko Gambit", "d4 Nf6 c4 c5 d5 b5 cxb5 a6 bxa6 Bxa6 Nc3 d6 e4", "rn1qkb1r/4pppp/b2p1n2/2pP4/4P3/2N5/PP3PPP/R1BQKBNR", "e2e4"), - "A60" -> Ecopening("A60", "Benoni", "Benoni Defence", "d4 Nf6 c4 c5 d5 e6", "rnbqkb1r/pp1p1ppp/4pn2/2pP4/2P5/8/PP2PPPP/RNBQKBNR", "e7e6"), - "A61" -> Ecopening("A61", "Benoni", "Benoni", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 Nf3 g6", "rnbqkb1r/pp3p1p/3p1np1/2pP4/8/2N2N2/PP2PPPP/R1BQKB1R", "g7g6"), - "A62" -> Ecopening("A62", "Benoni", "Benoni, Fianchetto Variation", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 Nf3 g6 g3 Bg7 Bg2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/8/2N2NP1/PP2PPBP/R1BQK2R", "e8g8"), - "A63" -> Ecopening("A63", "Benoni", "Benoni, Fianchetto, 9...Nbd7", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 Nf3 g6 g3 Bg7 Bg2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/8/2N2NP1/PP2PPBP/R1BQK2R", "e8g8"), - "A64" -> Ecopening("A64", "Benoni", "Benoni, Fianchetto, 11...Re8", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 Nf3 g6 g3 Bg7 Bg2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/8/2N2NP1/PP2PPBP/R1BQK2R", "e8g8"), - "A65" -> Ecopening("A65", "Benoni", "Benoni, 6.e4", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4", "rnbqkb1r/pp3ppp/3p1n2/2pP4/4P3/2N5/PP3PPP/R1BQKBNR", "e2e4"), - "A66" -> Ecopening("A66", "Benoni", "Benoni", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 f4", "rnbqkb1r/pp3p1p/3p1np1/2pP4/4PP2/2N5/PP4PP/R1BQKBNR", "f2f4"), - "A67" -> Ecopening("A67", "Benoni", "Benoni, Taimanov Variation", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 f4 Bg7 Bb5+", "rnbqk2r/pp3pbp/3p1np1/1BpP4/4PP2/2N5/PP4PP/R1BQK1NR", "f1b5"), - "A68" -> Ecopening("A68", "Benoni", "Benoni, Four Pawns Attack", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 f4 Bg7 Nf3 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4PP2/2N2N2/PP4PP/R1BQKB1R", "e8g8"), - "A69" -> Ecopening("A69", "Benoni", "Benoni, Four Pawns Attack, Main line", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 f4 Bg7 Nf3 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4PP2/2N2N2/PP4PP/R1BQKB1R", "e8g8"), - "A70" -> Ecopening("A70", "Benoni", "Benoni, Classical with 7.Nf3", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3", "rnbqkb1r/pp3p1p/3p1np1/2pP4/4P3/2N2N2/PP3PPP/R1BQKB1R", "g1f3"), - "A71" -> Ecopening("A71", "Benoni", "Benoni, Classical, 8.Bg5", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Bg5", "rnbqk2r/pp3pbp/3p1np1/2pP2B1/4P3/2N2N2/PP3PPP/R2QKB1R", "c1g5"), - "A72" -> Ecopening("A72", "Benoni", "Benoni, Classical without 9.O-O", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A73" -> Ecopening("A73", "Benoni", "Benoni, Classical, 9.O-O", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A74" -> Ecopening("A74", "Benoni", "Benoni, Classical, 9...a6, 10.a4", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A75" -> Ecopening("A75", "Benoni", "Benoni, Classical with ...a6 and 10...Bg4", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A76" -> Ecopening("A76", "Benoni", "Benoni, Classical, 9...Re8", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A77" -> Ecopening("A77", "Benoni", "Benoni, Classical, 9...Re8, 10.Nd2", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A78" -> Ecopening("A78", "Benoni", "Benoni, Classical with ...Re8 and ...Na6", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A79" -> Ecopening("A79", "Benoni", "Benoni, Classical, 11.f3", "d4 Nf6 c4 c5 d5 e6 Nc3 exd5 cxd5 d6 e4 g6 Nf3 Bg7 Be2 O-O", "rnbq1rk1/pp3pbp/3p1np1/2pP4/4P3/2N2N2/PP2BPPP/R1BQK2R", "e8g8"), - "A80" -> Ecopening("A80", "Dutch", "Dutch", "d4 f5", "rnbqkbnr/ppppp1pp/8/5p2/3P4/8/PPP1PPPP/RNBQKBNR", "f7f5"), - "A81" -> Ecopening("A81", "Dutch", "Dutch", "d4 f5 g3", "rnbqkbnr/ppppp1pp/8/5p2/3P4/6P1/PPP1PP1P/RNBQKBNR", "g2g3"), - "A82" -> Ecopening("A82", "Dutch", "Dutch, Staunton Gambit", "d4 f5 e4", "rnbqkbnr/ppppp1pp/8/5p2/3PP3/8/PPP2PPP/RNBQKBNR", "e2e4"), - "A83" -> Ecopening("A83", "Dutch", "Dutch, Staunton Gambit", "d4 f5 e4 fxe4 Nc3 Nf6 Bg5", "rnbqkb1r/ppppp1pp/5n2/6B1/3Pp3/2N5/PPP2PPP/R2QKBNR", "c1g5"), - "A84" -> Ecopening("A84", "Dutch", "Dutch", "d4 f5 c4", "rnbqkbnr/ppppp1pp/8/5p2/2PP4/8/PP2PPPP/RNBQKBNR", "c2c4"), - "A85" -> Ecopening("A85", "Dutch", "Dutch, with c4 & Nc3", "d4 f5 c4 Nf6 Nc3", "rnbqkb1r/ppppp1pp/5n2/5p2/2PP4/2N5/PP2PPPP/R1BQKBNR", "b1c3"), - "A86" -> Ecopening("A86", "Dutch", "Dutch", "d4 f5 c4 Nf6 g3", "rnbqkb1r/ppppp1pp/5n2/5p2/2PP4/6P1/PP2PP1P/RNBQKBNR", "g2g3"), - "A87" -> Ecopening("A87", "Dutch", "Dutch, Leningrad, Main Variation", "d4 f5 c4 Nf6 g3 g6 Bg2 Bg7 Nf3", "rnbqk2r/ppppp1bp/5np1/5p2/2PP4/5NP1/PP2PPBP/RNBQK2R", "g1f3"), - "A88" -> Ecopening("A88", "Dutch", "Dutch, Leningrad, Main Variation with c6", "d4 f5 c4 Nf6 g3 g6 Bg2 Bg7 Nf3 O-O O-O d6 Nc3 c6", "rnbq1rk1/pp2p1bp/2pp1np1/5p2/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "c7c6"), - "A89" -> Ecopening("A89", "Dutch", "Dutch, Leningrad, Main Variation with Nc6", "d4 f5 c4 Nf6 g3 g6 Bg2 Bg7 Nf3 O-O O-O d6 Nc3 Nc6", "r1bq1rk1/ppp1p1bp/2np1np1/5p2/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "b8c6"), - "A90" -> Ecopening("A90", "Dutch", "Dutch", "d4 f5 c4 Nf6 g3 e6 Bg2", "rnbqkb1r/pppp2pp/4pn2/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR", "f1g2"), - "A91" -> Ecopening("A91", "Dutch", "Dutch Defence", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7", "rnbqk2r/ppppb1pp/4pn2/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR", "f8e7"), - "A92" -> Ecopening("A92", "Dutch", "Dutch", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O", "rnbq1rk1/ppppb1pp/4pn2/5p2/2PP4/5NP1/PP2PPBP/RNBQK2R", "e8g8"), - "A93" -> Ecopening("A93", "Dutch", "Dutch, Stonewall, Botvinnik Variation", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d5 b3", "rnbq1rk1/ppp1b1pp/4pn2/3p1p2/2PP4/1P3NP1/P3PPBP/RNBQ1RK1", "b2b3"), - "A94" -> Ecopening("A94", "Dutch", "Dutch, Stonewall with Ba3", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d5 b3 c6 Ba3", "rnbq1rk1/pp2b1pp/2p1pn2/3p1p2/2PP4/BP3NP1/P3PPBP/RN1Q1RK1", "c1a3"), - "A95" -> Ecopening("A95", "Dutch", "Dutch, Stonewall", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d5 Nc3 c6", "rnbq1rk1/pp2b1pp/2p1pn2/3p1p2/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "c7c6"), - "A96" -> Ecopening("A96", "Dutch", "Dutch, Classical Variation", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d6", "rnbq1rk1/ppp1b1pp/3ppn2/5p2/2PP4/5NP1/PP2PPBP/RNBQ1RK1", "d7d6"), - "A97" -> Ecopening("A97", "Dutch", "Dutch, Ilyin-Genevsky", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d6 Nc3 Qe8", "rnb1qrk1/ppp1b1pp/3ppn2/5p2/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "d8e8"), - "A98" -> Ecopening("A98", "Dutch", "Dutch, Ilyin-Genevsky Variation with Qc2", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d6 Nc3 Qe8 Qc2", "rnb1qrk1/ppp1b1pp/3ppn2/5p2/2PP4/2N2NP1/PPQ1PPBP/R1B2RK1", "d1c2"), - "A99" -> Ecopening("A99", "Dutch", "Dutch, Ilyin-Genevsky Variation with b3", "d4 f5 c4 Nf6 g3 e6 Bg2 Be7 Nf3 O-O O-O d6 Nc3 Qe8 b3", "rnb1qrk1/ppp1b1pp/3ppn2/5p2/2PP4/1PN2NP1/P3PPBP/R1BQ1RK1", "b2b3"), - "B00" -> Ecopening("B00", "Uncommon King's Pawn Opening", "Uncommon King's Pawn Opening", "e4", "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR", "e2e4"), - "B01" -> Ecopening("B01", "Scandinavian", "Scandinavian", "e4 d5", "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR", "d7d5"), - "B02" -> Ecopening("B02", "Alekhine's Defence", "Alekhine's Defence", "e4 Nf6", "rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR", "g8f6"), - "B03" -> Ecopening("B03", "Alekhine's Defence", "Alekhine's Defence", "e4 Nf6 e5 Nd5 d4", "rnbqkb1r/pppppppp/8/3nP3/3P4/8/PPP2PPP/RNBQKBNR", "d2d4"), - "B04" -> Ecopening("B04", "Alekhine's Defence", "Alekhine's Defence, Modern", "e4 Nf6 e5 Nd5 d4 d6 Nf3", "rnbqkb1r/ppp1pppp/3p4/3nP3/3P4/5N2/PPP2PPP/RNBQKB1R", "g1f3"), - "B05" -> Ecopening("B05", "Alekhine's Defence", "Alekhine's Defence, Modern", "e4 Nf6 e5 Nd5 d4 d6 Nf3 Bg4", "rn1qkb1r/ppp1pppp/3p4/3nP3/3P2b1/5N2/PPP2PPP/RNBQKB1R", "c8g4"), - "B06" -> Ecopening("B06", "Robatsch", "Robatsch", "e4 g6", "rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR", "g7g6"), - "B07" -> Ecopening("B07", "Pirc", "Pirc", "e4 d6 d4 Nf6", "rnbqkb1r/ppp1pppp/3p1n2/8/3PP3/8/PPP2PPP/RNBQKBNR", "g8f6"), - "B08" -> Ecopening("B08", "Pirc", "Pirc, Classical", "e4 d6 d4 Nf6 Nc3 g6 Nf3", "rnbqkb1r/ppp1pp1p/3p1np1/8/3PP3/2N2N2/PPP2PPP/R1BQKB1R", "g1f3"), - "B09" -> Ecopening("B09", "Pirc", "Pirc, Austrian Attack", "e4 d6 d4 Nf6 Nc3 g6 f4", "rnbqkb1r/ppp1pp1p/3p1np1/8/3PPP2/2N5/PPP3PP/R1BQKBNR", "f2f4"), - "B10" -> Ecopening("B10", "Caro-Kann", "Caro-Kann", "e4 c6", "rnbqkbnr/pp1ppppp/2p5/8/4P3/8/PPPP1PPP/RNBQKBNR", "c7c6"), - "B11" -> Ecopening("B11", "Caro-Kann", "Caro-Kann, Two Knights, 3...Bg4", "e4 c6 Nc3 d5 Nf3 Bg4", "rn1qkbnr/pp2pppp/2p5/3p4/4P1b1/2N2N2/PPPP1PPP/R1BQKB1R", "c8g4"), - "B12" -> Ecopening("B12", "Caro-Kann", "Caro-Kann Defence", "e4 c6 d4", "rnbqkbnr/pp1ppppp/2p5/8/3PP3/8/PPP2PPP/RNBQKBNR", "d2d4"), - "B13" -> Ecopening("B13", "Caro-Kann", "Caro-Kann, Exchange", "e4 c6 d4 d5 exd5 cxd5", "rnbqkbnr/pp2pppp/8/3p4/3P4/8/PPP2PPP/RNBQKBNR", "c6d5"), - "B14" -> Ecopening("B14", "Caro-Kann", "Caro-Kann, Panov-Botvinnik Attack", "e4 c6 d4 d5 exd5 cxd5 c4 Nf6 Nc3 e6", "rnbqkb1r/pp3ppp/4pn2/3p4/2PP4/2N5/PP3PPP/R1BQKBNR", "e7e6"), - "B15" -> Ecopening("B15", "Caro-Kann", "Caro-Kann", "e4 c6 d4 d5 Nc3", "rnbqkbnr/pp2pppp/2p5/3p4/3PP3/2N5/PPP2PPP/R1BQKBNR", "b1c3"), - "B16" -> Ecopening("B16", "Caro-Kann", "Caro-Kann, Bronstein-Larsen Variation", "e4 c6 d4 d5 Nc3 dxe4 Nxe4 Nf6 Nxf6+ gxf6", "rnbqkb1r/pp2pp1p/2p2p2/8/3P4/8/PPP2PPP/R1BQKBNR", "g7f6"), - "B17" -> Ecopening("B17", "Caro-Kann", "Caro-Kann, Steinitz Variation", "e4 c6 d4 d5 Nc3 dxe4 Nxe4 Nd7", "r1bqkbnr/pp1npppp/2p5/8/3PN3/8/PPP2PPP/R1BQKBNR", "b8d7"), - "B18" -> Ecopening("B18", "Caro-Kann", "Caro-Kann, Classical", "e4 c6 d4 d5 Nc3 dxe4 Nxe4 Bf5", "rn1qkbnr/pp2pppp/2p5/5b2/3PN3/8/PPP2PPP/R1BQKBNR", "c8f5"), - "B19" -> Ecopening("B19", "Caro-Kann", "Caro-Kann, Classical", "e4 c6 d4 d5 Nc3 dxe4 Nxe4 Bf5 Ng3 Bg6 h4 h6 Nf3 Nd7", "r2qkbnr/pp1nppp1/2p3bp/8/3P3P/5NN1/PPP2PP1/R1BQKB1R", "b8d7"), - "B20" -> Ecopening("B20", "Sicilian", "Sicilian", "e4 c5", "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR", "c7c5"), - "B21" -> Ecopening("B21", "Sicilian", "Sicilian, 2.f4 and 2.d4", "e4 c5 f4", "rnbqkbnr/pp1ppppp/8/2p5/4PP2/8/PPPP2PP/RNBQKBNR", "f2f4"), - "B22" -> Ecopening("B22", "Sicilian", "Sicilian, Alapin", "e4 c5 c3", "rnbqkbnr/pp1ppppp/8/2p5/4P3/2P5/PP1P1PPP/RNBQKBNR", "c2c3"), - "B23" -> Ecopening("B23", "Sicilian", "Sicilian, Closed", "e4 c5 Nc3", "rnbqkbnr/pp1ppppp/8/2p5/4P3/2N5/PPPP1PPP/R1BQKBNR", "b1c3"), - "B24" -> Ecopening("B24", "Sicilian", "Sicilian, Closed", "e4 c5 Nc3 Nc6 g3", "r1bqkbnr/pp1ppppp/2n5/2p5/4P3/2N3P1/PPPP1P1P/R1BQKBNR", "g2g3"), - "B25" -> Ecopening("B25", "Sicilian", "Sicilian, Closed", "e4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 d3 d6", "r1bqk1nr/pp2ppbp/2np2p1/2p5/4P3/2NP2P1/PPP2PBP/R1BQK1NR", "d7d6"), - "B26" -> Ecopening("B26", "Sicilian", "Sicilian, Closed, 6.Be3", "e4 c5 Nc3 Nc6 g3 g6 Bg2 Bg7 d3 d6 Be3", "r1bqk1nr/pp2ppbp/2np2p1/2p5/4P3/2NPB1P1/PPP2PBP/R2QK1NR", "c1e3"), - "B27" -> Ecopening("B27", "Sicilian", "Sicilian", "e4 c5 Nf3", "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "g1f3"), - "B28" -> Ecopening("B28", "Sicilian", "Sicilian, O'Kelly Variation", "e4 c5 Nf3 a6", "rnbqkbnr/1p1ppppp/p7/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "a7a6"), - "B29" -> Ecopening("B29", "Sicilian", "Sicilian, Nimzovich-Rubinstein", "e4 c5 Nf3 Nf6", "rnbqkb1r/pp1ppppp/5n2/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "g8f6"), - "B30" -> Ecopening("B30", "Sicilian", "Sicilian", "e4 c5 Nf3 Nc6", "r1bqkbnr/pp1ppppp/2n5/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "b8c6"), - "B31" -> Ecopening("B31", "Sicilian", "Sicilian, Rossolimo Variation", "e4 c5 Nf3 Nc6 Bb5 g6", "r1bqkbnr/pp1ppp1p/2n3p1/1Bp5/4P3/5N2/PPPP1PPP/RNBQK2R", "g7g6"), - "B32" -> Ecopening("B32", "Sicilian", "Sicilian", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 e5", "r1bqkbnr/pp1p1ppp/2n5/4p3/3NP3/8/PPP2PPP/RNBQKB1R", "e7e5"), - "B33" -> Ecopening("B33", "Sicilian", "Sicilian", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4", "r1bqkbnr/pp1ppppp/2n5/8/3NP3/8/PPP2PPP/RNBQKB1R", "f3d4"), - "B34" -> Ecopening("B34", "Sicilian", "Sicilian, Accelerated Fianchetto", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 Nxc6", "r1bqkbnr/pp1ppp1p/2N3p1/8/4P3/8/PPP2PPP/RNBQKB1R", "d4c6"), - "B35" -> Ecopening("B35", "Sicilian", "Sicilian, Accelerated Fianchetto, Modern Variation with Bc4", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 Nc3 Bg7 Be3 Nf6 Bc4", "r1bqk2r/pp1pppbp/2n2np1/8/2BNP3/2N1B3/PPP2PPP/R2QK2R", "f1c4"), - "B36" -> Ecopening("B36", "Sicilian", "Sicilian, Accelerated Fianchetto", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 c4", "r1bqkbnr/pp1ppp1p/2n3p1/8/2PNP3/8/PP3PPP/RNBQKB1R", "c2c4"), - "B37" -> Ecopening("B37", "Sicilian", "Sicilian, Accelerated Fianchetto", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 c4 Bg7", "r1bqk1nr/pp1pppbp/2n3p1/8/2PNP3/8/PP3PPP/RNBQKB1R", "f8g7"), - "B38" -> Ecopening("B38", "Sicilian", "Sicilian, Accelerated Fianchetto, Maroczy Bind, 6.Be3", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 c4 Bg7 Be3", "r1bqk1nr/pp1pppbp/2n3p1/8/2PNP3/4B3/PP3PPP/RN1QKB1R", "c1e3"), - "B39" -> Ecopening("B39", "Sicilian", "Sicilian, Accelerated Fianchetto, Breyer Variation", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 g6 c4 Bg7 Be3 Nf6 Nc3 Ng4", "r1bqk2r/pp1pppbp/2n3p1/8/2PNP1n1/2N1B3/PP3PPP/R2QKB1R", "f6g4"), - "B40" -> Ecopening("B40", "Sicilian", "Sicilian", "e4 c5 Nf3 e6", "rnbqkbnr/pp1p1ppp/4p3/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "e7e6"), - "B41" -> Ecopening("B41", "Sicilian", "Sicilian, Kan", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 a6", "rnbqkbnr/1p1p1ppp/p3p3/8/3NP3/8/PPP2PPP/RNBQKB1R", "a7a6"), - "B42" -> Ecopening("B42", "Sicilian", "Sicilian, Kan", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 a6 Bd3", "rnbqkbnr/1p1p1ppp/p3p3/8/3NP3/3B4/PPP2PPP/RNBQK2R", "f1d3"), - "B43" -> Ecopening("B43", "Sicilian", "Sicilian, Kan, 5.Nc3", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 a6 Nc3", "rnbqkbnr/1p1p1ppp/p3p3/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "b1c3"), - "B44" -> Ecopening("B44", "Sicilian", "Sicilian", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6", "r1bqkbnr/pp1p1ppp/2n1p3/8/3NP3/8/PPP2PPP/RNBQKB1R", "b8c6"), - "B45" -> Ecopening("B45", "Sicilian", "Sicilian, Taimanov", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3", "r1bqkbnr/pp1p1ppp/2n1p3/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "b1c3"), - "B46" -> Ecopening("B46", "Sicilian", "Sicilian, Taimanov Variation", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 a6", "r1bqkbnr/1p1p1ppp/p1n1p3/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "a7a6"), - "B47" -> Ecopening("B47", "Sicilian", "Sicilian, Taimanov (Bastrikov) Variation", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 Qc7", "r1b1kbnr/ppqp1ppp/2n1p3/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "d8c7"), - "B48" -> Ecopening("B48", "Sicilian", "Sicilian, Taimanov Variation", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 Qc7 Be3", "r1b1kbnr/ppqp1ppp/2n1p3/8/3NP3/2N1B3/PPP2PPP/R2QKB1R", "c1e3"), - "B49" -> Ecopening("B49", "Sicilian", "Sicilian, Taimanov Variation", "e4 c5 Nf3 e6 d4 cxd4 Nxd4 Nc6 Nc3 Qc7 Be3 a6 Be2", "r1b1kbnr/1pqp1ppp/p1n1p3/8/3NP3/2N1B3/PPP1BPPP/R2QK2R", "f1e2"), - "B50" -> Ecopening("B50", "Sicilian", "Sicilian", "e4 c5 Nf3 d6", "rnbqkbnr/pp2pppp/3p4/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R", "d7d6"), - "B51" -> Ecopening("B51", "Sicilian", "Sicilian, Canal-Sokolsky (Rossolimo) Attack", "e4 c5 Nf3 d6 Bb5+", "rnbqkbnr/pp2pppp/3p4/1Bp5/4P3/5N2/PPPP1PPP/RNBQK2R", "f1b5"), - "B52" -> Ecopening("B52", "Sicilian", "Sicilian, Canal-Sokolsky (Rossolimo) Attack", "e4 c5 Nf3 d6 Bb5+ Bd7", "rn1qkbnr/pp1bpppp/3p4/1Bp5/4P3/5N2/PPPP1PPP/RNBQK2R", "c8d7"), - "B53" -> Ecopening("B53", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Qxd4", "rnbqkbnr/pp2pppp/3p4/8/3QP3/5N2/PPP2PPP/RNB1KB1R", "d1d4"), - "B54" -> Ecopening("B54", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Nxd4", "rnbqkbnr/pp2pppp/3p4/8/3NP3/8/PPP2PPP/RNBQKB1R", "f3d4"), - "B55" -> Ecopening("B55", "Sicilian", "Sicilian, Prins Variation, Venice Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 f3 e5 Bb5+", "rnbqkb1r/pp3ppp/3p1n2/1B2p3/3NP3/5P2/PPP3PP/RNBQK2R", "f1b5"), - "B56" -> Ecopening("B56", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3", "rnbqkb1r/pp2pppp/3p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "b1c3"), - "B57" -> Ecopening("B57", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bc4", "r1bqkb1r/pp2pppp/2np1n2/8/2BNP3/2N5/PPP2PPP/R1BQK2R", "f1c4"), - "B58" -> Ecopening("B58", "Sicilian", "Sicilian", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 Nf6 Nc3 d6 Be2", "r1bqkb1r/pp2pppp/2np1n2/8/3NP3/2N5/PPP1BPPP/R1BQK2R", "f1e2"), - "B59" -> Ecopening("B59", "Sicilian", "Sicilian, Boleslavsky Variation, 7.Nb3", "e4 c5 Nf3 Nc6 d4 cxd4 Nxd4 Nf6 Nc3 d6 Be2 e5 Nb3", "r1bqkb1r/pp3ppp/2np1n2/4p3/4P3/1NN5/PPP1BPPP/R1BQK2R", "d4b3"), - "B60" -> Ecopening("B60", "Sicilian", "Sicilian, Richter-Rauzer", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5", "r1bqkb1r/pp2pppp/2np1n2/6B1/3NP3/2N5/PPP2PPP/R2QKB1R", "c1g5"), - "B61" -> Ecopening("B61", "Sicilian", "Sicilian, Richter-Rauzer, Larsen Variation, 7.Qd2", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 Bd7 Qd2", "r2qkb1r/pp1bpppp/2np1n2/6B1/3NP3/2N5/PPPQ1PPP/R3KB1R", "d1d2"), - "B62" -> Ecopening("B62", "Sicilian", "Sicilian, Richter-Rauzer", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6", "r1bqkb1r/pp3ppp/2nppn2/6B1/3NP3/2N5/PPP2PPP/R2QKB1R", "e7e6"), - "B63" -> Ecopening("B63", "Sicilian", "Sicilian, Richter-Rauzer Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2", "r1bqkb1r/pp3ppp/2nppn2/6B1/3NP3/2N5/PPPQ1PPP/R3KB1R", "d1d2"), - "B64" -> Ecopening("B64", "Sicilian", "Sicilian, Richter-Rauzer Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 Be7 O-O-O O-O f4", "r1bq1rk1/pp2bppp/2nppn2/6B1/3NPP2/2N5/PPPQ2PP/2KR1B1R", "f2f4"), - "B65" -> Ecopening("B65", "Sicilian", "Sicilian, Richter-Rauzer Attack, 7...Be7 Defence, 9...Nxd4", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 Be7 O-O-O O-O f4 Nxd4 Qxd4", "r1bq1rk1/pp2bppp/3ppn2/6B1/3QPP2/2N5/PPP3PP/2KR1B1R", "d2d4"), - "B66" -> Ecopening("B66", "Sicilian", "Sicilian, Richter-Rauzer Attack, 7...a6", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 a6", "r1bqkb1r/1p3ppp/p1nppn2/6B1/3NP3/2N5/PPPQ1PPP/R3KB1R", "a7a6"), - "B67" -> Ecopening("B67", "Sicilian", "Sicilian, Richter-Rauzer Attack, 7...a6 Defence, 8...Bd7", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 a6 O-O-O Bd7", "r2qkb1r/1p1b1ppp/p1nppn2/6B1/3NP3/2N5/PPPQ1PPP/2KR1B1R", "c8d7"), - "B68" -> Ecopening("B68", "Sicilian", "Sicilian, Richter-Rauzer Attack, 7...a6 Defence, 9...Be7", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 a6 O-O-O Bd7 f4 Be7", "r2qk2r/1p1bbppp/p1nppn2/6B1/3NPP2/2N5/PPPQ2PP/2KR1B1R", "f8e7"), - "B69" -> Ecopening("B69", "Sicilian", "Sicilian, Richter-Rauzer Attack, 7...a6 Defence, 11.Bxf6", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 Nc6 Bg5 e6 Qd2 a6 O-O-O Bd7 f4 Be7 Nf3 b5 Bxf6", "r2qk2r/3bbppp/p1nppB2/1p6/4PP2/2N2N2/PPPQ2PP/2KR1B1R", "g5f6"), - "B70" -> Ecopening("B70", "Sicilian", "Sicilian, Dragon Variation", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6", "rnbqkb1r/pp2pp1p/3p1np1/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "g7g6"), - "B71" -> Ecopening("B71", "Sicilian", "Sicilian, Dragon, Levenfish Variation", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 f4", "rnbqkb1r/pp2pp1p/3p1np1/8/3NPP2/2N5/PPP3PP/R1BQKB1R", "f2f4"), - "B72" -> Ecopening("B72", "Sicilian", "Sicilian, Dragon", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3", "rnbqkb1r/pp2pp1p/3p1np1/8/3NP3/2N1B3/PPP2PPP/R2QKB1R", "c1e3"), - "B73" -> Ecopening("B73", "Sicilian", "Sicilian, Dragon, Classical", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 Be2 Nc6 O-O", "r1bqk2r/pp2ppbp/2np1np1/8/3NP3/2N1B3/PPP1BPPP/R2Q1RK1", "e1g1"), - "B74" -> Ecopening("B74", "Sicilian", "Sicilian, Dragon, Classical", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 Be2 Nc6 O-O O-O Nb3", "r1bq1rk1/pp2ppbp/2np1np1/8/4P3/1NN1B3/PPP1BPPP/R2Q1RK1", "d4b3"), - "B75" -> Ecopening("B75", "Sicilian", "Sicilian, Dragon, Yugoslav Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3", "rnbqk2r/pp2ppbp/3p1np1/8/3NP3/2N1BP2/PPP3PP/R2QKB1R", "f2f3"), - "B76" -> Ecopening("B76", "Sicilian", "Sicilian, Dragon, Yugoslav Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O", "rnbq1rk1/pp2ppbp/3p1np1/8/3NP3/2N1BP2/PPP3PP/R2QKB1R", "e8g8"), - "B77" -> Ecopening("B77", "Sicilian", "Sicilian, Dragon, Yugoslav Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O Qd2 Nc6 Bc4", "r1bq1rk1/pp2ppbp/2np1np1/8/2BNP3/2N1BP2/PPPQ2PP/R3K2R", "f1c4"), - "B78" -> Ecopening("B78", "Sicilian", "Sicilian, Dragon, Yugoslav Attack, 10.castle long", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O Qd2 Nc6 Bc4 Bd7 O-O-O", "r2q1rk1/pp1bppbp/2np1np1/8/2BNP3/2N1BP2/PPPQ2PP/2KR3R", "e1c1"), - "B79" -> Ecopening("B79", "Sicilian", "Sicilian, Dragon, Yugoslav Attack, 12.h4", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6 Be3 Bg7 f3 O-O Qd2 Nc6 Bc4 Bd7 O-O-O Qa5 Bb3 Rfc8 h4", "r1r3k1/pp1bppbp/2np1np1/q7/3NP2P/1BN1BP2/PPPQ2P1/2KR3R", "h2h4"), - "B80" -> Ecopening("B80", "Sicilian", "Sicilian, Scheveningen", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6", "rnbqkb1r/pp3ppp/3ppn2/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "e7e6"), - "B81" -> Ecopening("B81", "Sicilian", "Sicilian, Scheveningen, Keres Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 g4", "rnbqkb1r/pp3ppp/3ppn2/8/3NP1P1/2N5/PPP2P1P/R1BQKB1R", "g2g4"), - "B82" -> Ecopening("B82", "Sicilian", "Sicilian, Scheveningen", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 f4", "rnbqkb1r/pp3ppp/3ppn2/8/3NPP2/2N5/PPP3PP/R1BQKB1R", "f2f4"), - "B83" -> Ecopening("B83", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Be2", "rnbqkb1r/pp3ppp/3ppn2/8/3NP3/2N5/PPP1BPPP/R1BQK2R", "f1e2"), - "B84" -> Ecopening("B84", "Sicilian", "Sicilian, Scheveningen", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Be2 a6", "rnbqkb1r/1p3ppp/p2ppn2/8/3NP3/2N5/PPP1BPPP/R1BQK2R", "a7a6"), - "B85" -> Ecopening("B85", "Sicilian", "Sicilian, Scheveningen, Classical", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Be2 a6 O-O Qc7 f4 Nc6", "r1b1kb1r/1pq2ppp/p1nppn2/8/3NPP2/2N5/PPP1B1PP/R1BQ1RK1", "b8c6"), - "B86" -> Ecopening("B86", "Sicilian", "Sicilian, Fischer-Sozin Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Bc4", "rnbqkb1r/pp3ppp/3ppn2/8/2BNP3/2N5/PPP2PPP/R1BQK2R", "f1c4"), - "B87" -> Ecopening("B87", "Sicilian", "Sicilian, Fischer-Sozin with ...a6 and ...b5", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Bc4 a6 Bb3 b5", "rnbqkb1r/5ppp/p2ppn2/1p6/3NP3/1BN5/PPP2PPP/R1BQK2R", "b7b5"), - "B88" -> Ecopening("B88", "Sicilian", "Sicilian, Fischer-Sozin Attack", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Bc4 Nc6", "r1bqkb1r/pp3ppp/2nppn2/8/2BNP3/2N5/PPP2PPP/R1BQK2R", "b8c6"), - "B89" -> Ecopening("B89", "Sicilian", "Sicilian", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 e6 Bc4 Nc6 Be3", "r1bqkb1r/pp3ppp/2nppn2/8/2BNP3/2N1B3/PPP2PPP/R2QK2R", "c1e3"), - "B90" -> Ecopening("B90", "Sicilian", "Sicilian, Najdorf", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6", "rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP2PPP/R1BQKB1R", "a7a6"), - "B91" -> Ecopening("B91", "Sicilian", "Sicilian, Najdorf, Zagreb (Fianchetto) Variation", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 g3", "rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N3P1/PPP2P1P/R1BQKB1R", "g2g3"), - "B92" -> Ecopening("B92", "Sicilian", "Sicilian, Najdorf, Opocensky Variation", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Be2", "rnbqkb1r/1p2pppp/p2p1n2/8/3NP3/2N5/PPP1BPPP/R1BQK2R", "f1e2"), - "B93" -> Ecopening("B93", "Sicilian", "Sicilian, Najdorf, 6.f4", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 f4", "rnbqkb1r/1p2pppp/p2p1n2/8/3NPP2/2N5/PPP3PP/R1BQKB1R", "f2f4"), - "B94" -> Ecopening("B94", "Sicilian", "Sicilian, Najdorf", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5", "rnbqkb1r/1p2pppp/p2p1n2/6B1/3NP3/2N5/PPP2PPP/R2QKB1R", "c1g5"), - "B95" -> Ecopening("B95", "Sicilian", "Sicilian, Najdorf, 6...e6", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6", "rnbqkb1r/1p3ppp/p2ppn2/6B1/3NP3/2N5/PPP2PPP/R2QKB1R", "e7e6"), - "B96" -> Ecopening("B96", "Sicilian", "Sicilian, Najdorf", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6 f4", "rnbqkb1r/1p3ppp/p2ppn2/6B1/3NPP2/2N5/PPP3PP/R2QKB1R", "f2f4"), - "B97" -> Ecopening("B97", "Sicilian", "Sicilian, Najdorf", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6 f4 Qb6", "rnb1kb1r/1p3ppp/pq1ppn2/6B1/3NPP2/2N5/PPP3PP/R2QKB1R", "d8b6"), - "B98" -> Ecopening("B98", "Sicilian", "Sicilian, Najdorf", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6 f4 Be7", "rnbqk2r/1p2bppp/p2ppn2/6B1/3NPP2/2N5/PPP3PP/R2QKB1R", "f8e7"), - "B99" -> Ecopening("B99", "Sicilian", "Sicilian, Najdorf, 7...Be7 Main line", "e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 a6 Bg5 e6 f4 Be7 Qf3 Qc7 O-O-O Nbd7", "r1b1k2r/1pqnbppp/p2ppn2/6B1/3NPP2/2N2Q2/PPP3PP/2KR1B1R", "b8d7"), - "C00" -> Ecopening("C00", "French", "French Defence", "e4 e6", "rnbqkbnr/pppp1ppp/4p3/8/4P3/8/PPPP1PPP/RNBQKBNR", "e7e6"), - "C01" -> Ecopening("C01", "French", "French, Exchange", "e4 e6 d4 d5 exd5 exd5 Nc3 Nf6 Bg5", "rnbqkb1r/ppp2ppp/5n2/3p2B1/3P4/2N5/PPP2PPP/R2QKBNR", "c1g5"), - "C02" -> Ecopening("C02", "French", "French, Advance", "e4 e6 d4 d5 e5", "rnbqkbnr/ppp2ppp/4p3/3pP3/3P4/8/PPP2PPP/RNBQKBNR", "e4e5"), - "C03" -> Ecopening("C03", "French", "French, Tarrasch", "e4 e6 d4 d5 Nd2", "rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/8/PPPN1PPP/R1BQKBNR", "b1d2"), - "C04" -> Ecopening("C04", "French", "French, Tarrasch, Guimard Main line", "e4 e6 d4 d5 Nd2 Nc6 Ngf3 Nf6", "r1bqkb1r/ppp2ppp/2n1pn2/3p4/3PP3/5N2/PPPN1PPP/R1BQKB1R", "g8f6"), - "C05" -> Ecopening("C05", "French", "French, Tarrasch", "e4 e6 d4 d5 Nd2 Nf6", "rnbqkb1r/ppp2ppp/4pn2/3p4/3PP3/8/PPPN1PPP/R1BQKBNR", "g8f6"), - "C06" -> Ecopening("C06", "French", "French, Tarrasch", "e4 e6 d4 d5 Nd2 Nf6 e5 Nfd7 Bd3 c5 c3 Nc6 Ne2 cxd4 cxd4", "r1bqkb1r/pp1n1ppp/2n1p3/3pP3/3P4/3B4/PP1NNPPP/R1BQK2R", "c3d4"), - "C07" -> Ecopening("C07", "French", "French, Tarrasch", "e4 e6 d4 d5 Nd2 c5", "rnbqkbnr/pp3ppp/4p3/2pp4/3PP3/8/PPPN1PPP/R1BQKBNR", "c7c5"), - "C08" -> Ecopening("C08", "French", "French, Tarrasch, Open, 4.ed ed", "e4 e6 d4 d5 Nd2 c5 exd5 exd5", "rnbqkbnr/pp3ppp/8/2pp4/3P4/8/PPPN1PPP/R1BQKBNR", "e6d5"), - "C09" -> Ecopening("C09", "French", "French, Tarrasch, Open Variation, Main line", "e4 e6 d4 d5 Nd2 c5 exd5 exd5 Ngf3 Nc6", "r1bqkbnr/pp3ppp/2n5/2pp4/3P4/5N2/PPPN1PPP/R1BQKB1R", "b8c6"), - "C10" -> Ecopening("C10", "French", "French", "e4 e6 d4 d5 Nc3", "rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/2N5/PPP2PPP/R1BQKBNR", "b1c3"), - "C11" -> Ecopening("C11", "French", "French", "e4 e6 d4 d5 Nc3 Nf6", "rnbqkb1r/ppp2ppp/4pn2/3p4/3PP3/2N5/PPP2PPP/R1BQKBNR", "g8f6"), - "C12" -> Ecopening("C12", "French", "French, McCutcheon", "e4 e6 d4 d5 Nc3 Nf6 Bg5 Bb4", "rnbqk2r/ppp2ppp/4pn2/3p2B1/1b1PP3/2N5/PPP2PPP/R2QKBNR", "f8b4"), - "C13" -> Ecopening("C13", "French", "French", "e4 e6 d4 d5 Nc3 Nf6 Bg5 Be7", "rnbqk2r/ppp1bppp/4pn2/3p2B1/3PP3/2N5/PPP2PPP/R2QKBNR", "f8e7"), - "C14" -> Ecopening("C14", "French", "French, Classical", "e4 e6 d4 d5 Nc3 Nf6 Bg5 Be7 e5 Nfd7 Bxe7 Qxe7", "rnb1k2r/pppnqppp/4p3/3pP3/3P4/2N5/PPP2PPP/R2QKBNR", "d8e7"), - "C15" -> Ecopening("C15", "French", "French, Winawer", "e4 e6 d4 d5 Nc3 Bb4", "rnbqk1nr/ppp2ppp/4p3/3p4/1b1PP3/2N5/PPP2PPP/R1BQKBNR", "f8b4"), - "C16" -> Ecopening("C16", "French", "French, Winawer", "e4 e6 d4 d5 Nc3 Bb4 e5", "rnbqk1nr/ppp2ppp/4p3/3pP3/1b1P4/2N5/PPP2PPP/R1BQKBNR", "e4e5"), - "C17" -> Ecopening("C17", "French", "French, Winawer, Advance", "e4 e6 d4 d5 Nc3 Bb4 e5 c5", "rnbqk1nr/pp3ppp/4p3/2ppP3/1b1P4/2N5/PPP2PPP/R1BQKBNR", "c7c5"), - "C18" -> Ecopening("C18", "French", "French, Winawer", "e4 e6 d4 d5 Nc3 Bb4 e5 c5 a3 Bxc3+ bxc3", "rnbqk1nr/pp3ppp/4p3/2ppP3/3P4/P1P5/2P2PPP/R1BQKBNR", "b2c3"), - "C19" -> Ecopening("C19", "French", "French, Winawer, Advance", "e4 e6 d4 d5 Nc3 Bb4 e5 c5 a3 Bxc3+ bxc3 Ne7", "rnbqk2r/pp2nppp/4p3/2ppP3/3P4/P1P5/2P2PPP/R1BQKBNR", "g8e7"), - "C20" -> Ecopening("C20", "King's Pawn Game", "King's Pawn Game", "e4 e5", "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR", "e7e5"), - "C21" -> Ecopening("C21", "Center Game", "Center Game", "e4 e5 d4 exd4", "rnbqkbnr/pppp1ppp/8/8/3pP3/8/PPP2PPP/RNBQKBNR", "e5d4"), - "C22" -> Ecopening("C22", "Center Game", "Center Game", "e4 e5 d4 exd4 Qxd4 Nc6", "r1bqkbnr/pppp1ppp/2n5/8/3QP3/8/PPP2PPP/RNB1KBNR", "b8c6"), - "C23" -> Ecopening("C23", "Bishop's Opening", "Bishop's Opening", "e4 e5 Bc4", "rnbqkbnr/pppp1ppp/8/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR", "f1c4"), - "C24" -> Ecopening("C24", "Bishop's Opening", "Bishop's Opening", "e4 e5 Bc4 Nf6", "rnbqkb1r/pppp1ppp/5n2/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR", "g8f6"), - "C25" -> Ecopening("C25", "Vienna", "Vienna", "e4 e5 Nc3", "rnbqkbnr/pppp1ppp/8/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR", "b1c3"), - "C26" -> Ecopening("C26", "Vienna", "Vienna", "e4 e5 Nc3 Nf6", "rnbqkb1r/pppp1ppp/5n2/4p3/4P3/2N5/PPPP1PPP/R1BQKBNR", "g8f6"), - "C27" -> Ecopening("C27", "Vienna Game", "Vienna Game", "e4 e5 Nc3 Nf6 Bc4 Nxe4", "rnbqkb1r/pppp1ppp/8/4p3/2B1n3/2N5/PPPP1PPP/R1BQK1NR", "f6e4"), - "C28" -> Ecopening("C28", "Vienna Game", "Vienna Game", "e4 e5 Nc3 Nf6 Bc4 Nc6", "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/2N5/PPPP1PPP/R1BQK1NR", "b8c6"), - "C29" -> Ecopening("C29", "Vienna Gambit", "Vienna Gambit", "e4 e5 Nc3 Nf6 f4", "rnbqkb1r/pppp1ppp/5n2/4p3/4PP2/2N5/PPPP2PP/R1BQKBNR", "f2f4"), - "C30" -> Ecopening("C30", "King's Gambit Declined", "King's Gambit Declined", "e4 e5 f4", "rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR", "f2f4"), - "C31" -> Ecopening("C31", "King's Gambit Declined", "King's Gambit Declined, Falkbeer Counter Gambit", "e4 e5 f4 d5", "rnbqkbnr/ppp2ppp/8/3pp3/4PP2/8/PPPP2PP/RNBQKBNR", "d7d5"), - "C32" -> Ecopening("C32", "King's Gambit Declined", "King's Gambit Declined, Falkbeer Counter Gambit", "e4 e5 f4 d5 exd5 e4 d3 Nf6", "rnbqkb1r/ppp2ppp/5n2/3P4/4pP2/3P4/PPP3PP/RNBQKBNR", "g8f6"), - "C33" -> Ecopening("C33", "King's Gambit Accepted", "King's Gambit Accepted", "e4 e5 f4 exf4", "rnbqkbnr/pppp1ppp/8/8/4Pp2/8/PPPP2PP/RNBQKBNR", "e5f4"), - "C34" -> Ecopening("C34", "King's Gambit Accepted", "King's Gambit Accepted", "e4 e5 f4 exf4 Nf3", "rnbqkbnr/pppp1ppp/8/8/4Pp2/5N2/PPPP2PP/RNBQKB1R", "g1f3"), - "C35" -> Ecopening("C35", "King's Gambit Accepted", "King's Gambit Accepted, Cunningham", "e4 e5 f4 exf4 Nf3 Be7", "rnbqk1nr/ppppbppp/8/8/4Pp2/5N2/PPPP2PP/RNBQKB1R", "f8e7"), - "C36" -> Ecopening("C36", "King's Gambit Accepted", "King's Gambit Accepted, Abbazia Defence", "e4 e5 f4 exf4 Nf3 d5", "rnbqkbnr/ppp2ppp/8/3p4/4Pp2/5N2/PPPP2PP/RNBQKB1R", "d7d5"), - "C37" -> Ecopening("C37", "King's Gambit Accepted", "King's Gambit Accepted", "e4 e5 f4 exf4 Nf3 g5 Nc3", "rnbqkbnr/pppp1p1p/8/6p1/4Pp2/2N2N2/PPPP2PP/R1BQKB1R", "b1c3"), - "C38" -> Ecopening("C38", "King's Gambit Accepted", "King's Gambit Accepted", "e4 e5 f4 exf4 Nf3 g5 Bc4 Bg7", "rnbqk1nr/pppp1pbp/8/6p1/2B1Pp2/5N2/PPPP2PP/RNBQK2R", "f8g7"), - "C39" -> Ecopening("C39", "King's Gambit Accepted", "King's Gambit Accepted", "e4 e5 f4 exf4 Nf3 g5 h4", "rnbqkbnr/pppp1p1p/8/6p1/4Pp1P/5N2/PPPP2P1/RNBQKB1R", "h2h4"), - "C40" -> Ecopening("C40", "King's Knight Opening", "King's Knight Opening", "e4 e5 Nf3", "rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R", "g1f3"), - "C41" -> Ecopening("C41", "Philidor Defence", "Philidor Defence", "e4 e5 Nf3 d6", "rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R", "d7d6"), - "C42" -> Ecopening("C42", "Petrov Defence", "Petrov Defence", "e4 e5 Nf3 Nf6", "rnbqkb1r/pppp1ppp/5n2/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R", "g8f6"), - "C43" -> Ecopening("C43", "Petrov", "Petrov, Modern Attack", "e4 e5 Nf3 Nf6 d4 exd4 e5 Ne4 Qxd4", "rnbqkb1r/pppp1ppp/8/4P3/3Qn3/5N2/PPP2PPP/RNB1KB1R", "d1d4"), - "C44" -> Ecopening("C44", "King's Pawn Game", "King's Pawn Game", "e4 e5 Nf3 Nc6", "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R", "b8c6"), - "C45" -> Ecopening("C45", "Scotch Game", "Scotch Game", "e4 e5 Nf3 Nc6 d4 exd4 Nxd4", "r1bqkbnr/pppp1ppp/2n5/8/3NP3/8/PPP2PPP/RNBQKB1R", "f3d4"), - "C46" -> Ecopening("C46", "Three Knights", "Three Knights", "e4 e5 Nf3 Nc6 Nc3", "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/2N2N2/PPPP1PPP/R1BQKB1R", "b1c3"), - "C47" -> Ecopening("C47", "Four Knights", "Four Knights", "e4 e5 Nf3 Nc6 Nc3 Nf6", "r1bqkb1r/pppp1ppp/2n2n2/4p3/4P3/2N2N2/PPPP1PPP/R1BQKB1R", "g8f6"), - "C48" -> Ecopening("C48", "Four Knights", "Four Knights", "e4 e5 Nf3 Nc6 Nc3 Nf6 Bb5", "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/2N2N2/PPPP1PPP/R1BQK2R", "f1b5"), - "C49" -> Ecopening("C49", "Four Knights", "Four Knights", "e4 e5 Nf3 Nc6 Nc3 Nf6 Bb5 Bb4", "r1bqk2r/pppp1ppp/2n2n2/1B2p3/1b2P3/2N2N2/PPPP1PPP/R1BQK2R", "f8b4"), - "C50" -> Ecopening("C50", "Giuoco Piano", "Giuoco Piano", "e4 e5 Nf3 Nc6 Bc4 Bc5", "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R", "f8c5"), - "C51" -> Ecopening("C51", "Evans Gambit", "Evans Gambit", "e4 e5 Nf3 Nc6 Bc4 Bc5 b4", "r1bqk1nr/pppp1ppp/2n5/2b1p3/1PB1P3/5N2/P1PP1PPP/RNBQK2R", "b2b4"), - "C52" -> Ecopening("C52", "Evans Gambit", "Evans Gambit", "e4 e5 Nf3 Nc6 Bc4 Bc5 b4 Bxb4 c3 Ba5", "r1bqk1nr/pppp1ppp/2n5/b3p3/2B1P3/2P2N2/P2P1PPP/RNBQK2R", "b4a5"), - "C53" -> Ecopening("C53", "Giuoco Piano", "Giuoco Piano", "e4 e5 Nf3 Nc6 Bc4 Bc5 c3", "r1bqk1nr/pppp1ppp/2n5/2b1p3/2B1P3/2P2N2/PP1P1PPP/RNBQK2R", "c2c3"), - "C54" -> Ecopening("C54", "Giuoco Piano", "Giuoco Piano", "e4 e5 Nf3 Nc6 Bc4 Bc5 c3 Nf6 d4 exd4 cxd4", "r1bqk2r/pppp1ppp/2n2n2/2b5/2BPP3/5N2/PP3PPP/RNBQK2R", "c3d4"), - "C55" -> Ecopening("C55", "Two Knights", "Two Knights Defence", "e4 e5 Nf3 Nc6 Bc4 Nf6", "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R", "g8f6"), - "C56" -> Ecopening("C56", "Two Knights", "Two Knights", "e4 e5 Nf3 Nc6 Bc4 Nf6 d4 exd4 O-O Nxe4", "r1bqkb1r/pppp1ppp/2n5/8/2Bpn3/5N2/PPP2PPP/RNBQ1RK1", "f6e4"), - "C57" -> Ecopening("C57", "Two Knights", "Two Knights", "e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5", "r1bqkb1r/pppp1ppp/2n2n2/4p1N1/2B1P3/8/PPPP1PPP/RNBQK2R", "f3g5"), - "C58" -> Ecopening("C58", "Two Knights", "Two Knights", "e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5", "r1bqkb1r/ppp2ppp/5n2/n2Pp1N1/2B5/8/PPPP1PPP/RNBQK2R", "c6a5"), - "C59" -> Ecopening("C59", "Two Knights", "Two Knights", "e4 e5 Nf3 Nc6 Bc4 Nf6 Ng5 d5 exd5 Na5 Bb5+ c6 dxc6 bxc6 Be2 h6", "r1bqkb1r/p4pp1/2p2n1p/n3p1N1/8/8/PPPPBPPP/RNBQK2R", "h7h6"), - "C60" -> Ecopening("C60", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5", "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R", "f1b5"), - "C61" -> Ecopening("C61", "Ruy Lopez", "Ruy Lopez, Bird's Defence", "e4 e5 Nf3 Nc6 Bb5 Nd4", "r1bqkbnr/pppp1ppp/8/1B2p3/3nP3/5N2/PPPP1PPP/RNBQK2R", "c6d4"), - "C62" -> Ecopening("C62", "Ruy Lopez", "Ruy Lopez, Old Steinitz Defence", "e4 e5 Nf3 Nc6 Bb5 d6", "r1bqkbnr/ppp2ppp/2np4/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R", "d7d6"), - "C63" -> Ecopening("C63", "Ruy Lopez", "Ruy Lopez, Schliemann Defence", "e4 e5 Nf3 Nc6 Bb5 f5", "r1bqkbnr/pppp2pp/2n5/1B2pp2/4P3/5N2/PPPP1PPP/RNBQK2R", "f7f5"), - "C64" -> Ecopening("C64", "Ruy Lopez", "Ruy Lopez, Classical", "e4 e5 Nf3 Nc6 Bb5 Bc5", "r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R", "f8c5"), - "C65" -> Ecopening("C65", "Ruy Lopez", "Ruy Lopez, Berlin Defence", "e4 e5 Nf3 Nc6 Bb5 Nf6", "r1bqkb1r/pppp1ppp/2n2n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R", "g8f6"), - "C66" -> Ecopening("C66", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 Nf6 O-O d6", "r1bqkb1r/ppp2ppp/2np1n2/1B2p3/4P3/5N2/PPPP1PPP/RNBQ1RK1", "d7d6"), - "C67" -> Ecopening("C67", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 Nf6 O-O Nxe4", "r1bqkb1r/pppp1ppp/2n5/1B2p3/4n3/5N2/PPPP1PPP/RNBQ1RK1", "f6e4"), - "C68" -> Ecopening("C68", "Ruy Lopez", "Ruy Lopez, Exchange", "e4 e5 Nf3 Nc6 Bb5 a6 Bxc6", "r1bqkbnr/1ppp1ppp/p1B5/4p3/4P3/5N2/PPPP1PPP/RNBQK2R", "b5c6"), - "C69" -> Ecopening("C69", "Ruy Lopez", "Ruy Lopez, Exchange, Gligoric Variation, 6.d4", "e4 e5 Nf3 Nc6 Bb5 a6 Bxc6 dc O-O f6 d4", "r1bqkbnr/1ppp2pp/p1B2p2/4p3/3PP3/5N2/PPP2PPP/RNBQK2R", "d2d4"), - "C70" -> Ecopening("C70", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4", "r1bqkbnr/1ppp1ppp/p1n5/4p3/B3P3/5N2/PPPP1PPP/RNBQK2R", "b5a4"), - "C71" -> Ecopening("C71", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6", "r1bqkbnr/1pp2ppp/p1np4/4p3/B3P3/5N2/PPPP1PPP/RNBQK2R", "d7d6"), - "C72" -> Ecopening("C72", "Ruy Lopez", "Ruy Lopez, Modern Steinitz Defence, 5.O-O", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 O-O", "r1bqkbnr/1pp2ppp/p1np4/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1", "e1g1"), - "C73" -> Ecopening("C73", "Ruy Lopez", "Ruy Lopez, Modern Steinitz Defence", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 Bxc6+ bxc6 d4", "r1bqkbnr/2p2ppp/p1pp4/4p3/3PP3/5N2/PPP2PPP/RNBQK2R", "d2d4"), - "C74" -> Ecopening("C74", "Ruy Lopez", "Ruy Lopez, Modern Steinitz Defence", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 c3", "r1bqkbnr/1pp2ppp/p1np4/4p3/B3P3/2P2N2/PP1P1PPP/RNBQK2R", "c2c3"), - "C75" -> Ecopening("C75", "Ruy Lopez", "Ruy Lopez, Modern Steinitz Defence", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 c3 Bd7", "r2qkbnr/1ppb1ppp/p1np4/4p3/B3P3/2P2N2/PP1P1PPP/RNBQK2R", "c8d7"), - "C76" -> Ecopening("C76", "Ruy Lopez", "Ruy Lopez, Modern Steinitz Defence, Fianchetto Variation", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 d6 c3 Bd7 d4 g6", "r2qkbnr/1ppb1p1p/p1np2p1/4p3/B2PP3/2P2N2/PP3PPP/RNBQK2R", "g7g6"), - "C77" -> Ecopening("C77", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6", "r1bqkb1r/1ppp1ppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQK2R", "g8f6"), - "C78" -> Ecopening("C78", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O", "r1bqkb1r/1ppp1ppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1", "e1g1"), - "C79" -> Ecopening("C79", "Ruy Lopez", "Ruy Lopez, Steinitz Defence Deferred", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O d6", "r1bqkb1r/1pp2ppp/p1np1n2/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1", "d7d6"), - "C80" -> Ecopening("C80", "Ruy Lopez", "Ruy Lopez, Open", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Nxe4", "r1bqkb1r/1ppp1ppp/p1n5/4p3/B3n3/5N2/PPPP1PPP/RNBQ1RK1", "f6e4"), - "C81" -> Ecopening("C81", "Ruy Lopez", "Ruy Lopez, Open, Howell Attack", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Nxe4 d4 b5 Bb3 d5 dxe5 Be6", "r2qkb1r/2p2ppp/p1n1b3/1p1pP3/4n3/1B3N2/PPP2PPP/RNBQ1RK1", "c8e6"), - "C82" -> Ecopening("C82", "Ruy Lopez", "Ruy Lopez, Open", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Nxe4 d4 b5 Bb3 d5 dxe5 Be6 c3", "r2qkb1r/2p2ppp/p1n1b3/1p1pP3/4n3/1BP2N2/PP3PPP/RNBQ1RK1", "c2c3"), - "C83" -> Ecopening("C83", "Ruy Lopez", "Ruy Lopez, Open", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Nxe4 d4 b5 Bb3 d5 dxe5 Be6", "r2qkb1r/2p2ppp/p1n1b3/1p1pP3/4n3/1B3N2/PPP2PPP/RNBQ1RK1", "c8e6"), - "C84" -> Ecopening("C84", "Ruy Lopez", "Ruy Lopez, Closed", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7", "r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1", "f8e7"), - "C85" -> Ecopening("C85", "Ruy Lopez", "Ruy Lopez, Exchange Variation Doubly Deferred (DERLD)", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Bxc6 dxc6", "r1bqk2r/1pp1bppp/p1p2n2/4p3/4P3/5N2/PPPP1PPP/RNBQ1RK1", "d7c6"), - "C86" -> Ecopening("C86", "Ruy Lopez", "Ruy Lopez, Worrall Attack", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Qe2", "r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPPQPPP/RNB2RK1", "d1e2"), - "C87" -> Ecopening("C87", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 d6", "r1bqk2r/1pp1bppp/p1np1n2/4p3/B3P3/5N2/PPPP1PPP/RNBQR1K1", "d7d6"), - "C88" -> Ecopening("C88", "Ruy Lopez", "Ruy Lopez", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3", "r1bqk2r/2ppbppp/p1n2n2/1p2p3/4P3/1B3N2/PPPP1PPP/RNBQR1K1", "a4b3"), - "C89" -> Ecopening("C89", "Ruy Lopez", "Ruy Lopez, Marshall", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d5", "r1bq1rk1/2p1bppp/p1n2n2/1p1pp3/4P3/1BP2N2/PP1P1PPP/RNBQR1K1", "d7d5"), - "C90" -> Ecopening("C90", "Ruy Lopez", "Ruy Lopez, Closed", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6", "r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BP2N2/PP1P1PPP/RNBQR1K1", "d7d6"), - "C91" -> Ecopening("C91", "Ruy Lopez", "Ruy Lopez, Closed", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 d4", "r1bq1rk1/2p1bppp/p1np1n2/1p2p3/3PP3/1BP2N2/PP3PPP/RNBQR1K1", "d2d4"), - "C92" -> Ecopening("C92", "Ruy Lopez", "Ruy Lopez, Closed", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3", "r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BP2N1P/PP1P1PP1/RNBQR1K1", "h2h3"), - "C93" -> Ecopening("C93", "Ruy Lopez", "Ruy Lopez, Closed, Smyslov Defence", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 h6", "r1bq1rk1/2p1bpp1/p1np1n1p/1p2p3/4P3/1BP2N1P/PP1P1PP1/RNBQR1K1", "h7h6"), - "C94" -> Ecopening("C94", "Ruy Lopez", "Ruy Lopez, Closed, Breyer Defence", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Nb8", "rnbq1rk1/2p1bppp/p2p1n2/1p2p3/4P3/1BP2N1P/PP1P1PP1/RNBQR1K1", "c6b8"), - "C95" -> Ecopening("C95", "Ruy Lopez", "Ruy Lopez, Closed, Breyer", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Nb8 d4", "rnbq1rk1/2p1bppp/p2p1n2/1p2p3/3PP3/1BP2N1P/PP3PP1/RNBQR1K1", "d2d4"), - "C96" -> Ecopening("C96", "Ruy Lopez", "Ruy Lopez, Closed", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Na5 Bc2", "r1bq1rk1/2p1bppp/p2p1n2/np2p3/4P3/2P2N1P/PPBP1PP1/RNBQR1K1", "b3c2"), - "C97" -> Ecopening("C97", "Ruy Lopez", "Ruy Lopez, Closed, Chigorin", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Na5 Bc2 c5 d4 Qc7", "r1b2rk1/2q1bppp/p2p1n2/npp1p3/3PP3/2P2N1P/PPB2PP1/RNBQR1K1", "d8c7"), - "C98" -> Ecopening("C98", "Ruy Lopez", "Ruy Lopez, Closed, Chigorin", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Na5 Bc2 c5 d4 Qc7 Nbd2 Nc6", "r1b2rk1/2q1bppp/p1np1n2/1pp1p3/3PP3/2P2N1P/PPBN1PP1/R1BQR1K1", "a5c6"), - "C99" -> Ecopening("C99", "Ruy Lopez", "Ruy Lopez, Closed, Chigorin, 12...cd", "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1 b5 Bb3 O-O c3 d6 h3 Na5 Bc2 c5 d4 Qc7 Nbd2 cxd4 cxd4", "r1b2rk1/2q1bppp/p2p1n2/np2p3/3PP3/5N1P/PPBN1PP1/R1BQR1K1", "c3d4"), - "D00" -> Ecopening("D00", "Queen's Pawn Game", "Queen's Pawn Game", "d4 d5", "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR", "d7d5"), - "D01" -> Ecopening("D01", "Richter-Veresov Attack", "Richter-Veresov Attack", "d4 d5 Nc3 Nf6 Bg5", "rnbqkb1r/ppp1pppp/5n2/3p2B1/3P4/2N5/PPP1PPPP/R2QKBNR", "c1g5"), - "D02" -> Ecopening("D02", "Queen's Pawn Game", "Queen's Pawn Game", "d4 d5 Nf3", "rnbqkbnr/ppp1pppp/8/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R", "g1f3"), - "D03" -> Ecopening("D03", "Torre Attack", "Torre Attack (Tartakower Variation)", "d4 d5 Nf3 Nf6 Bg5", "rnbqkb1r/ppp1pppp/5n2/3p2B1/3P4/5N2/PPP1PPPP/RN1QKB1R", "c1g5"), - "D04" -> Ecopening("D04", "Queen's Pawn Game", "Queen's Pawn Game", "d4 d5 Nf3 Nf6 e3", "rnbqkb1r/ppp1pppp/5n2/3p4/3P4/4PN2/PPP2PPP/RNBQKB1R", "e2e3"), - "D05" -> Ecopening("D05", "Queen's Pawn Game", "Queen's Pawn Game", "d4 d5 Nf3 Nf6 e3 e6", "rnbqkb1r/ppp2ppp/4pn2/3p4/3P4/4PN2/PPP2PPP/RNBQKB1R", "e7e6"), - "D06" -> Ecopening("D06", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4", "rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR", "c2c4"), - "D07" -> Ecopening("D07", "Queen's Gambit Declined", "Queen's Gambit Declined, Chigorin Defence", "d4 d5 c4 Nc6", "r1bqkbnr/ppp1pppp/2n5/3p4/2PP4/8/PP2PPPP/RNBQKBNR", "b8c6"), - "D08" -> Ecopening("D08", "Queen's Gambit Declined", "Queen's Gambit Declined, Albin Counter Gambit", "d4 d5 c4 e5", "rnbqkbnr/ppp2ppp/8/3pp3/2PP4/8/PP2PPPP/RNBQKBNR", "e7e5"), - "D09" -> Ecopening("D09", "Queen's Gambit Declined", "Queen's Gambit Declined, Albin Counter Gambit, 5.g3", "d4 d5 c4 e5 dxe5 d4 Nf3 Nc6 g3", "r1bqkbnr/ppp2ppp/2n5/4P3/2Pp4/5NP1/PP2PP1P/RNBQKB1R", "g2g3"), - "D10" -> Ecopening("D10", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6", "rnbqkbnr/pp2pppp/2p5/3p4/2PP4/8/PP2PPPP/RNBQKBNR", "c7c6"), - "D11" -> Ecopening("D11", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6 Nf3", "rnbqkbnr/pp2pppp/2p5/3p4/2PP4/5N2/PP2PPPP/RNBQKB1R", "g1f3"), - "D12" -> Ecopening("D12", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6 Nf3 Nf6 e3 Bf5", "rn1qkb1r/pp2pppp/2p2n2/3p1b2/2PP4/4PN2/PP3PPP/RNBQKB1R", "c8f5"), - "D13" -> Ecopening("D13", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav, Exchange Variation", "d4 d5 c4 c6 Nf3 Nf6 cxd5 cxd5", "rnbqkb1r/pp2pppp/5n2/3p4/3P4/5N2/PP2PPPP/RNBQKB1R", "c6d5"), - "D14" -> Ecopening("D14", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav, Exchange Variation", "d4 d5 c4 c6 Nf3 Nf6 cxd5 cxd5 Nc3 Nc6 Bf4 Bf5", "r2qkb1r/pp2pppp/2n2n2/3p1b2/3P1B2/2N2N2/PP2PPPP/R2QKB1R", "c8f5"), - "D15" -> Ecopening("D15", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6 Nf3 Nf6 Nc3", "rnbqkb1r/pp2pppp/2p2n2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "b1c3"), - "D16" -> Ecopening("D16", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6 Nf3 Nf6 Nc3 dxc4 a4", "rnbqkb1r/pp2pppp/2p2n2/8/P1pP4/2N2N2/1P2PPPP/R1BQKB1R", "a2a4"), - "D17" -> Ecopening("D17", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav", "d4 d5 c4 c6 Nf3 Nf6 Nc3 dxc4 a4 Bf5", "rn1qkb1r/pp2pppp/2p2n2/5b2/P1pP4/2N2N2/1P2PPPP/R1BQKB1R", "c8f5"), - "D18" -> Ecopening("D18", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav, Dutch", "d4 d5 c4 c6 Nf3 Nf6 Nc3 dxc4 a4 Bf5 e3", "rn1qkb1r/pp2pppp/2p2n2/5b2/P1pP4/2N1PN2/1P3PPP/R1BQKB1R", "e2e3"), - "D19" -> Ecopening("D19", "Queen's Gambit Declined Slav", "Queen's Gambit Declined Slav, Dutch", "d4 d5 c4 c6 Nf3 Nf6 Nc3 dxc4 a4 Bf5 e3 e6 Bxc4 Bb4 O-O O-O Qe2", "rn1q1rk1/pp3ppp/2p1pn2/5b2/PbBP4/2N1PN2/1P2QPPP/R1B2RK1", "d1e2"), - "D20" -> Ecopening("D20", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4", "rnbqkbnr/ppp1pppp/8/8/2pP4/8/PP2PPPP/RNBQKBNR", "d5c4"), - "D21" -> Ecopening("D21", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3", "rnbqkbnr/ppp1pppp/8/8/2pP4/5N2/PP2PPPP/RNBQKB1R", "g1f3"), - "D22" -> Ecopening("D22", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3 a6 e3 Bg4 Bxc4 e6 d5", "rn1qkbnr/1pp2ppp/p3p3/3P4/2B3b1/4PN2/PP3PPP/RNBQK2R", "d4d5"), - "D23" -> Ecopening("D23", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3 Nf6", "rnbqkb1r/ppp1pppp/5n2/8/2pP4/5N2/PP2PPPP/RNBQKB1R", "g8f6"), - "D24" -> Ecopening("D24", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3 Nf6 Nc3", "rnbqkb1r/ppp1pppp/5n2/8/2pP4/2N2N2/PP2PPPP/R1BQKB1R", "b1c3"), - "D25" -> Ecopening("D25", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3 Nf6 e3", "rnbqkb1r/ppp1pppp/5n2/8/2pP4/4PN2/PP3PPP/RNBQKB1R", "e2e3"), - "D26" -> Ecopening("D26", "Queen's Gambit Accepted", "Queen's Gambit Accepted", "d4 d5 c4 dxc4 Nf3 Nf6 e3 e6", "rnbqkb1r/ppp2ppp/4pn2/8/2pP4/4PN2/PP3PPP/RNBQKB1R", "e7e6"), - "D27" -> Ecopening("D27", "Queen's Gambit Accepted", "Queen's Gambit Accepted, Classical", "d4 d5 c4 dxc4 Nf3 Nf6 e3 e6 Bxc4 c5 O-O a6", "rnbqkb1r/1p3ppp/p3pn2/2p5/2BP4/4PN2/PP3PPP/RNBQ1RK1", "a7a6"), - "D28" -> Ecopening("D28", "Queen's Gambit Accepted", "Queen's Gambit Accepted, Classical", "d4 d5 c4 dxc4 Nf3 Nf6 e3 e6 Bxc4 c5 O-O a6 Qe2", "rnbqkb1r/1p3ppp/p3pn2/2p5/2BP4/4PN2/PP2QPPP/RNB2RK1", "d1e2"), - "D29" -> Ecopening("D29", "Queen's Gambit Accepted", "Queen's Gambit Accepted, Classical", "d4 d5 c4 dxc4 Nf3 Nf6 e3 e6 Bxc4 c5 O-O a6 Qe2 b5 Bb3 Bb7", "rn1qkb1r/1b3ppp/p3pn2/1pp5/3P4/1B2PN2/PP2QPPP/RNB2RK1", "c8b7"), - "D30" -> Ecopening("D30", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6", "rnbqkbnr/ppp2ppp/4p3/3p4/2PP4/8/PP2PPPP/RNBQKBNR", "e7e6"), - "D31" -> Ecopening("D31", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3", "rnbqkbnr/ppp2ppp/4p3/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR", "b1c3"), - "D32" -> Ecopening("D32", "Queen's Gambit Declined", "Queen's Gambit Declined, Tarrasch", "d4 d5 c4 e6 Nc3 c5", "rnbqkbnr/pp3ppp/4p3/2pp4/2PP4/2N5/PP2PPPP/R1BQKBNR", "c7c5"), - "D33" -> Ecopening("D33", "Queen's Gambit Declined", "Queen's Gambit Declined, Tarrasch", "d4 d5 c4 e6 Nc3 c5 cxd5 exd5 Nf3 Nc6 g3", "r1bqkbnr/pp3ppp/2n5/2pp4/3P4/2N2NP1/PP2PP1P/R1BQKB1R", "g2g3"), - "D34" -> Ecopening("D34", "Queen's Gambit Declined", "Queen's Gambit Declined, Tarrasch", "d4 d5 c4 e6 Nc3 c5 cxd5 exd5 Nf3 Nc6 g3 Nf6 Bg2 Be7", "r1bqk2r/pp2bppp/2n2n2/2pp4/3P4/2N2NP1/PP2PPBP/R1BQK2R", "f8e7"), - "D35" -> Ecopening("D35", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6", "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR", "g8f6"), - "D36" -> Ecopening("D36", "Queen's Gambit Declined", "Queen's Gambit Declined, Exchange, Positional line, 6.Qc2", "d4 d5 c4 e6 Nc3 Nf6 cxd5 exd5 Bg5 c6 Qc2", "rnbqkb1r/pp3ppp/2p2n2/3p2B1/3P4/2N5/PPQ1PPPP/R3KBNR", "d1c2"), - "D37" -> Ecopening("D37", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Nf3", "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "g1f3"), - "D38" -> Ecopening("D38", "Queen's Gambit Declined", "Queen's Gambit Declined, Ragozin Variation", "d4 d5 c4 e6 Nc3 Nf6 Nf3 Bb4", "rnbqk2r/ppp2ppp/4pn2/3p4/1bPP4/2N2N2/PP2PPPP/R1BQKB1R", "f8b4"), - "D39" -> Ecopening("D39", "Queen's Gambit Declined", "Queen's Gambit Declined, Ragozin, Vienna Variation", "d4 d5 c4 e6 Nc3 Nf6 Nf3 Bb4 Bg5 dxc4", "rnbqk2r/ppp2ppp/4pn2/6B1/1bpP4/2N2N2/PP2PPPP/R2QKB1R", "d5c4"), - "D40" -> Ecopening("D40", "Queen's Gambit Declined", "Queen's Gambit Declined, Semi-Tarrasch", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c5", "rnbqkb1r/pp3ppp/4pn2/2pp4/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "c7c5"), - "D41" -> Ecopening("D41", "Queen's Gambit Declined", "Queen's Gambit Declined, Semi-Tarrasch", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c5 cxd5", "rnbqkb1r/pp3ppp/4pn2/2pP4/3P4/2N2N2/PP2PPPP/R1BQKB1R", "c4d5"), - "D42" -> Ecopening("D42", "Queen's Gambit Declined", "Queen's Gambit Declined, Semi-Tarrasch, 7.Bd3", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c5 cxd5 Nxd5 e3 Nc6 Bd3", "r1bqkb1r/pp3ppp/2n1p3/2pn4/3P4/2NBPN2/PP3PPP/R1BQK2R", "f1d3"), - "D43" -> Ecopening("D43", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6", "rnbqkb1r/pp3ppp/2p1pn2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "c7c6"), - "D44" -> Ecopening("D44", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 Bg5 dxc4", "rnbqkb1r/pp3ppp/2p1pn2/6B1/2pP4/2N2N2/PP2PPPP/R2QKB1R", "d5c4"), - "D45" -> Ecopening("D45", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 e3", "rnbqkb1r/pp3ppp/2p1pn2/3p4/2PP4/2N1PN2/PP3PPP/R1BQKB1R", "e2e3"), - "D46" -> Ecopening("D46", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 e3 Nbd7 Bd3", "r1bqkb1r/pp1n1ppp/2p1pn2/3p4/2PP4/2NBPN2/PP3PPP/R1BQK2R", "f1d3"), - "D47" -> Ecopening("D47", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 e3 Nbd7 Bd3 dxc4 Bxc4", "r1bqkb1r/pp1n1ppp/2p1pn2/8/2BP4/2N1PN2/PP3PPP/R1BQK2R", "d3c4"), - "D48" -> Ecopening("D48", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav, Meran", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 e3 Nbd7 Bd3 dxc4 Bxc4 b5 Bd3 a6", "r1bqkb1r/3n1ppp/p1p1pn2/1p6/3P4/2NBPN2/PP3PPP/R1BQK2R", "a7a6"), - "D49" -> Ecopening("D49", "Queen's Gambit Declined Semi-Slav", "Queen's Gambit Declined Semi-Slav, Meran", "d4 d5 c4 e6 Nc3 Nf6 Nf3 c6 e3 Nbd7 Bd3 dxc4 Bxc4 b5 Bd3 a6 e4 c5 e5 cxd4 Nxb5", "r1bqkb1r/3n1ppp/p3pn2/1N2P3/3p4/3B1N2/PP3PPP/R1BQK2R", "c3b5"), - "D50" -> Ecopening("D50", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5", "rnbqkb1r/ppp2ppp/4pn2/3p2B1/2PP4/2N5/PP2PPPP/R2QKBNR", "c1g5"), - "D51" -> Ecopening("D51", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Nbd7", "r1bqkb1r/pppn1ppp/4pn2/3p2B1/2PP4/2N5/PP2PPPP/R2QKBNR", "b8d7"), - "D52" -> Ecopening("D52", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Nbd7 e3 c6 Nf3", "r1bqkb1r/pp1n1ppp/2p1pn2/3p2B1/2PP4/2N1PN2/PP3PPP/R2QKB1R", "g1f3"), - "D53" -> Ecopening("D53", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7", "rnbqk2r/ppp1bppp/4pn2/3p2B1/2PP4/2N5/PP2PPPP/R2QKBNR", "f8e7"), - "D54" -> Ecopening("D54", "Queen's Gambit Declined", "Queen's Gambit Declined, Anti-Neo-Orthodox Variation", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Rc1", "rnbq1rk1/ppp1bppp/4pn2/3p2B1/2PP4/2N1P3/PP3PPP/2RQKBNR", "a1c1"), - "D55" -> Ecopening("D55", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3", "rnbq1rk1/ppp1bppp/4pn2/3p2B1/2PP4/2N1PN2/PP3PPP/R2QKB1R", "g1f3"), - "D56" -> Ecopening("D56", "Queen's Gambit Declined", "Queen's Gambit Declined", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 h6 Bh4", "rnbq1rk1/ppp1bpp1/4pn1p/3p4/2PP3B/2N1PN2/PP3PPP/R2QKB1R", "g5h4"), - "D57" -> Ecopening("D57", "Queen's Gambit Declined", "Queen's Gambit Declined, Lasker Defence", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 h6 Bh4 Ne4 Bxe7 Qxe7", "rnb2rk1/ppp1qpp1/4p2p/3p4/2PPn3/2N1PN2/PP3PPP/R2QKB1R", "d8e7"), - "D58" -> Ecopening("D58", "Queen's Gambit Declined", "Queen's Gambit Declined, Tartakower (Makagonov-Bondarevsky) Syst", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 h6 Bh4 b6", "rnbq1rk1/p1p1bpp1/1p2pn1p/3p4/2PP3B/2N1PN2/PP3PPP/R2QKB1R", "b7b6"), - "D59" -> Ecopening("D59", "Queen's Gambit Declined", "Queen's Gambit Declined, Tartakower", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 h6 Bh4 b6 cxd5 Nxd5", "rnbq1rk1/p1p1bpp1/1p2p2p/3n4/3P3B/2N1PN2/PP3PPP/R2QKB1R", "f6d5"), - "D60" -> Ecopening("D60", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7", "r1bq1rk1/pppnbppp/4pn2/3p2B1/2PP4/2N1PN2/PP3PPP/R2QKB1R", "b8d7"), - "D61" -> Ecopening("D61", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox, Rubinstein Attack", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Qc2", "r1bq1rk1/pppnbppp/4pn2/3p2B1/2PP4/2N1PN2/PPQ2PPP/R3KB1R", "d1c2"), - "D62" -> Ecopening("D62", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox, Rubinstein Attack", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Qc2 c5 cxd5", "r1bq1rk1/pp1nbppp/4pn2/2pP2B1/3P4/2N1PN2/PPQ2PPP/R3KB1R", "c4d5"), - "D63" -> Ecopening("D63", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1", "r1bq1rk1/pppnbppp/4pn2/3p2B1/2PP4/2N1PN2/PP3PPP/2RQKB1R", "a1c1"), - "D64" -> Ecopening("D64", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox, Rubinstein Attack", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Qc2", "r1bq1rk1/pp1nbppp/2p1pn2/3p2B1/2PP4/2N1PN2/PPQ2PPP/2R1KB1R", "d1c2"), - "D65" -> Ecopening("D65", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox, Rubinstein Attack, Main line", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Qc2 a6 cxd5", "r1bq1rk1/1p1nbppp/p1p1pn2/3P2B1/3P4/2N1PN2/PPQ2PPP/2R1KB1R", "c4d5"), - "D66" -> Ecopening("D66", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence, Bd3 line", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Bd3", "r1bq1rk1/pp1nbppp/2p1pn2/3p2B1/2PP4/2NBPN2/PP3PPP/2RQK2R", "f1d3"), - "D67" -> Ecopening("D67", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence, Bd3 line", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Bd3 dxc4 Bxc4 Nd5", "r1bq1rk1/pp1nbppp/2p1p3/3n2B1/2BP4/2N1PN2/PP3PPP/2RQK2R", "f6d5"), - "D68" -> Ecopening("D68", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence, Classical", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Bd3 dxc4", "r1bq1rk1/pp1nbppp/2p1pn2/6B1/2pP4/2NBPN2/PP3PPP/2RQK2R", "d5c4"), - "D69" -> Ecopening("D69", "Queen's Gambit Declined", "Queen's Gambit Declined, Orthodox Defence, Classical, 13.de", "d4 d5 c4 e6 Nc3 Nf6 Bg5 Be7 e3 O-O Nf3 Nbd7 Rc1 c6 Bd3 dxc4", "r1bq1rk1/pp1nbppp/2p1pn2/6B1/2pP4/2NBPN2/PP3PPP/2RQK2R", "d5c4"), - "D70" -> Ecopening("D70", "Neo-Grunfeld Defence", "Neo-Grunfeld Defence", "d4 Nf6 c4 g6 f3 d5", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/5P2/PP2P1PP/RNBQKBNR", "d7d5"), - "D71" -> Ecopening("D71", "Neo-Grunfeld", "Neo-Grunfeld", "d4 Nf6 c4 g6 g3 d5", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/6P1/PP2PP1P/RNBQKBNR", "d7d5"), - "D72" -> Ecopening("D72", "Neo-Grunfeld", "Neo-Grunfeld, 5.cd, Main line", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 cxd5 Nxd5 e4 Nb6 Ne2", "rnbqk2r/ppp1ppbp/1n4p1/8/3PP3/6P1/PP2NPBP/RNBQK2R", "g1e2"), - "D73" -> Ecopening("D73", "Neo-Grunfeld", "Neo-Grunfeld, 5.Nf3", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3", "rnbqk2r/ppp1ppbp/5np1/3p4/2PP4/5NP1/PP2PPBP/RNBQK2R", "g1f3"), - "D74" -> Ecopening("D74", "Neo-Grunfeld", "Neo-Grunfeld, 6.cd Nxd5, 7.O-O", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O cxd5 Nxd5 O-O", "rnbq1rk1/ppp1ppbp/6p1/3n4/3P4/5NP1/PP2PPBP/RNBQ1RK1", "e1g1"), - "D75" -> Ecopening("D75", "Neo-Grunfeld", "Neo-Grunfeld, 6.cd Nxd5, 7.O-O c5, 8.dxc5", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O cxd5 Nxd5 O-O c5 dxc5", "rnbq1rk1/pp2ppbp/6p1/2Pn4/8/5NP1/PP2PPBP/RNBQ1RK1", "d4c5"), - "D76" -> Ecopening("D76", "Neo-Grunfeld", "Neo-Grunfeld, 6.cd Nxd5, 7.O-O Nb6", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O cxd5 Nxd5 O-O Nb6", "rnbq1rk1/ppp1ppbp/1n4p1/8/3P4/5NP1/PP2PPBP/RNBQ1RK1", "d5b6"), - "D77" -> Ecopening("D77", "Neo-Grunfeld", "Neo-Grunfeld, 6.O-O", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O O-O", "rnbq1rk1/ppp1ppbp/5np1/3p4/2PP4/5NP1/PP2PPBP/RNBQ1RK1", "e1g1"), - "D78" -> Ecopening("D78", "Neo-Grunfeld", "Neo-Grunfeld, 6.O-O c6", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O O-O c6", "rnbq1rk1/pp2ppbp/2p2np1/3p4/2PP4/5NP1/PP2PPBP/RNBQ1RK1", "c7c6"), - "D79" -> Ecopening("D79", "Neo-Grunfeld", "Neo-Grunfeld, 6.O-O, Main line", "d4 Nf6 c4 g6 g3 d5 Bg2 Bg7 Nf3 O-O O-O c6 cxd5 cxd5", "rnbq1rk1/pp2ppbp/5np1/3p4/3P4/5NP1/PP2PPBP/RNBQ1RK1", "c6d5"), - "D80" -> Ecopening("D80", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/2N5/PP2PPPP/R1BQKBNR", "d7d5"), - "D81" -> Ecopening("D81", "Grunfeld", "Grunfeld, Russian Variation", "d4 Nf6 c4 g6 Nc3 d5 Qb3", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/1QN5/PP2PPPP/R1B1KBNR", "d1b3"), - "D82" -> Ecopening("D82", "Grunfeld", "Grunfeld, 4.Bf4", "d4 Nf6 c4 g6 Nc3 d5 Bf4", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP1B2/2N5/PP2PPPP/R2QKBNR", "c1f4"), - "D83" -> Ecopening("D83", "Grunfeld", "Grunfeld, Grunfeld Gambit", "d4 Nf6 c4 g6 Nc3 d5 Bf4 Bg7 e3 O-O", "rnbq1rk1/ppp1ppbp/5np1/3p4/2PP1B2/2N1P3/PP3PPP/R2QKBNR", "e8g8"), - "D84" -> Ecopening("D84", "Grunfeld", "Grunfeld, Grunfeld Gambit Accepted", "d4 Nf6 c4 g6 Nc3 d5 Bf4 Bg7 e3 O-O cxd5 Nxd5 Nxd5 Qxd5 Bxc7", "rnb2rk1/ppB1ppbp/6p1/3q4/3P4/4P3/PP3PPP/R2QKBNR", "f4c7"), - "D85" -> Ecopening("D85", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5", "rnbqkb1r/ppp1pp1p/6p1/3n4/3P4/2N5/PP2PPPP/R1BQKBNR", "f6d5"), - "D86" -> Ecopening("D86", "Grunfeld", "Grunfeld, Exchange", "d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4", "rnbqk2r/ppp1ppbp/6p1/8/2BPP3/2P5/P4PPP/R1BQK1NR", "f1c4"), - "D87" -> Ecopening("D87", "Grunfeld", "Grunfeld, Exchange", "d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4 O-O Ne2 c5", "rnbq1rk1/pp2ppbp/6p1/2p5/2BPP3/2P5/P3NPPP/R1BQK2R", "c7c5"), - "D88" -> Ecopening("D88", "Grunfeld", "Grunfeld, Spassky Variation, Main line, 10...cd, 11.cd", "d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4 O-O Ne2", "rnbq1rk1/ppp1ppbp/6p1/8/2BPP3/2P5/P3NPPP/R1BQK2R", "g1e2"), - "D89" -> Ecopening("D89", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5 cxd5 Nxd5 e4 Nxc3 bxc3 Bg7 Bc4 O-O Ne2", "rnbq1rk1/ppp1ppbp/6p1/8/2BPP3/2P5/P3NPPP/R1BQK2R", "g1e2"), - "D90" -> Ecopening("D90", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5 Nf3", "rnbqkb1r/ppp1pp1p/5np1/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R", "g1f3"), - "D91" -> Ecopening("D91", "Grunfeld", "Grunfeld, 5.Bg5", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Bg5", "rnbqk2r/ppp1ppbp/5np1/3p2B1/2PP4/2N2N2/PP2PPPP/R2QKB1R", "c1g5"), - "D92" -> Ecopening("D92", "Grunfeld", "Grunfeld, 5.Bf4", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Bf4", "rnbqk2r/ppp1ppbp/5np1/3p4/2PP1B2/2N2N2/PP2PPPP/R2QKB1R", "c1f4"), - "D93" -> Ecopening("D93", "Grunfeld", "Grunfeld, with Bf4 & e3", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Bf4 O-O e3", "rnbq1rk1/ppp1ppbp/5np1/3p4/2PP1B2/2N1PN2/PP3PPP/R2QKB1R", "e2e3"), - "D94" -> Ecopening("D94", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 e3", "rnbqk2r/ppp1ppbp/5np1/3p4/2PP4/2N1PN2/PP3PPP/R1BQKB1R", "e2e3"), - "D95" -> Ecopening("D95", "Grunfeld", "Grunfeld", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 e3 O-O Qb3", "rnbq1rk1/ppp1ppbp/5np1/3p4/2PP4/1QN1PN2/PP3PPP/R1B1KB1R", "d1b3"), - "D96" -> Ecopening("D96", "Grunfeld", "Grunfeld, Russian Variation", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Qb3", "rnbqk2r/ppp1ppbp/5np1/3p4/2PP4/1QN2N2/PP2PPPP/R1B1KB1R", "d1b3"), - "D97" -> Ecopening("D97", "Grunfeld", "Grunfeld, Russian", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Qb3 dxc4 Qxc4 O-O e4", "rnbq1rk1/ppp1ppbp/5np1/8/2QPP3/2N2N2/PP3PPP/R1B1KB1R", "e2e4"), - "D98" -> Ecopening("D98", "Grunfeld", "Grunfeld, Russian", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Qb3 dxc4 Qxc4 O-O e4 Bg4", "rn1q1rk1/ppp1ppbp/5np1/8/2QPP1b1/2N2N2/PP3PPP/R1B1KB1R", "c8g4"), - "D99" -> Ecopening("D99", "Grunfeld", "Grunfeld Defence, Smyslov", "d4 Nf6 c4 g6 Nc3 d5 Nf3 Bg7 Qb3 dxc4 Qxc4 O-O e4 Bg4 Be3", "rn1q1rk1/ppp1ppbp/5np1/8/2QPP1b1/2N1BN2/PP3PPP/R3KB1R", "c1e3"), - "E00" -> Ecopening("E00", "Queen's Pawn Game", "Queen's Pawn Game", "d4 Nf6 c4 e6", "rnbqkb1r/pppp1ppp/4pn2/8/2PP4/8/PP2PPPP/RNBQKBNR", "e7e6"), - "E01" -> Ecopening("E01", "Catalan", "Catalan, Closed", "d4 Nf6 c4 e6 g3 d5 Bg2", "rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/6P1/PP2PPBP/RNBQK1NR", "f1g2"), - "E02" -> Ecopening("E02", "Catalan", "Catalan, Open, 5.Qa4", "d4 Nf6 c4 e6 g3 d5 Bg2 dxc4 Qa4+", "rnbqkb1r/ppp2ppp/4pn2/8/Q1pP4/6P1/PP2PPBP/RNB1K1NR", "d1a4"), - "E03" -> Ecopening("E03", "Catalan", "Catalan, Open", "d4 Nf6 c4 e6 g3 d5 Bg2 dxc4 Qa4+ Nbd7 Qxc4", "r1bqkb1r/pppn1ppp/4pn2/8/2QP4/6P1/PP2PPBP/RNB1K1NR", "a4c4"), - "E04" -> Ecopening("E04", "Catalan", "Catalan, Open, 5.Nf3", "d4 Nf6 c4 e6 g3 d5 Bg2 dxc4 Nf3", "rnbqkb1r/ppp2ppp/4pn2/8/2pP4/5NP1/PP2PPBP/RNBQK2R", "g1f3"), - "E05" -> Ecopening("E05", "Catalan", "Catalan, Open, Classical line", "d4 Nf6 c4 e6 g3 d5 Bg2 dxc4 Nf3 Be7", "rnbqk2r/ppp1bppp/4pn2/8/2pP4/5NP1/PP2PPBP/RNBQK2R", "f8e7"), - "E06" -> Ecopening("E06", "Catalan", "Catalan, Closed, 5.Nf3", "d4 Nf6 c4 e6 g3 d5 Bg2 Be7 Nf3", "rnbqk2r/ppp1bppp/4pn2/3p4/2PP4/5NP1/PP2PPBP/RNBQK2R", "g1f3"), - "E07" -> Ecopening("E07", "Catalan", "Catalan, Closed", "d4 Nf6 c4 e6 g3 d5 Bg2 Be7 Nf3 O-O O-O Nbd7", "r1bq1rk1/pppnbppp/4pn2/3p4/2PP4/5NP1/PP2PPBP/RNBQ1RK1", "b8d7"), - "E08" -> Ecopening("E08", "Catalan", "Catalan, Closed", "d4 Nf6 c4 e6 g3 d5 Bg2 Be7 Nf3 O-O O-O Nbd7 Qc2", "r1bq1rk1/pppnbppp/4pn2/3p4/2PP4/5NP1/PPQ1PPBP/RNB2RK1", "d1c2"), - "E09" -> Ecopening("E09", "Catalan", "Catalan, Closed", "d4 Nf6 c4 e6 g3 d5 Bg2 Be7 Nf3 O-O O-O Nbd7 Qc2 c6 Nbd2", "r1bq1rk1/pp1nbppp/2p1pn2/3p4/2PP4/5NP1/PPQNPPBP/R1B2RK1", "b1d2"), - "E10" -> Ecopening("E10", "Queen's Pawn Game", "Queen's Pawn Game", "d4 Nf6 c4 e6 Nf3", "rnbqkb1r/pppp1ppp/4pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R", "g1f3"), - "E11" -> Ecopening("E11", "Bogo-Indian Defence", "Bogo-Indian Defence", "d4 Nf6 c4 e6 Nf3 Bb4+", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/5N2/PP2PPPP/RNBQKB1R", "f8b4"), - "E12" -> Ecopening("E12", "Queen's Indian", "Queen's Indian", "d4 Nf6 c4 e6 Nf3 b6", "rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R", "b7b6"), - "E13" -> Ecopening("E13", "Queen's Indian", "Queen's Indian, 4.Nc3, Main line", "d4 Nf6 c4 e6 Nf3 b6 Nc3 Bb7 Bg5 h6 Bh4 Bb4", "rn1qk2r/pbpp1pp1/1p2pn1p/8/1bPP3B/2N2N2/PP2PPPP/R2QKB1R", "f8b4"), - "E14" -> Ecopening("E14", "Queen's Indian", "Queen's Indian", "d4 Nf6 c4 e6 Nf3 b6 e3", "rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/4PN2/PP3PPP/RNBQKB1R", "e2e3"), - "E15" -> Ecopening("E15", "Queen's Indian", "Queen's Indian", "d4 Nf6 c4 e6 Nf3 b6 g3", "rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/5NP1/PP2PP1P/RNBQKB1R", "g2g3"), - "E16" -> Ecopening("E16", "Queen's Indian", "Queen's Indian", "d4 Nf6 c4 e6 Nf3 b6 g3 Bb7 Bg2 Bb4+", "rn1qk2r/pbpp1ppp/1p2pn2/8/1bPP4/5NP1/PP2PPBP/RNBQK2R", "f8b4"), - "E17" -> Ecopening("E17", "Queen's Indian", "Queen's Indian", "d4 Nf6 c4 e6 Nf3 b6 g3 Bb7 Bg2 Be7", "rn1qk2r/pbppbppp/1p2pn2/8/2PP4/5NP1/PP2PPBP/RNBQK2R", "f8e7"), - "E18" -> Ecopening("E18", "Queen's Indian", "Queen's Indian, Old Main line, 7.Nc3", "d4 Nf6 c4 e6 Nf3 b6 g3 Bb7 Bg2 Be7 O-O O-O Nc3", "rn1q1rk1/pbppbppp/1p2pn2/8/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "b1c3"), - "E19" -> Ecopening("E19", "Queen's Indian", "Queen's Indian, Old Main line, 9.Qxc3", "d4 Nf6 c4 e6 Nf3 b6 g3 Bb7 Bg2 Be7 O-O O-O Nc3 Ne4 Qc2 Nxc3", "rn1q1rk1/pbppbppp/1p2p3/8/2PP4/2n2NP1/PPQ1PPBP/R1B2RK1", "e4c3"), - "E20" -> Ecopening("E20", "Nimzo-Indian", "Nimzo-Indian", "d4 Nf6 c4 e6 Nc3 Bb4", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N5/PP2PPPP/R1BQKBNR", "f8b4"), - "E21" -> Ecopening("E21", "Nimzo-Indian", "Nimzo-Indian, Three Knights", "d4 Nf6 c4 e6 Nc3 Bb4 Nf3", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N2N2/PP2PPPP/R1BQKB1R", "g1f3"), - "E22" -> Ecopening("E22", "Nimzo-Indian", "Nimzo-Indian, Spielmann Variation", "d4 Nf6 c4 e6 Nc3 Bb4 Qb3", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/1QN5/PP2PPPP/R1B1KBNR", "d1b3"), - "E23" -> Ecopening("E23", "Nimzo-Indian", "Nimzo-Indian, Spielmann", "d4 Nf6 c4 e6 Nc3 Bb4 Qb3 c5 dxc5 Nc6", "r1bqk2r/pp1p1ppp/2n1pn2/2P5/1bP5/1QN5/PP2PPPP/R1B1KBNR", "b8c6"), - "E24" -> Ecopening("E24", "Nimzo-Indian", "Nimzo-Indian, Samisch", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3", "rnbqk2r/pppp1ppp/4pn2/8/2PP4/P1P5/4PPPP/R1BQKBNR", "b2c3"), - "E25" -> Ecopening("E25", "Nimzo-Indian", "Nimzo-Indian, Samisch", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3 c5 f3 d5 cxd5", "rnbqk2r/pp3ppp/4pn2/2pP4/3P4/P1P2P2/4P1PP/R1BQKBNR", "c4d5"), - "E26" -> Ecopening("E26", "Nimzo-Indian", "Nimzo-Indian, Samisch", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3 c5 e3", "rnbqk2r/pp1p1ppp/4pn2/2p5/2PP4/P1P1P3/5PPP/R1BQKBNR", "e2e3"), - "E27" -> Ecopening("E27", "Nimzo-Indian", "Nimzo-Indian, Samisch Variation", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3 O-O", "rnbq1rk1/pppp1ppp/4pn2/8/2PP4/P1P5/4PPPP/R1BQKBNR", "e8g8"), - "E28" -> Ecopening("E28", "Nimzo-Indian", "Nimzo-Indian, Samisch Variation", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3 O-O e3", "rnbq1rk1/pppp1ppp/4pn2/8/2PP4/P1P1P3/5PPP/R1BQKBNR", "e2e3"), - "E29" -> Ecopening("E29", "Nimzo-Indian", "Nimzo-Indian, Samisch", "d4 Nf6 c4 e6 Nc3 Bb4 a3 Bxc3+ bxc3 O-O e3 c5 Bd3 Nc6", "r1bq1rk1/pp1p1ppp/2n1pn2/2p5/2PP4/P1PBP3/5PPP/R1BQK1NR", "b8c6"), - "E30" -> Ecopening("E30", "Nimzo-Indian", "Nimzo-Indian, Leningrad", "d4 Nf6 c4 e6 Nc3 Bb4 Bg5", "rnbqk2r/pppp1ppp/4pn2/6B1/1bPP4/2N5/PP2PPPP/R2QKBNR", "c1g5"), - "E31" -> Ecopening("E31", "Nimzo-Indian", "Nimzo-Indian, Leningrad, Main line", "d4 Nf6 c4 e6 Nc3 Bb4 Bg5 h6 Bh4 c5 d5 d6", "rnbqk2r/pp3pp1/3ppn1p/2pP4/1bP4B/2N5/PP2PPPP/R2QKBNR", "d7d6"), - "E32" -> Ecopening("E32", "Nimzo-Indian", "Nimzo-Indian, Classical", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N5/PPQ1PPPP/R1B1KBNR", "d1c2"), - "E33" -> Ecopening("E33", "Nimzo-Indian", "Nimzo-Indian, Classical", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 Nc6", "r1bqk2r/pppp1ppp/2n1pn2/8/1bPP4/2N5/PPQ1PPPP/R1B1KBNR", "b8c6"), - "E34" -> Ecopening("E34", "Nimzo-Indian", "Nimzo-Indian, Classical, Noa Variation", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 d5", "rnbqk2r/ppp2ppp/4pn2/3p4/1bPP4/2N5/PPQ1PPPP/R1B1KBNR", "d7d5"), - "E35" -> Ecopening("E35", "Nimzo-Indian", "Nimzo-Indian, Classical, Noa Variation, 5.cd ed", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 d5 cxd5 exd5", "rnbqk2r/ppp2ppp/5n2/3p4/1b1P4/2N5/PPQ1PPPP/R1B1KBNR", "e6d5"), - "E36" -> Ecopening("E36", "Nimzo-Indian", "Nimzo-Indian, Classical", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 d5 a3", "rnbqk2r/ppp2ppp/4pn2/3p4/1bPP4/P1N5/1PQ1PPPP/R1B1KBNR", "a2a3"), - "E37" -> Ecopening("E37", "Nimzo-Indian", "Nimzo-Indian, Classical", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 d5 a3 Bxc3+ Qxc3 Ne4 Qc2", "rnbqk2r/ppp2ppp/4p3/3p4/2PPn3/P7/1PQ1PPPP/R1B1KBNR", "c3c2"), - "E38" -> Ecopening("E38", "Nimzo-Indian", "Nimzo-Indian, Classical, 4...c5", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 c5", "rnbqk2r/pp1p1ppp/4pn2/2p5/1bPP4/2N5/PPQ1PPPP/R1B1KBNR", "c7c5"), - "E39" -> Ecopening("E39", "Nimzo-Indian", "Nimzo-Indian, Classical, Pirc Variation", "d4 Nf6 c4 e6 Nc3 Bb4 Qc2 c5 dxc5 O-O", "rnbq1rk1/pp1p1ppp/4pn2/2P5/1bP5/2N5/PPQ1PPPP/R1B1KBNR", "e8g8"), - "E40" -> Ecopening("E40", "Nimzo-Indian", "Nimzo-Indian, 4.e3", "d4 Nf6 c4 e6 Nc3 Bb4 e3", "rnbqk2r/pppp1ppp/4pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR", "e2e3"), - "E41" -> Ecopening("E41", "Nimzo-Indian", "Nimzo-Indian", "d4 Nf6 c4 e6 Nc3 Bb4 e3 c5", "rnbqk2r/pp1p1ppp/4pn2/2p5/1bPP4/2N1P3/PP3PPP/R1BQKBNR", "c7c5"), - "E42" -> Ecopening("E42", "Nimzo-Indian", "Nimzo-Indian, 4.e3 c5, 5.Ne2 (Rubinstein)", "d4 Nf6 c4 e6 Nc3 Bb4 e3 c5 Ne2", "rnbqk2r/pp1p1ppp/4pn2/2p5/1bPP4/2N1P3/PP2NPPP/R1BQKB1R", "g1e2"), - "E43" -> Ecopening("E43", "Nimzo-Indian", "Nimzo-Indian, Fischer Variation", "d4 Nf6 c4 e6 Nc3 Bb4 e3 b6", "rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR", "b7b6"), - "E44" -> Ecopening("E44", "Nimzo-Indian", "Nimzo-Indian, Fischer Variation, 5.Ne2", "d4 Nf6 c4 e6 Nc3 Bb4 e3 b6 Ne2", "rnbqk2r/p1pp1ppp/1p2pn2/8/1bPP4/2N1P3/PP2NPPP/R1BQKB1R", "g1e2"), - "E45" -> Ecopening("E45", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Bronstein (Byrne) Variation", "d4 Nf6 c4 e6 Nc3 Bb4 e3 b6 Ne2 Ba6", "rn1qk2r/p1pp1ppp/bp2pn2/8/1bPP4/2N1P3/PP2NPPP/R1BQKB1R", "c8a6"), - "E46" -> Ecopening("E46", "Nimzo-Indian", "Nimzo-Indian", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O", "rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/2N1P3/PP3PPP/R1BQKBNR", "e8g8"), - "E47" -> Ecopening("E47", "Nimzo-Indian", "Nimzo-Indian, 4.e3 O-O 5.Bd3", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Bd3", "rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/2NBP3/PP3PPP/R1BQK1NR", "f1d3"), - "E48" -> Ecopening("E48", "Nimzo-Indian", "Nimzo-Indian, 4.e3 O-O 5.Bd3 d5", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Bd3 d5", "rnbq1rk1/ppp2ppp/4pn2/3p4/1bPP4/2NBP3/PP3PPP/R1BQK1NR", "d7d5"), - "E49" -> Ecopening("E49", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Botvinnik System", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Bd3 d5 a3 Bxc3+ bxc3", "rnbq1rk1/ppp2ppp/4pn2/3p4/2PP4/P1PBP3/5PPP/R1BQK1NR", "b2c3"), - "E50" -> Ecopening("E50", "Nimzo-Indian", "Nimzo-Indian, 4.e3 O-O 5.Nf3, without ...d5", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3", "rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/2N1PN2/PP3PPP/R1BQKB1R", "g1f3"), - "E51" -> Ecopening("E51", "Nimzo-Indian", "Nimzo-Indian, 4.e3", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5", "rnbq1rk1/ppp2ppp/4pn2/3p4/1bPP4/2N1PN2/PP3PPP/R1BQKB1R", "d7d5"), - "E52" -> Ecopening("E52", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Main line with ...b6", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 b6", "rnbq1rk1/p1p2ppp/1p2pn2/3p4/1bPP4/2NBPN2/PP3PPP/R1BQK2R", "b7b6"), - "E53" -> Ecopening("E53", "Nimzo-Indian", "Nimzo-Indian, 4.e3", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5", "rnbq1rk1/pp3ppp/4pn2/2pp4/1bPP4/2NBPN2/PP3PPP/R1BQK2R", "c7c5"), - "E54" -> Ecopening("E54", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Gligoric System", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O dxc4 Bxc4", "rnbq1rk1/pp3ppp/4pn2/2p5/1bBP4/2N1PN2/PP3PPP/R1BQ1RK1", "d3c4"), - "E55" -> Ecopening("E55", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Gligoric System, Bronstein Variation", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O dxc4 Bxc4 Nbd7", "r1bq1rk1/pp1n1ppp/4pn2/2p5/1bBP4/2N1PN2/PP3PPP/R1BQ1RK1", "b8d7"), - "E56" -> Ecopening("E56", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Main line with 7...Nc6", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O Nc6", "r1bq1rk1/pp3ppp/2n1pn2/2pp4/1bPP4/2NBPN2/PP3PPP/R1BQ1RK1", "b8c6"), - "E57" -> Ecopening("E57", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Main line with 8...dc and 9...cd", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O Nc6 a3 dxc4 Bxc4", "r1bq1rk1/pp3ppp/2n1pn2/2p5/1bBP4/P1N1PN2/1P3PPP/R1BQ1RK1", "d3c4"), - "E58" -> Ecopening("E58", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Main line with 8...Bxc3", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O Nc6 a3 Bxc3 bxc3", "r1bq1rk1/pp3ppp/2n1pn2/2pp4/2PP4/P1PBPN2/5PPP/R1BQ1RK1", "b2c3"), - "E59" -> Ecopening("E59", "Nimzo-Indian", "Nimzo-Indian, 4.e3, Main line", "d4 Nf6 c4 e6 Nc3 Bb4 e3 O-O Nf3 d5 Bd3 c5 O-O Nc6 a3 Bxc3 bxc3 dxc4 Bxc4", "r1bq1rk1/pp3ppp/2n1pn2/2p5/2BP4/P1P1PN2/5PPP/R1BQ1RK1", "d3c4"), - "E60" -> Ecopening("E60", "King's Indian", "King's Indian Defence", "d4 Nf6 c4 g6", "rnbqkb1r/pppppp1p/5np1/8/2PP4/8/PP2PPPP/RNBQKBNR", "g7g6"), - "E61" -> Ecopening("E61", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3", "rnbqkb1r/pppppp1p/5np1/8/2PP4/2N5/PP2PPPP/R1BQKBNR", "b1c3"), - "E62" -> Ecopening("E62", "King's Indian", "King's Indian, Fianchetto", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3", "rnbqk2r/ppp1ppbp/3p1np1/8/2PP4/2N2NP1/PP2PP1P/R1BQKB1R", "g2g3"), - "E63" -> Ecopening("E63", "King's Indian", "King's Indian, Fianchetto, Panno Variation", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 Nc6 O-O a6", "r1bq1rk1/1pp1ppbp/p1np1np1/8/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "a7a6"), - "E64" -> Ecopening("E64", "King's Indian", "King's Indian, Fianchetto, Yugoslav System", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 c5", "rnbq1rk1/pp2ppbp/3p1np1/2p5/2PP4/2N2NP1/PP2PPBP/R1BQK2R", "c7c5"), - "E65" -> Ecopening("E65", "King's Indian", "King's Indian, Fianchetto, Yugoslav, 7.O-O", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 c5 O-O", "rnbq1rk1/pp2ppbp/3p1np1/2p5/2PP4/2N2NP1/PP2PPBP/R1BQ1RK1", "e1g1"), - "E66" -> Ecopening("E66", "King's Indian", "King's Indian, Fianchetto, Yugoslav Panno", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 c5 O-O Nc6 d5", "r1bq1rk1/pp2ppbp/2np1np1/2pP4/2P5/2N2NP1/PP2PPBP/R1BQ1RK1", "d4d5"), - "E67" -> Ecopening("E67", "King's Indian", "King's Indian, Fianchetto", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 Nbd7", "r1bq1rk1/pppnppbp/3p1np1/8/2PP4/2N2NP1/PP2PPBP/R1BQK2R", "b8d7"), - "E68" -> Ecopening("E68", "King's Indian", "King's Indian, Fianchetto, Classical Variation, 8.e4", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 Nbd7 O-O e5 e4", "r1bq1rk1/pppn1pbp/3p1np1/4p3/2PPP3/2N2NP1/PP3PBP/R1BQ1RK1", "e2e4"), - "E69" -> Ecopening("E69", "King's Indian", "King's Indian, Fianchetto, Classical Main line", "d4 Nf6 c4 g6 Nc3 Bg7 Nf3 d6 g3 O-O Bg2 Nbd7 O-O e5 e4 c6 h3", "r1bq1rk1/pp1n1pbp/2pp1np1/4p3/2PPP3/2N2NPP/PP3PB1/R1BQ1RK1", "h2h3"), - "E70" -> Ecopening("E70", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4", "rnbqk2r/ppppppbp/5np1/8/2PPP3/2N5/PP3PPP/R1BQKBNR", "e2e4"), - "E71" -> Ecopening("E71", "King's Indian", "King's Indian, Makagonov System (5.h3)", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 h3", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N4P/PP3PP1/R1BQKBNR", "h2h3"), - "E72" -> Ecopening("E72", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 g3", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N3P1/PP3P1P/R1BQKBNR", "g2g3"), - "E73" -> Ecopening("E73", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Be2", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N5/PP2BPPP/R1BQK1NR", "f1e2"), - "E74" -> Ecopening("E74", "King's Indian", "King's Indian, Averbakh, 6...c5", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Be2 O-O Bg5 c5", "rnbq1rk1/pp2ppbp/3p1np1/2p3B1/2PPP3/2N5/PP2BPPP/R2QK1NR", "c7c5"), - "E75" -> Ecopening("E75", "King's Indian", "King's Indian, Averbakh, Main line", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Be2 O-O Bg5 c5 d5 e6", "rnbq1rk1/pp3pbp/3ppnp1/2pP2B1/2P1P3/2N5/PP2BPPP/R2QK1NR", "e7e6"), - "E76" -> Ecopening("E76", "King's Indian", "King's Indian, Four Pawns Attack", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f4", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPPP2/2N5/PP4PP/R1BQKBNR", "f2f4"), - "E77" -> Ecopening("E77", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f4 O-O Be2", "rnbq1rk1/ppp1ppbp/3p1np1/8/2PPPP2/2N5/PP2B1PP/R1BQK1NR", "f1e2"), - "E78" -> Ecopening("E78", "King's Indian", "King's Indian, Four Pawns Attack, with Be2 and Nf3", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f4 O-O Be2 c5 Nf3", "rnbq1rk1/pp2ppbp/3p1np1/2p5/2PPPP2/2N2N2/PP2B1PP/R1BQK2R", "g1f3"), - "E79" -> Ecopening("E79", "King's Indian", "King's Indian, Four Pawns Attack, Main line", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f4 O-O Be2 c5 Nf3 cxd4 Nxd4 Nc6 Be3", "r1bq1rk1/pp2ppbp/2np1np1/8/2PNPP2/2N1B3/PP2B1PP/R2QK2R", "c1e3"), - "E80" -> Ecopening("E80", "King's Indian", "King's Indian, Samisch Variation", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N2P2/PP4PP/R1BQKBNR", "f2f3"), - "E81" -> Ecopening("E81", "King's Indian", "King's Indian, Samisch", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O", "rnbq1rk1/ppp1ppbp/3p1np1/8/2PPP3/2N2P2/PP4PP/R1BQKBNR", "e8g8"), - "E82" -> Ecopening("E82", "King's Indian", "King's Indian, Samisch, double Fianchetto Variation", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 b6", "rnbq1rk1/p1p1ppbp/1p1p1np1/8/2PPP3/2N1BP2/PP4PP/R2QKBNR", "b7b6"), - "E83" -> Ecopening("E83", "King's Indian", "King's Indian, Samisch", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 Nc6", "r1bq1rk1/ppp1ppbp/2np1np1/8/2PPP3/2N1BP2/PP4PP/R2QKBNR", "b8c6"), - "E84" -> Ecopening("E84", "King's Indian", "King's Indian, Samisch, Panno Main line", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 Nc6 Nge2 a6 Qd2 Rb8", "1rbq1rk1/1pp1ppbp/p1np1np1/8/2PPP3/2N1BP2/PP1QN1PP/R3KB1R", "a8b8"), - "E85" -> Ecopening("E85", "King's Indian", "King's Indian, Samisch, Orthodox Variation", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5", "rnbq1rk1/ppp2pbp/3p1np1/4p3/2PPP3/2N1BP2/PP4PP/R2QKBNR", "e7e5"), - "E86" -> Ecopening("E86", "King's Indian", "King's Indian, Samisch, Orthodox, 7.Nge2 c6", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5 Nge2 c6", "rnbq1rk1/pp3pbp/2pp1np1/4p3/2PPP3/2N1BP2/PP2N1PP/R2QKB1R", "c7c6"), - "E87" -> Ecopening("E87", "King's Indian", "King's Indian, Samisch, Orthodox", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5 d5", "rnbq1rk1/ppp2pbp/3p1np1/3Pp3/2P1P3/2N1BP2/PP4PP/R2QKBNR", "d4d5"), - "E88" -> Ecopening("E88", "King's Indian", "King's Indian, Samisch, Orthodox, 7.d5 c6", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5 d5 c6", "rnbq1rk1/pp3pbp/2pp1np1/3Pp3/2P1P3/2N1BP2/PP4PP/R2QKBNR", "c7c6"), - "E89" -> Ecopening("E89", "King's Indian", "King's Indian, Samisch, Orthodox Main line", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 f3 O-O Be3 e5 d5 c6 Nge2 cxd5", "rnbq1rk1/pp3pbp/3p1np1/3pp3/2P1P3/2N1BP2/PP2N1PP/R2QKB1R", "c6d5"), - "E90" -> Ecopening("E90", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3", "rnbqk2r/ppp1ppbp/3p1np1/8/2PPP3/2N2N2/PP3PPP/R1BQKB1R", "g1f3"), - "E91" -> Ecopening("E91", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2", "rnbq1rk1/ppp1ppbp/3p1np1/8/2PPP3/2N2N2/PP2BPPP/R1BQK2R", "f1e2"), - "E92" -> Ecopening("E92", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5", "rnbq1rk1/ppp2pbp/3p1np1/4p3/2PPP3/2N2N2/PP2BPPP/R1BQK2R", "e7e5"), - "E93" -> Ecopening("E93", "King's Indian", "King's Indian, Petrosian System", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 d5 Nbd7", "r1bq1rk1/pppn1pbp/3p1np1/3Pp3/2P1P3/2N2N2/PP2BPPP/R1BQK2R", "b8d7"), - "E94" -> Ecopening("E94", "King's Indian", "King's Indian, Orthodox", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O", "rnbq1rk1/ppp2pbp/3p1np1/4p3/2PPP3/2N2N2/PP2BPPP/R1BQ1RK1", "e1g1"), - "E95" -> Ecopening("E95", "King's Indian", "King's Indian, Orthodox, 7...Nbd7, 8.Re1", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nbd7 Re1", "r1bq1rk1/pppn1pbp/3p1np1/4p3/2PPP3/2N2N2/PP2BPPP/R1BQR1K1", "f1e1"), - "E96" -> Ecopening("E96", "King's Indian", "King's Indian, Orthodox, 7...Nbd7, Main line", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nbd7 Re1 c6 Bf1 a5", "r1bq1rk1/1p1n1pbp/2pp1np1/p3p3/2PPP3/2N2N2/PP3PPP/R1BQRBK1", "a7a5"), - "E97" -> Ecopening("E97", "King's Indian", "King's Indian", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nc6", "r1bq1rk1/ppp2pbp/2np1np1/4p3/2PPP3/2N2N2/PP2BPPP/R1BQ1RK1", "b8c6"), - "E98" -> Ecopening("E98", "King's Indian", "King's Indian, Orthodox, Taimanov, 9.Ne1", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nc6 d5 Ne7 Ne1", "r1bq1rk1/ppp1npbp/3p1np1/3Pp3/2P1P3/2N5/PP2BPPP/R1BQNRK1", "f3e1"), - "E99" -> Ecopening("E99", "King's Indian", "King's Indian, Orthodox, Taimanov", "d4 Nf6 c4 g6 Nc3 Bg7 e4 d6 Nf3 O-O Be2 e5 O-O Nc6 d5 Ne7 Ne1 Nd7 f3 f5", "r1bq1rk1/pppnn1bp/3p2p1/3Ppp2/2P1P3/2N2P2/PP2B1PP/R1BQNRK1", "f7f5")) -} diff --git a/modules/coach_old/src/main/Env.scala b/modules/coach_old/src/main/Env.scala deleted file mode 100644 index 12f669e700..0000000000 --- a/modules/coach_old/src/main/Env.scala +++ /dev/null @@ -1,46 +0,0 @@ -package lila.coach - -import akka.actor._ -import com.typesafe.config.Config -import scala.concurrent.duration._ - -import akka.actor._ -import lila.common.PimpedConfig._ - -final class Env( - config: Config, - getPref: String => Fu[lila.pref.Pref], - areFriends: (String, String) => Fu[Boolean], - lightUser: String => Option[lila.common.LightUser], - system: ActorSystem, - db: lila.db.Env) { - - private val settings = new { - val CollectionStat = config getString "collection.stat" - } - import settings._ - - private lazy val jsonWriters = new JSONWriters(lightUser = lightUser) - - lazy val share = new Share(getPref, areFriends) - - lazy val jsonView = new JsonView(jsonWriters) - - lazy val statApi = new StatApi( - coll = db(CollectionStat)) - - lazy val aggregator = new Aggregator( - api = statApi, - sequencer = system.actorOf(Props(classOf[lila.hub.Sequencer], None))) -} - -object Env { - - lazy val current: Env = "coach" boot new Env( - config = lila.common.PlayApp loadConfig "coach", - getPref = lila.pref.Env.current.api.getPrefById, - areFriends = lila.relation.Env.current.api.areFriends, - lightUser = lila.user.Env.current.lightUser, - system = lila.common.PlayApp.system, - db = lila.db.Env.current) -} diff --git a/modules/coach_old/src/main/GameSections.scala b/modules/coach_old/src/main/GameSections.scala deleted file mode 100644 index 5e0af66440..0000000000 --- a/modules/coach_old/src/main/GameSections.scala +++ /dev/null @@ -1,70 +0,0 @@ -package lila.coach - -case class GameSections( - all: GameSections.Section, - opening: GameSections.Section, - middle: GameSections.Section, - end: GameSections.Section) { - - private def avg(ints: Vector[Int]) = ints.nonEmpty option (ints.sum / ints.size) - private def boundedAvg(ints: Option[Vector[Int]], bounds: (Int, Int)) = ints ?? { is => - bounds match { - case (from, to) => avg(is drop from take (to - from)) - } - } - - def aggregate(p: RichPov) = { - val moveTimes = p.pov.game.hasClock option p.pov.game.moveTimes(p.pov.color).toVector - copy( - all = all.add( - m = p.division.plies / 2, - a = p.accuracy.map(_.all), - t = moveTimes.flatMap(avg)), - opening = opening.add( - m = p.division.openingSize / 2, - a = p.accuracy.map(_.opening), - t = p.division.openingBounds.flatMap { boundedAvg(moveTimes, _) }), - middle = middle.add( - m = ~p.division.middleSize / 2, - a = p.accuracy.flatMap(_.middle), - t = p.division.middleBounds.flatMap { boundedAvg(moveTimes, _) }), - end = end.add( - m = ~p.division.endSize / 2, - a = p.accuracy.flatMap(_.end), - t = p.division.endBounds.flatMap { boundedAvg(moveTimes, _) }) - ) - } - - def merge(s: GameSections) = GameSections( - all = all merge s.all, - opening = opening merge s.opening, - middle = middle merge s.middle, - end = end merge s.end) -} - -object GameSections { - - case class Section( - nb: Int, - moves: NbSum, - acpl: NbSum, - time: NbSum) { - - def merge(m: Section) = Section( - nb = nb + m.nb, - moves = moves merge m.moves, - acpl = acpl merge m.acpl, - time = time merge m.time) - - def add(m: Int, a: Option[Int], t: Option[Int]) = copy( - nb = nb + 1, - moves = moves add m, - acpl = a.fold(acpl)(acpl.add), - time = t.fold(time)(time.add)) - } - object Section { - val empty = Section(0, NbSum.empty, NbSum.empty, NbSum.empty) - } - - val empty = GameSections(Section.empty, Section.empty, Section.empty, Section.empty) -} diff --git a/modules/coach_old/src/main/JSONWriters.scala b/modules/coach_old/src/main/JSONWriters.scala deleted file mode 100644 index fa9c83bd2b..0000000000 --- a/modules/coach_old/src/main/JSONWriters.scala +++ /dev/null @@ -1,90 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat -import play.api.libs.json._ - -private[coach] final class JSONWriters( - lightUser: String => Option[lila.common.LightUser]) { - - implicit object JodaDateWrites extends Writes[DateTime] { - private val isoFormatter = ISODateTimeFormat.dateTime - def writes(d: DateTime): JsValue = JsString(isoFormatter print d) - } - - implicit val NbSumWriter = OWrites[NbSum] { s => - Json.obj("nb" -> s.nb, "avg" -> s.avg) - } - - private val AutoEcopeningWriter = Json.writes[Ecopening] - implicit val EcopeningWriter = OWrites[Ecopening] { o => - AutoEcopeningWriter.writes(o) + ("formattedMoves" -> JsString(o.formattedMoves)) - } - implicit val SectionWriter = Json.writes[GameSections.Section] - implicit val GameSectionsWriter = Json.writes[GameSections] - implicit val BestWinWriter = OWrites[Results.BestWin] { o => - Json.obj( - "id" -> o.id, - "rating" -> o.rating, - "user" -> lightUser(o.userId).map { u => - Json.obj( - "id" -> u.id, - "name" -> u.name, - "title" -> u.title) - }) - } - implicit val MoveWriter = Json.writes[Move] - implicit val TrimmedMovesWriter = Writes[TrimmedMoves] { o => - Json.toJson(o.moves) - } - implicit val ColorMovesWriter = Json.writes[ColorMoves] - implicit val ResultsWriter = OWrites[Results] { o => - Json.obj( - "nbGames" -> o.nbGames, - "nbAnalysis" -> o.nbAnalysis, - "nbWin" -> o.nbWin, - "nbLoss" -> o.nbLoss, - "nbDraw" -> o.nbDraw, - "ratingDiff" -> o.ratingDiff, - "gameSections" -> o.gameSections, - "bestWin" -> o.bestWin, - "opponentRatingAvg" -> o.opponentRatingAvg, - "lastPlayed" -> o.lastPlayed) - } - implicit val ColorResultsWriter = Json.writes[ColorResults] - implicit val OpeningsMapWriter = Writes[Openings.OpeningsMap] { o => - Json.obj( - "map" -> Json.toJson(o.trim.m), - "results" -> o.results - ) - } - implicit val PerfTypeWriter = Writes[lila.rating.PerfType] { o => - Json.obj( - "key" -> o.key, - "name" -> o.name, - "icon" -> o.iconChar.toString) - } - implicit val OpeningsWriter = Json.writes[Openings] - implicit val PerfResultsBestRatingWriter = Json.writes[PerfResults.BestRating] - implicit val PerfResultsStatusMapWriter = OWrites[Map[chess.Status, Int]] { m => - JsObject(m.map { case (status, i) => status.name -> JsNumber(i) }) - } - implicit val PerfResultsStatusScoresWriter = Json.writes[PerfResults.StatusScores] - implicit val PerfResultsOutcomeStatusesWriter = Json.writes[PerfResults.OutcomeStatuses] - implicit val PerfResultsWriter = Json.writes[PerfResults] - implicit val PerfResultsPerfMapWriter = OWrites[Map[lila.rating.PerfType, PerfResults]] { m => - JsObject(m.map { case (pt, res) => pt.key -> PerfResultsWriter.writes(res) }) - } - implicit val PerfResultsMapWriter = Json.writes[PerfResults.PerfResultsMap] - implicit val UserStatWriter = Json.writes[UserStat] - - implicit val OpeningWriter: OWrites[chess.Opening] = OWrites { o => - Json.obj( - "code" -> o.code, - "name" -> o.name, - "moves" -> o.moves - ) - } - - implicit val EcopeningFamilyWriter = Json.writes[Ecopening.Family] -} diff --git a/modules/coach_old/src/main/JsonView.scala b/modules/coach_old/src/main/JsonView.scala deleted file mode 100644 index e30705f6f7..0000000000 --- a/modules/coach_old/src/main/JsonView.scala +++ /dev/null @@ -1,56 +0,0 @@ -package lila.coach - -import play.api.libs.json._ - -final class JsonView(jsonWriters: JSONWriters) { - - import jsonWriters._ - - def opening(period: Period, color: chess.Color): Fu[JsObject] = fuccess { - val stat = period.data - val openings = stat.openings(color).trim - Json.obj( - "from" -> period.from, - "to" -> period.to, - "color" -> color.name, - "results" -> stat.results.base, - "colorResults" -> stat.colorResults(color), - "openings" -> openings.m.map { - case (eco, results) => eco -> Json.obj( - "opening" -> EcopeningDB.allByEco.get(eco), - "results" -> results) - }, - "openingResults" -> openings.results, - "families" -> Ecopening.makeFamilies { - openings.m.keys.flatMap(EcopeningDB.allByEco.get) - }.values.toList.sortBy(-_.ecos.size).map { fam => - Json.obj( - "family" -> fam, - "results" -> fam.ecos.flatMap(openings.m.get).foldLeft(Results.empty) { - (res, oRes) => res merge oRes - }) - } - ) - } - - def move(period: Period): Fu[JsObject] = fuccess { - val stat = period.data - Json.obj( - "from" -> period.from, - "to" -> period.to, - "perfs" -> (Json.obj( - "perf" -> Json.obj( - "key" -> "global", - "name" -> "Global", - "icon" -> "C"), - "results" -> stat.results - ) :: lila.rating.PerfType.nonPuzzle.flatMap { pt => - stat.perfResults.m.get(pt) map { results => - Json.obj( - "perf" -> pt, - "results" -> results) - } - }) - ) - } -} diff --git a/modules/coach_old/src/main/Moves.scala b/modules/coach_old/src/main/Moves.scala deleted file mode 100644 index 4608380a93..0000000000 --- a/modules/coach_old/src/main/Moves.scala +++ /dev/null @@ -1,73 +0,0 @@ -package lila.coach - -case class Move(nb: Int, acpl: NbSum, time: NbSum) { - - def merge(m: Move) = Move( - nb = nb + m.nb, - acpl = acpl merge m.acpl, - time = time merge m.time) - - def add(a: Option[Int], t: Option[Int]) = copy( - nb = nb + 1, - acpl = a.fold(acpl)(acpl.add), - time = t.fold(time)(time.add)) -} -object Move { - val empty = Move(0, NbSum.empty, NbSum.empty) -} - -case class TrimmedMoves(moves: Vector[Move]) { - - def fill = FilledMoves { - moves ++ Vector.fill(Moves.SIZE - moves.size)(Move.empty) - } - - def aggregate(p: RichPov) = fill.aggregate(p).trim - - def merge(o: TrimmedMoves) = fill.merge(o.fill).trim -} - -case class FilledMoves(moves: Vector[Move]) { - - def trim = TrimmedMoves { - moves.takeWhile(_.nb > 0) - } - - def aggregate(p: RichPov) = FilledMoves { - val moveTimes = p.pov.game.hasClock option p.pov.game.moveTimes(p.pov.color).toVector - val accuracy = p.moveAccuracy.map(_.toVector) - - (0 to (Moves.SIZE - 1).min(p.pov.game.playerMoves(p.pov.color) - 1)).foldLeft(moves) { - case (moves, index) => moves.updated( - index, - moves(index).add( - accuracy.flatMap(_ lift index), - if (index == 0) Some(0) else moveTimes.flatMap(_ lift index))) - } - } - - // TODO keep longest size - def merge(o: FilledMoves) = FilledMoves { - moves zip o.moves map { case (a, b) => a merge b } - } -} -object Moves { - val SIZE = 60 - val empty = TrimmedMoves(Vector.empty) -} - -case class ColorMoves(white: TrimmedMoves, black: TrimmedMoves) { - - def aggregate(p: RichPov) = copy( - white = if (p.pov.color.white) white aggregate p else white, - black = if (p.pov.color.black) black aggregate p else black) - - def merge(o: ColorMoves) = ColorMoves( - white = white merge o.white, - black = black merge o.black) -} - -object ColorMoves { - - val empty = ColorMoves(Moves.empty, Moves.empty) -} diff --git a/modules/coach_old/src/main/Openings.scala b/modules/coach_old/src/main/Openings.scala deleted file mode 100644 index c7f0b754ce..0000000000 --- a/modules/coach_old/src/main/Openings.scala +++ /dev/null @@ -1,57 +0,0 @@ -package lila.coach - -import chess.Color -import lila.analyse.Analysis -import lila.game.Pov - -case class Openings( - white: Openings.OpeningsMap, - black: Openings.OpeningsMap) { - - def apply(c: Color) = c.fold(white, black) - - def merge(o: Openings) = Openings( - white = white merge o.white, - black = black merge o.black) -} - -object Openings { - - val MAP_SIZE = 15 - - case class OpeningsMap(m: Map[String, Results]) { - def best: List[(String, Results)] = m.toList.sortBy(-_._2.nbGames) take MAP_SIZE - def trim = OpeningsMap(m = best.toMap) - def merge(o: OpeningsMap) = OpeningsMap { - m.map { - case (k, v) => k -> o.m.get(k).fold(v)(v.merge) - } ++ o.m.filterKeys(k => !m.contains(k)) - } - lazy val results = m.foldLeft(Results.empty) { - case (res, (_, r)) => res merge r - } - } - val emptyOpeningsMap = OpeningsMap(Map.empty) - - val empty = Openings(emptyOpeningsMap, emptyOpeningsMap) - - case class Computation( - white: Map[String, Results.Computation], - black: Map[String, Results.Computation]) { - - def aggregate(p: RichPov) = - Ecopening.fromGame(p.pov.game).map(_.eco).fold(this) { eco => - copy( - white = if (p.pov.color.white) agg(white, eco, p) else white, - black = if (p.pov.color.black) agg(black, eco, p) else black) - } - - private def agg(ops: Map[String, Results.Computation], eco: String, p: RichPov) = - ops + (eco -> ops.get(eco).|(Results.emptyComputation).aggregate(p)) - - def run = Openings( - white = OpeningsMap(white.mapValues(_.run)).trim, - black = OpeningsMap(black.mapValues(_.run)).trim) - } - val emptyComputation = Computation(Map.empty, Map.empty) -} diff --git a/modules/coach_old/src/main/PerfResults.scala b/modules/coach_old/src/main/PerfResults.scala deleted file mode 100644 index 73a0891845..0000000000 --- a/modules/coach_old/src/main/PerfResults.scala +++ /dev/null @@ -1,97 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime - -import chess.Status -import lila.game.Pov -import lila.rating.PerfType - -case class PerfResults( - base: Results, - moves: ColorMoves, - bestRating: Option[PerfResults.BestRating], - // winStreak: PerfResults.Streak, // nb games won in a row - // awakeMinutesStreak: PerfResults.Streak, // minutes played without sleeping - // dayStreak: PerfResults.Streak, // days played in a row - outcomeStatuses: PerfResults.OutcomeStatuses) { - - def aggregate(p: RichPov) = copy( - base = base aggregate p, - moves = moves aggregate p, - bestRating = if (~p.pov.win) { - PerfResults.makeBestRating(p.pov).fold(bestRating) { newBest => - bestRating.fold(newBest) { prev => - if (newBest.rating > prev.rating) newBest else prev - }.some - } - } - else bestRating, - outcomeStatuses = outcomeStatuses.aggregate(p)) - - def merge(o: PerfResults) = PerfResults( - base = base merge o.base, - moves = moves merge o.moves, - bestRating = (bestRating, o.bestRating) match { - case (Some(a), Some(b)) => Some(a merge b) - case (a, b) => a orElse b - }, - outcomeStatuses = outcomeStatuses merge o.outcomeStatuses) -} - -object PerfResults { - - case class BestRating(id: String, userId: String, rating: Int) { - def merge(o: BestRating) = if (rating >= o.rating) this else o - } - def makeBestRating(pov: Pov): Option[BestRating] = - pov.opponent.userId |@| pov.player.ratingAfter apply { - case (opId, myRating) => BestRating(pov.gameId, opId, myRating) - } - - case class PerfResultsMap(m: Map[PerfType, PerfResults]) { - def sorted: List[(PerfType, PerfResults)] = m.toList.sortBy(-_._2.base.nbGames) - def merge(o: PerfResultsMap) = PerfResultsMap { - m.map { - case (k, v) => k -> o.m.get(k).fold(v)(v.merge) - } - } - } - val emptyPerfResultsMap = PerfResultsMap(Map.empty) - - case class StatusScores(m: Map[Status, Int]) { - def aggregate(s: Status) = copy(m = m + (s -> m.get(s).fold(1)(1+))) - def merge(o: StatusScores) = StatusScores { - m.map { - case (k, v) => k -> (v + ~o.m.get(k)) - } - } - def sorted: List[(Status, Int)] = m.toList.sortBy(-_._2) - lazy val sum: Int = m.foldLeft(0)(_ + _._2) - } - - case class OutcomeStatuses(win: StatusScores, loss: StatusScores) { - def aggregate(p: RichPov) = copy( - win = if (~p.pov.win) win aggregate p.pov.game.status else win, - loss = if (~p.pov.loss) loss aggregate p.pov.game.status else loss) - def merge(o: OutcomeStatuses) = OutcomeStatuses( - win = win merge o.win, - loss = loss merge o.win) - } - val emptyOutcomeStatuses = OutcomeStatuses(StatusScores(Map.empty), StatusScores(Map.empty)) - - val empty = PerfResults(Results.empty, ColorMoves.empty, none, emptyOutcomeStatuses) - - case class Computation( - results: PerfResults, - base: Results.Computation) { - - def aggregate(p: RichPov) = copy( - results = results.aggregate(p), - base = base.aggregate(p)) - - def nbGames = base.nbGames - - def run = results.copy(base = base.run) - } - val emptyComputation = Computation(empty, Results.emptyComputation) -} diff --git a/modules/coach_old/src/main/Period.scala b/modules/coach_old/src/main/Period.scala deleted file mode 100644 index 853bc232b4..0000000000 --- a/modules/coach_old/src/main/Period.scala +++ /dev/null @@ -1,81 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime -import scalaz.NonEmptyList - -// contains aggregated data over (up to) 100 games -case class Period( - _id: String, // random - userId: String, - data: UserStat, - from: DateTime, - to: DateTime, - computedAt: DateTime) { - - def id = _id - - def merge(o: Period) = copy( - data = data merge o.data, - from = from, - to = o.to) - - def isFresh = DateTime.now minusDays 1 isBefore computedAt - def isStale = !isFresh - - def nbGames = data.results.base.nbGames -} - -object Period { - - val MAX_GAMES = 100 - - case class Computation(period: Period, data: UserStat.Computation) { - - def aggregate(p: RichPov) = copy(data = data aggregate p) - - def run = period.copy(data = data.run) - - def nbGames = data.nbGames - } - def initComputation(userId: String, pov: RichPov) = - Computation(build(userId, pov), UserStat.emptyComputation aggregate pov) - - def build(userId: String, pov: RichPov) = Period( - _id = ornicar.scalalib.Random nextStringUppercase 8, - userId = userId, - data = UserStat.empty, - from = pov.pov.game.createdAt, - to = pov.pov.game.createdAt, - computedAt = DateTime.now) -} - -case class Periods(periods: NonEmptyList[Period]) { - - lazy val period: Period = periods.tail.foldLeft(periods.head)(_ merge _) -} - -object Periods { - - case class Computation( - userId: String, - save: Period => Funit, - period: Option[Period.Computation]) { - - def aggregate(p: RichPov): Fu[Computation] = ((period, p) match { - case (None, p) => fuccess { - Period.initComputation(userId, p) - } - case (Some(comp), p) if comp.nbGames >= Period.MAX_GAMES => save(comp.run) inject { - Period.initComputation(userId, p) - } - case (Some(comp), p) => fuccess { - comp.aggregate(p) - } - }) map { comp => - copy(period = comp.some) - } - - def run: Funit = period.filter(_.nbGames > 0) ?? { p => save(p.run) } - } - def initComputation(userId: String, save: Period => Funit) = Computation(userId, save, None) -} diff --git a/modules/coach_old/src/main/Results.scala b/modules/coach_old/src/main/Results.scala deleted file mode 100644 index d8758d6451..0000000000 --- a/modules/coach_old/src/main/Results.scala +++ /dev/null @@ -1,87 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime - -import lila.game.Pov - -case class Results( - nbGames: Int, - nbAnalysis: Int, - nbWin: Int, - nbLoss: Int, - nbDraw: Int, - ratingDiff: Int, - gameSections: GameSections, - bestWin: Option[Results.BestWin], - opponentRatingSum: Int, - lastPlayed: Option[DateTime]) { - - def opponentRatingAvg = (nbGames > 0) option (opponentRatingSum / nbGames) - - def ratingDiffAvg = (nbGames > 0) option (ratingDiff / nbGames) - - def aggregate(p: RichPov) = copy( - nbGames = nbGames + 1, - nbAnalysis = nbAnalysis + p.analysis.isDefined.fold(1, 0), - nbWin = nbWin + (~p.pov.win).fold(1, 0), - nbLoss = nbLoss + (~p.pov.loss).fold(1, 0), - nbDraw = nbDraw + p.pov.game.drawn.fold(1, 0), - ratingDiff = ratingDiff + ~p.pov.player.ratingDiff, - gameSections = gameSections aggregate p, - bestWin = if (~p.pov.win) { - Results.makeBestWin(p.pov).fold(bestWin) { newBest => - bestWin.fold(newBest) { prev => - if (newBest.rating > prev.rating) newBest else prev - }.some - } - } - else bestWin, - opponentRatingSum = opponentRatingSum + ~p.pov.opponent.rating, - lastPlayed = p.pov.game.createdAt.some) - - def merge(r: Results) = Results( - nbGames = nbGames + r.nbGames, - nbAnalysis = nbAnalysis + r.nbAnalysis, - nbWin = nbWin + r.nbWin, - nbLoss = nbLoss + r.nbLoss, - nbDraw = nbDraw + r.nbDraw, - ratingDiff = ratingDiff + r.ratingDiff, - gameSections = gameSections merge r.gameSections, - bestWin = (bestWin, r.bestWin) match { - case (Some(a), Some(b)) => Some(a merge b) - case (Some(a), _) => a.some - case (_, Some(b)) => b.some - case _ => none - }, - opponentRatingSum = opponentRatingSum + r.opponentRatingSum, - lastPlayed = (lastPlayed, r.lastPlayed) match { - case (Some(a), Some(b)) => Some(if (a isAfter b) a else b) - case (Some(a), _) => a.some - case (_, Some(b)) => b.some - case _ => none - }) -} - -object Results { - - val empty = Results(0, 0, 0, 0, 0, 0, GameSections.empty, none, 0, none) - - case class BestWin(id: String, userId: String, rating: Int) { - def merge(b: BestWin) = if (rating > b.rating) this else b - } - def makeBestWin(pov: Pov): Option[BestWin] = (pov.game.playedTurns > 4) ?? { - pov.opponent.userId |@| pov.opponent.rating apply { - case (opId, opRating) => BestWin(pov.gameId, opId, opRating) - } - } - - case class Computation(results: Results) { - - def aggregate(p: RichPov) = copy(results = results.aggregate(p)) - - def nbGames = results.nbGames - - def run = results - } - val emptyComputation = Computation(empty) -} diff --git a/modules/coach_old/src/main/Share.scala b/modules/coach_old/src/main/Share.scala deleted file mode 100644 index 0e8e272966..0000000000 --- a/modules/coach_old/src/main/Share.scala +++ /dev/null @@ -1,18 +0,0 @@ -package lila.coach - -import lila.pref.Pref -import lila.user.User - -final class Share( - getPref: String => Fu[Pref], - areFriends: (String, String) => Fu[Boolean]) { - - def grant(coached: User, to: Option[User]): Fu[Boolean] = getPref(coached.id) flatMap { pref => - pref.coachShare match { - case _ if to.contains(coached) => fuccess(true) - case Pref.CoachShare.EVERYBODY => fuccess(true) - case Pref.CoachShare.FRIENDS => to ?? { t => areFriends(coached.id, t.id) } - case Pref.CoachShare.NOBODY => fuccess(false) - } - } -} diff --git a/modules/coach_old/src/main/StatApi.scala b/modules/coach_old/src/main/StatApi.scala deleted file mode 100644 index 965aabe148..0000000000 --- a/modules/coach_old/src/main/StatApi.scala +++ /dev/null @@ -1,57 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime -import play.api.libs.iteratee._ -import reactivemongo.bson._ -import reactivemongo.bson.Macros -import reactivemongo.core.commands._ -import scala.concurrent.duration._ - -import lila.db.BSON._ -import lila.db.Implicits._ -import lila.user.UserRepo - -final class StatApi(coll: Coll) { - - import BSONHandlers._ - - private def selectId(id: String) = BSONDocument("_id" -> id) - private def selectUserId(id: String) = BSONDocument("userId" -> id) - private val sortChronological = BSONDocument("from" -> 1) - private val sortAntiChronological = BSONDocument("from" -> -1) - - def fetchRangeForMoves(userId: String, range: Range) = - fetchRange(userId, range, BSONDocument("data.openings" -> false)) - - def fetchRangeForOpenings(userId: String, range: Range) = - fetchRange(userId, range, BSONDocument("data.perfResults" -> false)) - - private def fetchRange( - userId: String, - range: Range, - projection: BSONDocument): Fu[Option[Period]] = - coll.find(selectUserId(userId), projection) - .skip(range.min) - .sort(sortChronological) - .cursor[Period]() - .enumerate(range.size) &> - Enumeratee.take(range.size) |>>> - Iteratee.fold[Period, Option[Period]](none) { - case (a, b) => a.fold(b)(_ merge b).some - } - - def fetchFirst(userId: String): Fu[Option[Period]] = - fetchRange(userId, Range(0, 1), BSONDocument()) - - def fetchLast(userId: String): Fu[Option[Period]] = - coll.find(selectUserId(userId)).sort(sortAntiChronological).one[Period] - - def count(userId: String): Fu[Int] = - coll.count(selectUserId(userId).some) - - def insert(p: Period) = coll.insert(p).void - - def remove(p: Period) = coll.remove(selectId(p.id)).void - - def removeAll(userId: String) = coll.remove(selectUserId(userId)).void -} diff --git a/modules/coach_old/src/main/UserStat.scala b/modules/coach_old/src/main/UserStat.scala deleted file mode 100644 index 6e258c4376..0000000000 --- a/modules/coach_old/src/main/UserStat.scala +++ /dev/null @@ -1,56 +0,0 @@ -package lila.coach - -import org.joda.time.DateTime - -import lila.rating.PerfType - -case class UserStat( - colorResults: ColorResults, - openings: Openings, - results: PerfResults, - perfResults: PerfResults.PerfResultsMap) { - - def merge(o: UserStat) = copy( - colorResults = colorResults merge o.colorResults, - openings = openings merge o.openings, - results = results merge o.results, - perfResults = perfResults merge o.perfResults) -} - -object UserStat { - - case class Computation( - stat: UserStat, - colorResultsComp: ColorResults.Computation, - resultsComp: PerfResults.Computation, - perfResultsComp: Map[PerfType, PerfResults.Computation], - openingsComp: Openings.Computation) { - - def aggregate(p: RichPov) = copy( - resultsComp = resultsComp aggregate p, - colorResultsComp = colorResultsComp aggregate p, - perfResultsComp = p.pov.game.perfType.fold(perfResultsComp) { perfType => - perfResultsComp + ( - perfType -> - (perfResultsComp.get(perfType) | PerfResults.emptyComputation).aggregate(p) - ) - }, - openingsComp = openingsComp.aggregate(p)) - - def nbGames = resultsComp.nbGames - - def run = stat.copy( - results = resultsComp.run, - colorResults = colorResultsComp.run, - perfResults = PerfResults.PerfResultsMap(perfResultsComp.mapValues(_.run)), - openings = openingsComp.run) - } - - val empty = UserStat( - colorResults = ColorResults.empty, - openings = Openings.empty, - results = PerfResults.empty, - perfResults = PerfResults.emptyPerfResultsMap) - - val emptyComputation = Computation(empty, ColorResults.emptyComputation, PerfResults.emptyComputation, Map.empty, Openings.emptyComputation) -} diff --git a/modules/coach_old/src/main/model.scala b/modules/coach_old/src/main/model.scala deleted file mode 100644 index aa88be8551..0000000000 --- a/modules/coach_old/src/main/model.scala +++ /dev/null @@ -1,21 +0,0 @@ -package lila.coach - -case class RichPov( - pov: lila.game.Pov, - initialFen: Option[String], - analysis: Option[lila.analyse.Analysis], - division: chess.Division, - accuracy: Option[lila.analyse.Accuracy.DividedAccuracy], - moveAccuracy: Option[List[Int]]) - -case class NbSum(nb: Int, sum: Int) { - - def avg = (nb > 0) option (sum / nb) - - def add(v: Int) = copy(nb + 1, sum + v) - - def merge(o: NbSum) = NbSum(nb + o.nb, sum + o.sum) -} -object NbSum { - val empty = NbSum(0, 0) -} diff --git a/modules/coach_old/src/main/package.scala b/modules/coach_old/src/main/package.scala deleted file mode 100644 index d048d95a6d..0000000000 --- a/modules/coach_old/src/main/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package lila - -package object coach extends PackageObject with WithPlay diff --git a/modules/coach/src/main/AggregationPipeline.scala b/modules/insight/src/main/AggregationPipeline.scala similarity index 96% rename from modules/coach/src/main/AggregationPipeline.scala rename to modules/insight/src/main/AggregationPipeline.scala index b15d8bd8cb..b034258d4f 100644 --- a/modules/coach/src/main/AggregationPipeline.scala +++ b/modules/insight/src/main/AggregationPipeline.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ import reactivemongo.bson._ @@ -8,7 +8,7 @@ import lila.db.Implicits._ private final class AggregationPipeline { - import lila.coach.{ Dimension => D, Metric => M } + import lila.insight.{ Dimension => D, Metric => M } import Storage._ private val unwindMoves = Unwind("moves").some diff --git a/modules/coach/src/main/Answer.scala b/modules/insight/src/main/Answer.scala similarity index 95% rename from modules/coach/src/main/Answer.scala rename to modules/insight/src/main/Answer.scala index c12c803668..dec5ac16b0 100644 --- a/modules/coach/src/main/Answer.scala +++ b/modules/insight/src/main/Answer.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight case class Answer[X]( question: Question[X], diff --git a/modules/coach/src/main/BSONHandlers.scala b/modules/insight/src/main/BSONHandlers.scala similarity index 97% rename from modules/coach/src/main/BSONHandlers.scala rename to modules/insight/src/main/BSONHandlers.scala index b325748608..7ac21b0693 100644 --- a/modules/coach/src/main/BSONHandlers.scala +++ b/modules/insight/src/main/BSONHandlers.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import reactivemongo.bson._ import reactivemongo.bson.Macros @@ -10,7 +10,7 @@ import lila.db.Implicits._ import lila.game.BSONHandlers.StatusBSONHandler import lila.rating.PerfType -private[coach] object BSONHandlers { +private object BSONHandlers { implicit val ColorBSONHandler = new BSONHandler[BSONBoolean, Color] { def read(b: BSONBoolean) = Color(b.value) diff --git a/modules/coach/src/main/Chart.scala b/modules/insight/src/main/Chart.scala similarity index 98% rename from modules/coach/src/main/Chart.scala rename to modules/insight/src/main/Chart.scala index 7a16259657..8f449b7581 100644 --- a/modules/coach/src/main/Chart.scala +++ b/modules/insight/src/main/Chart.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import play.api.libs.json._ diff --git a/modules/coach/src/main/Dimension.scala b/modules/insight/src/main/Dimension.scala similarity index 90% rename from modules/coach/src/main/Dimension.scala rename to modules/insight/src/main/Dimension.scala index be31f99e89..ac96d8392a 100644 --- a/modules/coach/src/main/Dimension.scala +++ b/modules/insight/src/main/Dimension.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import reactivemongo.bson._ @@ -53,8 +53,8 @@ object Dimension { def valuesOf[X](d: Dimension[X]): List[X] = d match { case Perf => PerfType.nonPuzzle - case Phase => lila.coach.Phase.all - case Result => lila.coach.Result.all + case Phase => lila.insight.Phase.all + case Result => lila.insight.Result.all case Color => chess.Color.all case Opening => EcopeningDB.all case OpponentStrength => RelativeStrength.all @@ -63,8 +63,8 @@ object Dimension { def valueByKey[X](d: Dimension[X], key: String): Option[X] = d match { case Perf => PerfType.byKey get key - case Phase => parseIntOption(key) flatMap lila.coach.Phase.byId.get - case Result => parseIntOption(key) flatMap lila.coach.Result.byId.get + case Phase => parseIntOption(key) flatMap lila.insight.Phase.byId.get + case Result => parseIntOption(key) flatMap lila.insight.Result.byId.get case Color => chess.Color(key) case Opening => EcopeningDB.allByEco get key case OpponentStrength => parseIntOption(key) flatMap RelativeStrength.byId.get diff --git a/modules/coach/src/main/Ecopening.scala b/modules/insight/src/main/Ecopening.scala similarity index 98% rename from modules/coach/src/main/Ecopening.scala rename to modules/insight/src/main/Ecopening.scala index 58eddde8f9..d8cca1f954 100644 --- a/modules/coach/src/main/Ecopening.scala +++ b/modules/insight/src/main/Ecopening.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight case class Ecopening( eco: Ecopening.ECO, diff --git a/modules/coach/src/main/EcopeningDB.scala b/modules/insight/src/main/EcopeningDB.scala similarity index 99% rename from modules/coach/src/main/EcopeningDB.scala rename to modules/insight/src/main/EcopeningDB.scala index b028983a33..02b81230f8 100644 --- a/modules/coach/src/main/EcopeningDB.scala +++ b/modules/insight/src/main/EcopeningDB.scala @@ -1,6 +1,6 @@ -package lila.coach +package lila.insight -private[coach] object EcopeningDB { +private object EcopeningDB { import Ecopening._ diff --git a/modules/coach/src/main/Entry.scala b/modules/insight/src/main/Entry.scala similarity index 99% rename from modules/coach/src/main/Entry.scala rename to modules/insight/src/main/Entry.scala index 435d62ca77..b68b334152 100644 --- a/modules/coach/src/main/Entry.scala +++ b/modules/insight/src/main/Entry.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import chess.{ Color, Status, Role } import lila.game.{ PgnMoves, Game } diff --git a/modules/coach/src/main/Env.scala b/modules/insight/src/main/Env.scala similarity index 89% rename from modules/coach/src/main/Env.scala rename to modules/insight/src/main/Env.scala index c37d5d4b3a..33d22d635d 100644 --- a/modules/coach/src/main/Env.scala +++ b/modules/insight/src/main/Env.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import akka.actor._ import com.typesafe.config.Config @@ -30,7 +30,7 @@ final class Env( private lazy val aggregationPipeline = new AggregationPipeline - lazy val api = new CoachApi( + lazy val api = new InsightApi( storage = storage, pipeline = aggregationPipeline) @@ -41,8 +41,8 @@ final class Env( object Env { - lazy val current: Env = "coach" boot new Env( - config = lila.common.PlayApp loadConfig "coach", + lazy val current: Env = "insight" boot new Env( + config = lila.common.PlayApp loadConfig "insight", getPref = lila.pref.Env.current.api.getPrefById, areFriends = lila.relation.Env.current.api.areFriends, lightUser = lila.user.Env.current.lightUser, diff --git a/modules/coach/src/main/Indexer.scala b/modules/insight/src/main/Indexer.scala similarity index 85% rename from modules/coach/src/main/Indexer.scala rename to modules/insight/src/main/Indexer.scala index 125d772e23..ea80ff9f68 100644 --- a/modules/coach/src/main/Indexer.scala +++ b/modules/insight/src/main/Indexer.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import akka.actor.ActorRef import org.joda.time.DateTime @@ -48,8 +48,8 @@ final class Indexer(storage: Storage, sequencer: ActorRef) { pimpQB($query(gameQuery(user))).sort(Query.sortChronological).one[Game] private def computeFrom(user: User, from: DateTime): Funit = - lila.common.Chronometer.log(s"coach aggregator:${user.username}") { - loginfo(s"[coach] start aggregating ${user.username} games") + lila.common.Chronometer.log(s"insight aggregator:${user.username}") { + loginfo(s"[insight] start aggregating ${user.username} games") val query = $query(gameQuery(user) ++ Json.obj(Game.BSONFields.createdAt -> $gte($date(from)))) // val query = $query(gameQuery(user) ++ Query.analysed(true) ++ Json.obj(Game.BSONFields.createdAt -> $gte($date(from)))) pimpQB(query) @@ -64,13 +64,13 @@ final class Indexer(storage: Storage, sequencer: ActorRef) { } |>>> Iteratee.foldM[Either[Game, Entry], Int](0) { case (nb, Right(e)) => - if (nb % 100 == 0) loginfo(s"[coach ${user.username}] aggregated $nb games") + if (nb % 100 == 0) loginfo(s"[insight ${user.username}] aggregated $nb games") storage insert e inject (nb + 1) case (nb, Left(g)) => - logwarn(s"[coach ${user.username}] invalid game http://l.org/${g.id}") + logwarn(s"[insight ${user.username}] invalid game http://l.org/${g.id}") fuccess(nb) } addEffect { nb => - loginfo(s"[coach ${user.username}] done aggregating $nb games") + loginfo(s"[insight ${user.username}] done aggregating $nb games") } void } } diff --git a/modules/coach/src/main/CoachApi.scala b/modules/insight/src/main/InsightApi.scala similarity index 92% rename from modules/coach/src/main/CoachApi.scala rename to modules/insight/src/main/InsightApi.scala index cf8c6320bf..d27a9d9c47 100644 --- a/modules/coach/src/main/CoachApi.scala +++ b/modules/insight/src/main/InsightApi.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._ import reactivemongo.bson._ @@ -7,12 +7,12 @@ import lila.db.Implicits._ import lila.game.GameRepo import lila.user.User -final class CoachApi( +final class InsightApi( storage: Storage, pipeline: AggregationPipeline) { - import lila.coach.{ Dimension => D, Metric => M } - import CoachApi._ + import lila.insight.{ Dimension => D, Metric => M } + import InsightApi._ def ask[X](question: Question[X], user: User): Fu[Answer[X]] = storage.aggregate(pipeline(question, user.id)).map { res => @@ -48,7 +48,7 @@ final class CoachApi( } } -object CoachApi { +object InsightApi { sealed trait UserStatus object UserStatus { diff --git a/modules/coach/src/main/JsonQuestion.scala b/modules/insight/src/main/JsonQuestion.scala similarity index 98% rename from modules/coach/src/main/JsonQuestion.scala rename to modules/insight/src/main/JsonQuestion.scala index 4c007eccd8..893d00f0c3 100644 --- a/modules/coach/src/main/JsonQuestion.scala +++ b/modules/insight/src/main/JsonQuestion.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import play.api.libs.json._ diff --git a/modules/coach/src/main/JsonView.scala b/modules/insight/src/main/JsonView.scala similarity index 98% rename from modules/coach/src/main/JsonView.scala rename to modules/insight/src/main/JsonView.scala index 845edc04f1..8c309ceb7f 100644 --- a/modules/coach/src/main/JsonView.scala +++ b/modules/insight/src/main/JsonView.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import play.api.libs.json._ diff --git a/modules/coach/src/main/Math.scala b/modules/insight/src/main/Math.scala similarity index 97% rename from modules/coach/src/main/Math.scala rename to modules/insight/src/main/Math.scala index ad51d723dc..5d96df0651 100644 --- a/modules/coach/src/main/Math.scala +++ b/modules/insight/src/main/Math.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import scala.annotation._ import scala.math.{ pow, abs, sqrt, E, exp } diff --git a/modules/coach/src/main/Metric.scala b/modules/insight/src/main/Metric.scala similarity index 97% rename from modules/coach/src/main/Metric.scala rename to modules/insight/src/main/Metric.scala index ca85e8c82d..fe00b9fc28 100644 --- a/modules/coach/src/main/Metric.scala +++ b/modules/insight/src/main/Metric.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight sealed abstract class Metric( val key: String, diff --git a/modules/coach/src/main/Position.scala b/modules/insight/src/main/Position.scala similarity index 92% rename from modules/coach/src/main/Position.scala rename to modules/insight/src/main/Position.scala index d3cb60ff40..9286e6a2ca 100644 --- a/modules/coach/src/main/Position.scala +++ b/modules/insight/src/main/Position.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight sealed trait Position { diff --git a/modules/coach/src/main/PovToEntry.scala b/modules/insight/src/main/PovToEntry.scala similarity index 99% rename from modules/coach/src/main/PovToEntry.scala rename to modules/insight/src/main/PovToEntry.scala index ee330727dc..5aa44f15bd 100644 --- a/modules/coach/src/main/PovToEntry.scala +++ b/modules/insight/src/main/PovToEntry.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import chess.Role import lila.analyse.Accuracy diff --git a/modules/coach/src/main/Question.scala b/modules/insight/src/main/Question.scala similarity index 96% rename from modules/coach/src/main/Question.scala rename to modules/insight/src/main/Question.scala index 0dbba2f47e..9c2787e283 100644 --- a/modules/coach/src/main/Question.scala +++ b/modules/insight/src/main/Question.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight case class Question[X]( dimension: Dimension[X], diff --git a/modules/insight/src/main/Share.scala b/modules/insight/src/main/Share.scala new file mode 100644 index 0000000000..6f94ed25ee --- /dev/null +++ b/modules/insight/src/main/Share.scala @@ -0,0 +1,18 @@ +package lila.insight + +import lila.pref.Pref +import lila.user.User + +final class Share( + getPref: String => Fu[Pref], + areFriends: (String, String) => Fu[Boolean]) { + + def grant(insighted: User, to: Option[User]): Fu[Boolean] = getPref(insighted.id) flatMap { pref => + pref.insightShare match { + case _ if to.contains(insighted) => fuccess(true) + case Pref.InsightShare.EVERYBODY => fuccess(true) + case Pref.InsightShare.FRIENDS => to ?? { t => areFriends(insighted.id, t.id) } + case Pref.InsightShare.NOBODY => fuccess(false) + } + } +} diff --git a/modules/coach/src/main/Storage.scala b/modules/insight/src/main/Storage.scala similarity index 98% rename from modules/coach/src/main/Storage.scala rename to modules/insight/src/main/Storage.scala index 5e2aecc39b..45c76f1f0b 100644 --- a/modules/coach/src/main/Storage.scala +++ b/modules/insight/src/main/Storage.scala @@ -1,4 +1,4 @@ -package lila.coach +package lila.insight import org.joda.time.DateTime import play.api.libs.iteratee._ diff --git a/modules/insight/src/main/package.scala b/modules/insight/src/main/package.scala new file mode 100644 index 0000000000..89291b55a8 --- /dev/null +++ b/modules/insight/src/main/package.scala @@ -0,0 +1,3 @@ +package lila + +package object insight extends PackageObject with WithPlay diff --git a/modules/pref/src/main/DataForm.scala b/modules/pref/src/main/DataForm.scala index 7cc89cffdc..378ef3570a 100644 --- a/modules/pref/src/main/DataForm.scala +++ b/modules/pref/src/main/DataForm.scala @@ -25,7 +25,7 @@ private[pref] final class DataForm { "premove" -> number.verifying(Set(0, 1) contains _), "animation" -> number.verifying(Set(0, 1, 2, 3) contains _), "submitMove" -> number.verifying(Pref.SubmitMove.choices.toMap contains _), - "coachShare" -> number.verifying(Set(0, 1, 2) contains _), + "insightShare" -> number.verifying(Set(0, 1, 2) contains _), "confirmResign" -> number.verifying(Pref.ConfirmResign.choices.toMap contains _), "captured" -> number.verifying(Set(0, 1) contains _) )(PrefData.apply)(PrefData.unapply)) @@ -48,7 +48,7 @@ private[pref] final class DataForm { premove: Int, animation: Int, submitMove: Int, - coachShare: Int, + insightShare: Int, confirmResign: Int, captured: Int) { @@ -70,7 +70,7 @@ private[pref] final class DataForm { premove = premove == 1, animation = animation, submitMove = submitMove, - coachShare = coachShare, + insightShare = insightShare, confirmResign = confirmResign, captured = captured == 1) } @@ -94,7 +94,7 @@ private[pref] final class DataForm { premove = pref.premove.fold(1, 0), animation = pref.animation, submitMove = pref.submitMove, - coachShare = pref.coachShare, + insightShare = pref.insightShare, confirmResign = pref.confirmResign, captured = pref.captured.fold(1, 0)) } diff --git a/modules/pref/src/main/Pref.scala b/modules/pref/src/main/Pref.scala index 8a617340a7..d44dbd24c7 100644 --- a/modules/pref/src/main/Pref.scala +++ b/modules/pref/src/main/Pref.scala @@ -38,7 +38,7 @@ case class Pref( puzzleDifficulty: Int, submitMove: Int, confirmResign: Int, - coachShare: Int, + insightShare: Int, tags: Map[String, String] = Map.empty) { import Pref._ @@ -157,7 +157,7 @@ object Pref { YES -> "Yes") } - object CoachShare { + object InsightShare { val NOBODY = 0 val FRIENDS = 1 val EVERYBODY = 2 @@ -314,7 +314,7 @@ object Pref { puzzleDifficulty = Difficulty.NORMAL, submitMove = SubmitMove.CORRESPONDENCE_ONLY, confirmResign = ConfirmResign.YES, - coachShare = CoachShare.FRIENDS, + insightShare = InsightShare.FRIENDS, tags = Map.empty) import ornicar.scalalib.Zero diff --git a/modules/pref/src/main/PrefApi.scala b/modules/pref/src/main/PrefApi.scala index 2590ac96b3..fc02bdc04d 100644 --- a/modules/pref/src/main/PrefApi.scala +++ b/modules/pref/src/main/PrefApi.scala @@ -56,7 +56,7 @@ final class PrefApi( puzzleDifficulty = r.getD("puzzleDifficulty", Pref.default.puzzleDifficulty), submitMove = r.getD("submitMove", Pref.default.submitMove), confirmResign = r.getD("confirmResign", Pref.default.confirmResign), - coachShare = r.getD("coachShare", Pref.default.coachShare), + insightShare = r.getD("insightShare", Pref.default.insightShare), tags = r.getD("tags", Pref.default.tags)) def writes(w: BSON.Writer, o: Pref) = BSONDocument( @@ -91,7 +91,7 @@ final class PrefApi( "puzzleDifficulty" -> o.puzzleDifficulty, "submitMove" -> o.submitMove, "confirmResign" -> o.confirmResign, - "coachShare" -> o.coachShare, + "insightShare" -> o.insightShare, "tags" -> o.tags) } diff --git a/project/Build.scala b/project/Build.scala index 0675a1ec26..7a5e654b37 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -51,7 +51,7 @@ object ApplicationBuild extends Build { importer, tournament, simul, relation, report, pref, // simulation, evaluation, chat, puzzle, tv, coordinate, blog, donation, qa, history, worldMap, opening, video, shutup, - playban, coach) + playban, insight) lazy val moduleRefs = modules map projectToRef lazy val moduleCPDeps = moduleRefs map { new sbt.ClasspathDependency(_, None) } @@ -189,7 +189,7 @@ object ApplicationBuild extends Build { libraryDependencies ++= provided(play.api, RM, PRM) ) - lazy val coach = project("coach", + lazy val insight = project("insight", Seq(common, chess, game, user, analyse, relation, pref, socket, round) ).settings( libraryDependencies ++= provided(play.api, RM, PRM) diff --git a/public/javascripts/coach-refresh.js b/public/javascripts/insight-refresh.js similarity index 83% rename from public/javascripts/coach-refresh.js rename to public/javascripts/insight-refresh.js index 19829e356a..664e856681 100644 --- a/public/javascripts/coach-refresh.js +++ b/public/javascripts/insight-refresh.js @@ -1,6 +1,6 @@ $(function() { lichess.refreshCoachForm = function() { - $('form.coach-refresh').submit(function() { + $('form.insight-refresh').submit(function() { $.modal($(this).find('.crunshing')); $.post($(this).attr('action'), function() { location.reload(); diff --git a/public/stylesheets/coachMove.css b/public/stylesheets/coachMove.css deleted file mode 100644 index 5abf2c05c3..0000000000 --- a/public/stylesheets/coachMove.css +++ /dev/null @@ -1,5 +0,0 @@ -.coach_main .inspect .movechart { - height: 340px; - width: 772px; - margin-left: -10px; -} diff --git a/public/stylesheets/coachOpening.css b/public/stylesheets/coachOpening.css deleted file mode 100644 index 89580422f5..0000000000 --- a/public/stylesheets/coachOpening.css +++ /dev/null @@ -1,33 +0,0 @@ -.coach_main .inspect .board { - float: left; -} -.coach_main .inspect .content { - margin-top: 15px; -} -.coach_main .inspect .cg-board-wrap { - width: 330px; - height: 330px; - position: relative; -} -.coach_main .inspect .right { - position: relative; - margin-left: 350px; - font-size: 16px; -} -.coach_main .inspect .right .not_analysed { - line-height: 120px; -} -.coach_main .inspect .right h3 { - text-align: center; - width: 100%; - font-size: 0.9em; - margin: 0; -} -.coach_main .inspect .right .sections { - width: 100%; - height: 250px; -} -.coach_main .inspect .right table { - width: 100%; - line-height: 2em; -} diff --git a/public/stylesheets/coach.css b/public/stylesheets/insight.css similarity index 56% rename from public/stylesheets/coach.css rename to public/stylesheets/insight.css index 4a6b273cab..3a916e2f28 100644 --- a/public/stylesheets/coach.css +++ b/public/stylesheets/insight.css @@ -1,54 +1,54 @@ -#coach .square-wrap { +#insight .square-wrap { text-align: center; margin-top: 180px; } -#coach .square-in { +#insight .square-in { -webkit-transform: scale(5); } -#coach .left { +#insight .left { position: absolute; top: 80px; left: -260px; width: 240px; } -#coach .coach-stale { +#insight .insight-stale { margin-top: 10px; } -#coach .ms-parent { +#insight .ms-parent { display: block; margin-top: 10px; } -#coach .ms-parent > * { +#insight .ms-parent > * { position: absoule; top: 0; left: 0; } -#coach .ms-choice { +#insight .ms-choice { padding: 5px 10px; } -#coach .ms-drop { +#insight .ms-drop { margin-left: 100%; } -#coach .ms-drop ul { +#insight .ms-drop ul { padding: 0; } -#coach .ms-drop ul > li label { +#insight .ms-drop ul > li label { padding: 4px 8px; cursor: pointer; } -#coach .ms-drop ul > li label:hover { +#insight .ms-drop ul > li label:hover { background: #f0f0f0; } -#coach .ms-drop ul > li label input { +#insight .ms-drop ul > li label input { margin-right: 5px; } -#coach table { +#insight table { border: 1px solid #d4d4d4; } -#coach table td.data { +#insight table td.data { font-weight: bold; } diff --git a/ui/coachMove/gulpfile.js b/ui/coachMove/gulpfile.js deleted file mode 100644 index 5cc7668a6f..0000000000 --- a/ui/coachMove/gulpfile.js +++ /dev/null @@ -1,54 +0,0 @@ -var source = require('vinyl-source-stream'); -var gulp = require('gulp'); -var gutil = require('gulp-util'); -var watchify = require('watchify'); -var browserify = require('browserify'); -var uglify = require('gulp-uglify'); -var streamify = require('gulp-streamify'); - -var sources = ['./src/main.js']; -var destination = '../../public/compiled/'; -var onError = function(error) { - gutil.log(gutil.colors.red(error.message)); -}; -var standalone = 'LichessCoachMove'; - -gulp.task('prod', function() { - return browserify('./src/main.js', { - standalone: standalone - }).bundle() - .on('error', onError) - .pipe(source('lichess.coach.move.min.js')) - .pipe(streamify(uglify())) - .pipe(gulp.dest(destination)); -}); - -gulp.task('dev', function() { - return browserify('./src/main.js', { - standalone: standalone - }).bundle() - .on('error', onError) - .pipe(source('lichess.coach.move.js')) - .pipe(gulp.dest(destination)); -}); - -gulp.task('watch', function() { - var opts = watchify.args; - opts.debug = true; - opts.standalone = standalone; - - var bundleStream = watchify(browserify(sources, opts)) - .on('update', rebundle) - .on('log', gutil.log); - - function rebundle() { - return bundleStream.bundle() - .on('error', onError) - .pipe(source('lichess.coach.move.js')) - .pipe(gulp.dest(destination)); - } - - return rebundle(); -}); - -gulp.task('default', ['watch']); diff --git a/ui/coachMove/package.json b/ui/coachMove/package.json deleted file mode 100644 index 46d0f0fa0f..0000000000 --- a/ui/coachMove/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "coachMove", - "version": "1.0.0", - "description": "lichess.org move coach", - "main": "src/main.js", - "repository": { - "type": "git", - "url": "https://github.com/ornicar/lila" - }, - "keywords": [ - "chess", - "lichess", - "coach", - "move" - ], - "author": "ornicar", - "license": "MIT", - "bugs": { - "url": "https://github.com/ornicar/lila/issues" - }, - "homepage": "https://github.com/ornicar/lila", - "devDependencies": { - "browserify": "~9.0.8", - "gulp": "~3.9.0", - "gulp-streamify": "~0.0.5", - "gulp-uglify": "~1.2.0", - "gulp-util": "~3.0.4", - "vinyl-source-stream": "~1.1.0", - "watchify": "~3.1.1" - }, - "dependencies": { - "chessground": "github:ornicar/chessground#v3.0.4", - "coach": "../coach", - "mithril": "github:ornicar/mithril.js#v1.0.0" - } -} diff --git a/ui/coachMove/src/ctrl.js b/ui/coachMove/src/ctrl.js deleted file mode 100644 index 8682ad1634..0000000000 --- a/ui/coachMove/src/ctrl.js +++ /dev/null @@ -1,65 +0,0 @@ -var m = require('mithril'); - -var throttle = require('coach').throttle; - -module.exports = function(opts) { - - this.user = opts.user; - this.color = opts.color; - this.nbPeriods = opts.nbPeriods; - - this.vm = { - preloading: !!this.nbPeriods, - loading: true, - range: [0, this.nbPeriods], - inspecting: 'global' - }; - - var requestData = throttle(1000, false, function() { - m.request({ - url: '/coach/move/' + this.user.id + '.json', - data: { - range: this.vm.range.join('-') - } - }).then(function(data) { - console.log(data); - this.data = data; - if (location.hash) this.inspect(location.hash.replace(/#/, '').replace(/_/g, ' ')); - this.vm.preloading = false; - this.vm.loading = false; - }.bind(this)); - m.redraw(); - }.bind(this)); - if (this.nbPeriods) setTimeout(requestData, 200); - - this.selectPeriodRange = function(from, to) { - this.vm.range = [from, to]; - this.vm.loading = true; - if (from === to) this.data = null; - else requestData(); - m.redraw(); - }.bind(this); - - this.jumpBy = function(delta) { - if (!this.vm.inspecting) return; - var keys = this.data.perfs.map(function(o) { - return o.perf.key - }); - var i = keys.indexOf(this.vm.inspecting); - var i2 = (i + delta) % keys.length; - if (i2 < 0) i2 = keys.length - 1; - this.inspect(keys[i2]); - }.bind(this); - - this.inspect = function(key) { - if (!this.data || !this.data.perfs.some(function(o) { - return o.perf.key === key; - })) return; - if (this.vm.inspecting === key) return; - if (window.history.replaceState) - window.history.replaceState(null, null, '#' + key); - this.vm.inspecting = key; - }.bind(this); - - this.trans = lichess.trans(opts.i18n); -}; diff --git a/ui/coachMove/src/inspect.js b/ui/coachMove/src/inspect.js deleted file mode 100644 index 4605a48d63..0000000000 --- a/ui/coachMove/src/inspect.js +++ /dev/null @@ -1,57 +0,0 @@ -var m = require('mithril'); -var chessground = require('chessground'); - -var coach = require('coach'); -var movechart = require('./movechart'); - -function sideCommands(ctrl) { - return [ - m('a.to.prev', { - 'data-icon': 'I', - onclick: function() { - ctrl.jumpBy(-1); - } - }), - m('a.to.next', { - 'data-icon': 'H', - onclick: function() { - ctrl.jumpBy(1); - } - }) - ]; -} - -module.exports = function(ctrl) { - var d = ctrl.data; - var o = d.perfs.filter(function(o) { - return o.perf.key === ctrl.vm.inspecting; - })[0]; - if (!o) return m('div.top.nodata', [ - sideCommands(ctrl), - m('p', 'No results for this data range and perf!') - ]); - var perf = o.perf; - var perfResults = o.results; - var results = perfResults.base; - return m('div.top.inspect', [ - sideCommands(ctrl), - coach.resultBar(results), - m('div.main.clearfix', [ - coach.shared.progress(results.ratingDiff / results.nbGames), - m('h2', m('strong.text', { - 'data-icon': perf.icon - }, perf.name)), - m('div.baseline', [ - m('strong', results.nbGames), - ' games, ', - m('strong', results.nbAnalysis), - ' analysed. Last played ', - coach.shared.momentFromNow(results.lastPlayed), - '.', - ]) - ]), - m('div.content', [ - movechart(ctrl), - ]) - ]); -}; diff --git a/ui/coachMove/src/main.js b/ui/coachMove/src/main.js deleted file mode 100644 index a655ca789d..0000000000 --- a/ui/coachMove/src/main.js +++ /dev/null @@ -1,16 +0,0 @@ -var m = require('mithril'); - -var ctrl = require('./ctrl'); -var view = require('./view'); - -module.exports = function(element, opts) { - - var controller = new ctrl(opts); - - m.module(element, { - controller: function() { - return controller; - }, - view: view - }); -}; diff --git a/ui/coachMove/src/movechart.js b/ui/coachMove/src/movechart.js deleted file mode 100644 index 0905803759..0000000000 --- a/ui/coachMove/src/movechart.js +++ /dev/null @@ -1,182 +0,0 @@ -var m = require('mithril'); - -var green = '#759900', - red = '#dc322f', - orange = '#d59120', - translucid = 'rgba(0,0,0,0.3)'; - -var absFormatter = function() { - return Math.abs(this.value); -}; - -function makeChart(el, data) { - $(el).highcharts({ - chart: { - spacing: [0, 0, 0, 0], - animation: { - duration: 300 - }, - backgroundColor: null, - borderWidth: 0, - borderRadius: 0, - plotBackgroundColor: null, - plotShadow: false, - plotBorderWidth: 0 - }, - title: { - text: null - }, - xAxis: {}, - yAxis: [{ - min: -150, - max: 150, - title: { - text: 'Average centipawn loss (ACPL) per move' - }, - labels: { - formatter: absFormatter - }, - lineWidth: 1, - gridLineWidth: 1 - }, { - opposite: true, - title: { - text: 'Seconds per move' - }, - labels: { - formatter: absFormatter - }, - lineWidth: 1, - gridLineWidth: 1 - }], - plotOptions: { - column: { - stacking: 'normal' - }, - line: { - color: 'rgba(0,0,0,0.7)', - lineWidth: 1 - } - }, - tooltip: { - useHTML: true, - formatter: function() { - var base = '[move ' + this.point.x + ' as ' + this.point.c + '] '; - if (this.point.acpl) - return base + Math.abs(this.point.acpl.avg) + ' centipawns
    ' + - 'over ' + this.point.acpl.nb + ' analysed games'; - return base + Math.abs(this.point.time.avg / 10) + ' seconds
    ' + - 'over ' + this.point.time.nb + ' games'; - }, - }, - series: [{ - yAxis: 0, - type: 'column', - name: 'ACPL as white', - data: data.acpls.white, - pointWidth: 9 - }, { - yAxis: 0, - type: 'column', - name: 'ACPL as black', - data: data.acpls.black, - // pointPadding: -0.2, - pointWidth: 9 - }, { - yAxis: 1, - type: 'line', - name: 'Time as white', - data: data.times.white, - marker: { - radius: 3 - } - }, { - yAxis: 1, - type: 'line', - name: 'Time as black', - data: data.times.black, - }], - credits: { - enabled: false - }, - legend: { - enabled: false - } - }, recenter); - return $(el).highcharts(); -} - -function recenter(chart) { - [0, 1].forEach(function(i) { - var ext = chart.yAxis[i].getExtremes(); - var dMax = Math.abs(ext.dataMax); - var dMin = Math.abs(ext.dataMin); - var dExt = dMax >= dMin ? dMax : dMin; - var min = 0 - dExt; - console.log(i, min, dExt); - chart.yAxis[i].setExtremes(min, dExt); - }); -} - -function makeAcplData(pr) { - var data = {}; - ['white', 'black'].forEach(function(color) { - data[color] = pr.moves[color].filter(function(m) { - return m.acpl.nb > 0; - }).map(function(move, i) { - var acpl = move.acpl.avg; - var y = Math.min(acpl, 150) + 10; - return { - c: color, - x: i + 1, - y: color === 'white' ? y : -y, - color: (acpl < 50 ? green : (acpl < 100 ? orange : red)), - acpl: move.acpl - }; - }); - }); - - return data; -} - -function makeTimeData(pr) { - var data = {}; - ['white', 'black'].forEach(function(color) { - data[color] = pr.moves[color].filter(function(m) { - return m.time.nb > 0; - }).map(function(move, i) { - var time = move.time.avg / 10; - return { - c: color, - x: i + 1, - y: color === 'white' ? time : -time, - time: move.time - }; - }); - }); - - return data; -} - -module.exports = function(ctrl) { - var pr = ctrl.data.perfs.filter(function(p) { - return p.perf.key === ctrl.vm.inspecting; - })[0].results; - return [ - m('div.movechart', { - config: function(el, isUpdate, ctx) { - var data = { - acpls: makeAcplData(pr), - times: makeTimeData(pr) - }; - console.log(data); - if (ctx.chart) { - [data.acpls.white, data.acpls.black, data.times.white, data.times.black].forEach(function(d, i) { - ctx.chart.series[i].setData(d); - }); - recenter(ctx.chart); - } else ctx.chart = makeChart(el, data); - } - }) - ]; -}; diff --git a/ui/coachMove/src/table.js b/ui/coachMove/src/table.js deleted file mode 100644 index 51cc901acf..0000000000 --- a/ui/coachMove/src/table.js +++ /dev/null @@ -1,72 +0,0 @@ -var m = require('mithril'); - -var coach = require('coach'); -var strings = coach.shared.strings; - -var headers = [ - ['name', 'Category'], - ['nbGames', 'Games'], - ['result', 'Result', strings.result], - ['ratingDiffAvg', 'Rating', strings.ratingDiff], - ['acpl', 'ACPL', strings.acpl], - ['lastPlayed', 'Last played'] -]; - -function thead(ctrl) { - return m('thead', m('tr', headers.map(function(h) { - var props = { - key: h[0], - }; - var spanProps = {}; - if (h[2]) { - spanProps.class = 'hint--top'; - spanProps['data-hint'] = h[2]; - } - return m('th', props, m('span', spanProps, h[1])); - }))); -} - -module.exports = function(ctrl) { - var d = ctrl.data; - var percent = function(nb) { - return Math.round(nb * 100 / d.perfs[0].results.base.nbGames); - }; - var acplAvg = ctrl.data.perfs[0].results.base.gameSections.all.acpl.avg; - return m('table.selector.slist', [ - thead(ctrl), - m('tbody', ctrl.data.perfs.map(function(o, i) { - var perf = o.perf; - var perfResults = o.results; - var results = perfResults.base; - var acpl = results.gameSections.all.acpl.avg; - return m('tr', { - key: perf.key, - onclick: function() { - ctrl.inspect(perf.key); - }, - class: (ctrl.vm.inspecting === perf.key ? 'active' : '') - }, [ - m('td', [ - m('div.name.text', { - 'data-icon': perf.icon - }, perf.name) - ]), - m('td', [ - m('div', results.nbGames + ' (' + percent(results.nbGames) + '%)') - ]), - m('td', coach.resultBar(results)), - m('td', coach.shared.progress(results.nbGames > 0 ? results.ratingDiff / results.nbGames : 0)), - m('td', [ - m('span.progress', acpl === null ? '-' : m('span', { - class: acpl > acplAvg ? 'negative' : 'positive' - }, acpl)) - ]), - m('td', [ - m('time.moment-from-now', { - datetime: results.lastPlayed - }) - ]) - ]); - })) - ]); -}; diff --git a/ui/coachMove/src/view.js b/ui/coachMove/src/view.js deleted file mode 100644 index 627f124e4a..0000000000 --- a/ui/coachMove/src/view.js +++ /dev/null @@ -1,44 +0,0 @@ -var m = require('mithril'); - -var inspect = require('./inspect'); -var table = require('./table'); -var Slider = require('coach').slider; -var shared = require('coach').shared; - -module.exports = function(ctrl) { - if (!ctrl.nbPeriods) return m('div.content_box_top', [ - m('h1', [ - shared.userLink(ctrl.user.name), - ' moves: No data available' - ]), - ]); - return m('div', { - config: function() { - $('body').trigger('lichess.content_loaded'); - } - }, [ - m('div.content_box_top', { - class: 'content_box_top' + (ctrl.vm.loading ? ' loading' : '') - }, [ - ctrl.nbPeriods > 1 ? m.component(Slider, { - max: ctrl.nbPeriods, - range: ctrl.vm.range, - dates: ctrl.data ? [ctrl.data.from, ctrl.data.to] : null, - onChange: ctrl.selectPeriodRange - }) : null, - m('h1', [ - shared.userLink(ctrl.user.name), - ' moves', - ctrl.data ? m('div.over', [ - ' over ', - ctrl.data.perfs[0].results.base.nbGames, - ' games' - ]) : null - ]), - ]), - ctrl.vm.preloading ? m('div.loader') : (!ctrl.data ? m('div.top.nodata', m('p', 'Empty period range!')) : [ - inspect(ctrl), - table(ctrl) - ]) - ]); -}; diff --git a/ui/coachOld/package.json b/ui/coachOld/package.json deleted file mode 100644 index 44264a55b2..0000000000 --- a/ui/coachOld/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "coach", - "version": "1.0.0", - "description": "lichess.org coach", - "main": "src/main.js", - "repository": { - "type": "git", - "url": "https://github.com/ornicar/lila" - }, - "keywords": [ - "chess", - "lichess", - "coach" - ], - "author": "ornicar", - "license": "MIT", - "bugs": { - "url": "https://github.com/ornicar/lila/issues" - }, - "homepage": "https://github.com/ornicar/lila", - "devDependencies": { - "browserify": "~9.0.8", - "gulp": "~3.9.0", - "gulp-streamify": "~0.0.5", - "gulp-uglify": "~1.2.0", - "gulp-util": "~3.0.4", - "vinyl-source-stream": "~1.1.0", - "watchify": "~3.1.1" - }, - "dependencies": { - "mithril": "0.2.0" - } -} diff --git a/ui/coachOld/src/main.js b/ui/coachOld/src/main.js deleted file mode 100644 index c6af14a714..0000000000 --- a/ui/coachOld/src/main.js +++ /dev/null @@ -1,42 +0,0 @@ -var m = require('mithril'); - -var shared = require('./shared'); - -module.exports = { - throttle: require('./throttle'), - slider: require('./slider'), - shared: shared, - bestWin: function(w, color) { - if (!w.user) return; - return m('a', { - href: '/' + w.id + '/' + color - }, [ - w.user.title ? (w.user.title + ' ') : '', - w.user.name, - ' (', - m('strong', w.rating), - ')' - ]); - }, - resultBar: function(r) { - return m('div.result-bar', [ - ['nbWin', 'win'], - ['nbDraw', 'draw'], - ['nbLoss', 'loss'] - ].map(function(x) { - var k = x[0]; - var name = x[1]; - var percent = (r[k] * 100 / r.nbGames); - return m('div', { - key: k, - class: k, - style: { - width: percent + '%' - } - }, [ - m('strong', Math.round(percent)), - '% ' + name - ]); - })); - }, -}; diff --git a/ui/coachOld/src/shared.js b/ui/coachOld/src/shared.js deleted file mode 100644 index ddb843b3ca..0000000000 --- a/ui/coachOld/src/shared.js +++ /dev/null @@ -1,42 +0,0 @@ -var m = require('mithril'); - -var strings = { - acpl: 'Average centipawn loss per move', - ratingDiff: 'Average rating points won', - result: 'Wins, draws and losses' -}; - -function decimals(nb) { - return Number(nb).toFixed(2); -} - -module.exports = { - strings: strings, - userLink: function(username) { - return m('a', { - href: '/@/' + username - }, username); - }, - momentFromNow: function(date) { - return m('time.moment-from-now', { - datetime: date - }); - }, - momentFormat: function(date, format) { - var parsed = moment(date); - var textContent = (format || 'calendar') === 'calendar' ? parsed.calendar() : parsed.format(format); - return m('time', { - datetime: date - }, format == 'calendar' ? parsed.calendar() : parsed.format(format)); - }, - progress: function(r) { - var perf; - var dec = decimals(r > 0 ? r : -r); - if (r === 0) perf = m('span', ' ='); - else if (r > 0) perf = m('span.positive[data-icon=N]', dec); - else if (r < 0) perf = m('span.negative[data-icon=M]', dec); - return m('span.rating.progress.hint--top', { - 'data-hint': strings.ratingDiff - }, perf); - }, -}; diff --git a/ui/coachOld/src/slider.js b/ui/coachOld/src/slider.js deleted file mode 100644 index f7d3485ff5..0000000000 --- a/ui/coachOld/src/slider.js +++ /dev/null @@ -1,48 +0,0 @@ -var m = require('mithril'); - -var momentFormat = require('./shared').momentFormat; - -var cubeFacets = ['a', 'b', 'c', 'd'].map(function(x) { - return m('div.' + x, m('div')); -}); - -var dateFormat = 'LL'; - -module.exports = { - controller: function(args) { - this.max = args.max; - this.range = args.range; - }, - view: function(ctrl, args) { - var ratio = function(x) { - return x * 100 / ctrl.max; - }; - return m('div.cube', [ - cubeFacets, - m('div.slider', { - config: function(el, isUpdate) { - if (isUpdate) return; - $(el).slider({ - range: true, - min: 0, - max: ctrl.max, - values: ctrl.range, - slide: function(event, ui) { - $(el).parent().find('.a div, .b div, .c div, .d div').css({ - width: ratio(ui.values[1] - ui.values[0]) + "%", - marginLeft: ratio(ui.values[0]) + '%' - }); - args.onChange(ui.values[0], ui.values[1]); - } - }).find('.ui-slider-handle').text('<>'); - } - }), - args.dates ? m('div.dates', [ - 'From ', - momentFormat(args.dates[0], dateFormat), - ' to ', - momentFormat(args.dates[1], dateFormat) - ]) : null - ]); - } -}; diff --git a/ui/coachOld/src/strings.js b/ui/coachOld/src/strings.js deleted file mode 100644 index 359fe35bc0..0000000000 --- a/ui/coachOld/src/strings.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - acpl: 'Average centipawn loss per move', - ratingDiff: 'Average rating points won', - result: 'Wins, draws and losses' -}; diff --git a/ui/coachOld/src/throttle.js b/ui/coachOld/src/throttle.js deleted file mode 100644 index 66160183fa..0000000000 --- a/ui/coachOld/src/throttle.js +++ /dev/null @@ -1,86 +0,0 @@ - /** - * https://github.com/niksy/throttle-debounce/blob/master/lib/throttle.js - * - * Throttle execution of a function. Especially useful for rate limiting - * execution of handlers on events like resize and scroll. - * - * @param {Number} delay A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful. - * @param {Boolean} noTrailing Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds while the - * throttled-function is being called. If noTrailing is false or unspecified, callback will be executed one final time - * after the last throttled-function call. (After the throttled-function has not been called for `delay` milliseconds, - * the internal counter is reset) - * @param {Function} callback A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is, - * to `callback` when the throttled-function is executed. - * @param {Boolean} debounceMode If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is false (at end), - * schedule `callback` to execute after `delay` ms. - * - * @return {Function} A new, throttled, function. - */ - module.exports = function(delay, noTrailing, callback, debounceMode) { - - // After wrapper has stopped being called, this timeout ensures that - // `callback` is executed at the proper times in `throttle` and `end` - // debounce modes. - var timeoutID; - - // Keep track of the last time `callback` was executed. - var lastExec = 0; - - // `noTrailing` defaults to falsy. - if (typeof(noTrailing) !== 'boolean') { - debounceMode = callback; - callback = noTrailing; - noTrailing = undefined; - } - - // The `wrapper` function encapsulates all of the throttling / debouncing - // functionality and when executed will limit the rate at which `callback` - // is executed. - return function() { - - var self = this; - var elapsed = Number(new Date()) - lastExec; - var args = arguments; - - // Execute `callback` and update the `lastExec` timestamp. - function exec() { - lastExec = Number(new Date()); - callback.apply(self, args); - } - - // If `debounceMode` is true (at begin) this is used to clear the flag - // to allow future `callback` executions. - function clear() { - timeoutID = undefined; - } - - if (debounceMode && !timeoutID) { - // Since `wrapper` is being called for the first time and - // `debounceMode` is true (at begin), execute `callback`. - exec(); - } - - // Clear any existing timeout. - if (timeoutID) { - clearTimeout(timeoutID); - } - - if (debounceMode === undefined && elapsed > delay) { - // In throttle mode, if `delay` time has been exceeded, execute - // `callback`. - exec(); - - } else if (noTrailing !== true) { - // In trailing throttle mode, since `delay` time has not been - // exceeded, schedule `callback` to execute `delay` ms after most - // recent execution. - // - // If `debounceMode` is true (at begin), schedule `clear` to execute - // after `delay` ms. - // - // If `debounceMode` is false (at end), schedule `callback` to - // execute after `delay` ms. - timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay); - } - }; - }; diff --git a/ui/coachOpening/gulpfile.js b/ui/coachOpening/gulpfile.js deleted file mode 100644 index 2c3b2f7a7a..0000000000 --- a/ui/coachOpening/gulpfile.js +++ /dev/null @@ -1,54 +0,0 @@ -var source = require('vinyl-source-stream'); -var gulp = require('gulp'); -var gutil = require('gulp-util'); -var watchify = require('watchify'); -var browserify = require('browserify'); -var uglify = require('gulp-uglify'); -var streamify = require('gulp-streamify'); - -var sources = ['./src/main.js']; -var destination = '../../public/compiled/'; -var onError = function(error) { - gutil.log(gutil.colors.red(error.message)); -}; -var standalone = 'LichessCoachOpening'; - -gulp.task('prod', function() { - return browserify('./src/main.js', { - standalone: standalone - }).bundle() - .on('error', onError) - .pipe(source('lichess.coach.opening.min.js')) - .pipe(streamify(uglify())) - .pipe(gulp.dest(destination)); -}); - -gulp.task('dev', function() { - return browserify('./src/main.js', { - standalone: standalone - }).bundle() - .on('error', onError) - .pipe(source('lichess.coach.opening.js')) - .pipe(gulp.dest(destination)); -}); - -gulp.task('watch', function() { - var opts = watchify.args; - opts.debug = true; - opts.standalone = standalone; - - var bundleStream = watchify(browserify(sources, opts)) - .on('update', rebundle) - .on('log', gutil.log); - - function rebundle() { - return bundleStream.bundle() - .on('error', onError) - .pipe(source('lichess.coach.opening.js')) - .pipe(gulp.dest(destination)); - } - - return rebundle(); -}); - -gulp.task('default', ['watch']); diff --git a/ui/coachOpening/package.json b/ui/coachOpening/package.json deleted file mode 100644 index 3f071a3790..0000000000 --- a/ui/coachOpening/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "coachOpening", - "version": "1.0.0", - "description": "lichess.org opening coach", - "main": "src/main.js", - "repository": { - "type": "git", - "url": "https://github.com/ornicar/lila" - }, - "keywords": [ - "chess", - "lichess", - "coach", - "opening" - ], - "author": "ornicar", - "license": "MIT", - "bugs": { - "url": "https://github.com/ornicar/lila/issues" - }, - "homepage": "https://github.com/ornicar/lila", - "devDependencies": { - "browserify": "~9.0.8", - "gulp": "~3.9.0", - "gulp-streamify": "~0.0.5", - "gulp-uglify": "~1.2.0", - "gulp-util": "~3.0.4", - "vinyl-source-stream": "~1.1.0", - "watchify": "~3.1.1" - }, - "dependencies": { - "chessground": "github:ornicar/chessground#v3.0.4", - "coach": "../coach", - "mithril": "github:ornicar/mithril.js#v1.0.0" - } -} diff --git a/ui/coachOpening/src/ctrl.js b/ui/coachOpening/src/ctrl.js deleted file mode 100644 index 457a8fdd85..0000000000 --- a/ui/coachOpening/src/ctrl.js +++ /dev/null @@ -1,143 +0,0 @@ -var m = require('mithril'); - -var chessground = require('chessground'); -var throttle = require('coach').throttle; - -function copy(obj, newValues) { - var k, c = {}; - for (k in obj) { - c[k] = obj[k]; - } - for (k in newValues) { - c[k] = newValues[k]; - } - return c; -} - -module.exports = function(opts) { - - this.user = opts.user; - this.color = opts.color; - this.nbPeriods = opts.nbPeriods; - - this.vm = { - preloading: !!this.nbPeriods, - loading: true, - range: [0, this.nbPeriods], - sort: { - prop: 'nbGames', - order: -1 - }, - inspecting: null, - /* { - eco: 'D00', - chessground: null - } */ - }; - - var requestData = throttle(1000, false, function() { - m.request({ - url: '/coach/opening/' + this.user.id + '/' + this.color + '.json', - data: { - range: this.vm.range.join('-') - } - }).then(function(data) { - - this.data = data; - - this.list = Object.keys(this.data.openings).map(function(eco) { - var o = this.data.openings[eco]; - var r = o.results; - return copy(o, { - result: r.nbWin / r.nbLoss, - acpl: r.gameSections.all.acpl.avg, - ratingDiffAvg: r.nbGames > 0 ? r.ratingDiff / r.nbGames : 0, - // just for sorting - name: o.opening.name, - nbGames: r.nbGames, - lastPlayed: r.lastPlayed - }); - }.bind(this)); - - this.sortList(); - - if (location.hash) this.inspect(location.hash.replace(/#/, '').replace(/_/g, ' ')); - - this.vm.preloading = false; - this.vm.loading = false; - m.redraw(); - }.bind(this)); - }.bind(this)); - if (this.nbPeriods) setTimeout(requestData, 200); - - this.selectPeriodRange = function(from, to) { - this.vm.range = [from, to]; - this.vm.loading = true; - if (from === to) this.data = null; - else requestData(); - m.redraw(); - }.bind(this); - - this.sortList = function() { - var s = this.vm.sort; - this.list.sort(function(a, b) { - return a[s.prop] > b[s.prop] ? s.order : a[s.prop] < b[s.prop] ? -s.order : 0; - }); - }.bind(this); - - this.setSort = function(prop) { - if (this.vm.sort.prop === prop) this.vm.sort.order = -this.vm.sort.order; - else this.vm.sort = { - prop: prop, - order: 1 - }; - this.sortList(); - }.bind(this); - - this.jumpBy = function(delta) { - if (!this.vm.inspecting) return; - var ecos = this.list.map(function(o) { - return o.opening.eco; - }); - var i = ecos.indexOf(this.vm.inspecting.eco); - var i2 = (i + delta) % ecos.length; - if (i2 < 0) i2 = ecos.length - 1; - this.inspect(ecos[i2]); - }.bind(this); - - this.isInspecting = function(eco) { - return this.vm.inspecting && this.vm.inspecting.eco === eco; - }.bind(this); - - this.inspect = function(eco) { - if (!this.data.openings[eco]) return; - if (this.isInspecting(eco)) return; - if (window.history.replaceState) - window.history.replaceState(null, null, '#' + eco); - var opening = this.data.openings[eco].opening; - var config = { - fen: opening.fen, - orientation: this.color, - viewOnly: true, - minimalDom: true, - lastMove: opening.lastMoveUci ? [opening.lastMoveUci.substr(0, 2), opening.lastMoveUci.substr(2, 2)] : null, - coordinates: false - }; - if (this.vm.inspecting) { - this.vm.inspecting.eco = eco; - this.vm.inspecting.chessground.set(config); - } else - this.vm.inspecting = { - eco: eco, - chessground: new chessground.controller(config) - }; - }.bind(this); - - this.uninspect = function() { - this.vm.inspecting = null; - if (window.history.replaceState) - window.history.replaceState(null, null, '#'); - }.bind(this); - - this.trans = lichess.trans(opts.i18n); -}; diff --git a/ui/coachOpening/src/inspect.js b/ui/coachOpening/src/inspect.js deleted file mode 100644 index 8e1025c0c0..0000000000 --- a/ui/coachOpening/src/inspect.js +++ /dev/null @@ -1,99 +0,0 @@ -var m = require('mithril'); -var chessground = require('chessground'); - -var sections = require('./sections'); -var coach = require('coach'); - -function sideCommands(ctrl) { - return [ - m('a.to.back', { - 'data-icon': 'L', - onclick: ctrl.uninspect - }), - m('a.to.prev', { - 'data-icon': 'I', - onclick: function() { - ctrl.jumpBy(-1); - } - }), - m('a.to.next', { - 'data-icon': 'H', - onclick: function() { - ctrl.jumpBy(1); - } - }) - ]; -} - -module.exports = function(ctrl, inspecting) { - var d = ctrl.data; - var eco = inspecting.eco; - var o = d.openings[eco]; - if (!o) return m('div.top.nodata', [ - sideCommands(ctrl), - m('p', 'No results for this data range and opening!') - ]); - var opening = o.opening; - var results = o.results; - var user = ctrl.user.id; - var searchUrl = '/games/search?players.a=' + user + '&players.' + ctrl.color + '=' + user + '&opening=' + eco; - var analysedUrl = searchUrl + '&analysed=1'; - return m('div.top.inspect', [ - sideCommands(ctrl), - coach.resultBar(results), - m('div.main', [ - coach.shared.progress(results.ratingDiff / results.nbGames), - m('h2', [ - m('strong', [ - opening.eco, - ' ', - opening.name - ]), - m('em', opening.moves) - ]), - m('div.baseline', [ - m('a', { - href: searchUrl - }, [ - m('strong', results.nbGames), - ' games' - ]), - ', ', - m('a', { - href: analysedUrl - }, [ - m('strong', results.nbAnalysis), - ' analysed.' - ]), - ' Last played ', - coach.shared.momentFromNow(results.lastPlayed), - '.', - ]) - ]), - m('div.content', [ - m('div.board', - chessground.view(ctrl.vm.inspecting.chessground) - ), - m('div.right', [ - // moves(ctrl, results), - sections(ctrl, results), - results.bestWin ? [ - m('br'), - ' Best win: ', - coach.bestWin(results.bestWin, ctrl.color) - ] : null - // m('table', [ - // m('tr', [ - // m('tr', [ - // m('th', 'Average opponent'), - // m('tr', m('strong', results.opponentRatingAvg)) - // ]), - // results.bestWin ? m('tr', [ - // m('th', 'Best win'), - // m('tr', bestWin(results.bestWin)) - // ]) : null - // ]) - ]) - ]) - ]); -}; diff --git a/ui/coachOpening/src/main.js b/ui/coachOpening/src/main.js deleted file mode 100644 index a655ca789d..0000000000 --- a/ui/coachOpening/src/main.js +++ /dev/null @@ -1,16 +0,0 @@ -var m = require('mithril'); - -var ctrl = require('./ctrl'); -var view = require('./view'); - -module.exports = function(element, opts) { - - var controller = new ctrl(opts); - - m.module(element, { - controller: function() { - return controller; - }, - view: view - }); -}; diff --git a/ui/coachOpening/src/moves.js b/ui/coachOpening/src/moves.js deleted file mode 100644 index c90ea68c15..0000000000 --- a/ui/coachOpening/src/moves.js +++ /dev/null @@ -1,133 +0,0 @@ -var m = require('mithril'); - -var green = '#759900', - red = '#dc322f', - orange = '#d59120', - grey = '#aaaaaa'; - -var MAX_MOVES = 30; - -function makeChart(el, data) { - $(el).highcharts({ - chart: { - spacing: [0, 0, 0, 0], - animation: { - duration: 300 - }, - backgroundColor: null, - borderWidth: 0, - borderRadius: 0, - plotBackgroundColor: null, - plotShadow: false, - plotBorderWidth: 0 - }, - title: { - text: null - }, - xAxis: { - crosshair: false - }, - yAxis: [{ - min: 0, - tickInterval: 50, - title: { - text: null - }, - labels: { - enabled: false - }, - lineWidth: 1, - gridLineWidth: 1 - }, { - min: 0, - tickInterval: 10, - title: { - text: null - }, - labels: { - enabled: false - }, - lineWidth: 1, - gridLineWidth: 1 - }], - plotOptions: { - column: { - pointPadding: 0.2, - borderWidth: 0 - } - }, - tooltip: { - useHTML: true, - formatter: function() { - if (this.point.acpl) - return '[move ' + this.point.x + '] ' + this.point.acpl.avg + ' centipawns
    ' + - 'over ' + this.point.acpl.nb + ' analysed games'; - return '[move ' + this.point.x + '] ' + (this.point.time.avg / 10) + ' seconds
    ' + - 'over ' + this.point.time.nb + ' games'; - }, - }, - series: [{ - name: 'ACPL', - type: 'column', - data: data.acpls, - pointWidth: 12 - - }, { - name: 'Time', - data: data.times, - type: 'spline', - yAxis: 1, - lineWidth: 1, - marker: { - radius: 2 - } - }], - credits: { - enabled: false - }, - legend: { - enabled: false - } - }); - return $(el).highcharts(); -} - -function makeData(results, moves) { - return { - acpls: results.nbAnalysis ? moves.map(function(move, i) { - var acpl = move.acpl.avg; - return { - x: i + 1, - y: Math.min(acpl, 150) + 10, - color: acpl < 50 ? green : (acpl < 100 ? orange : red), - acpl: move.acpl - }; - }) : [], - times: moves.map(function(move, i) { - var time = move.time.avg; - return { - x: i + 1, - y: Math.min(time, 30) + 1, - time: move.time - }; - }) - }; -} - -module.exports = function(ctrl, results) { - var moves = results.moves[ctrl.data.color].slice(0, MAX_MOVES); - var acpl = results.gameSections.all.acpl.avg; - var globalAcpl = ctrl.data.colorResults.gameSections.all.acpl.avg; - return [ - m('div.moves', { - config: function(el, isUpdate, ctx) { - var data = makeData(results, moves); - if (ctx.chart) { - ctx.chart.series[0].setData(data.acpls) - ctx.chart.series[1].setData(data.times) - } - else ctx.chart = makeChart(el, data); - } - }) - ]; -}; diff --git a/ui/coachOpening/src/piechart.js b/ui/coachOpening/src/piechart.js deleted file mode 100644 index c280db4899..0000000000 --- a/ui/coachOpening/src/piechart.js +++ /dev/null @@ -1,167 +0,0 @@ -var m = require('mithril'); - -function sortByY(arr) { - return arr.sort(function(a, b) { - return a.y < b.y; - }); -} - -function signed(i) { - return (i > 0 ? '+' : '') + i; -} - -function makeSeries(ctrl) { - var data = ctrl.data; - var percent = function(nb) { - return nb * 100 / data.openingResults.nbGames; - }; - var colors = Highcharts.getOptions().colors, - raw = data.families.sort(function(a, b) { - return a.results.nbGames < b.results.nbGames ? 1 : -1; - }).map(function(fam, index) { - var graphColor = colors[index % colors.length]; - var family = fam.family; - var results = fam.results; - var openings = family.ecos.map(function(eco) { - return data.openings[eco]; - }).sort(function(a, b) { - return a.results.nbGames < b.results.nbGames ? 1 : -1; - }); - return { - name: family.name, - y: percent(results.nbGames), - results: results, - color: graphColor, - drilldown: { - name: family.name, - data: openings.map(function(o) { - return { - name: o.opening.eco + ' ' + o.opening.name, - y: percent(o.results.nbGames), - opening: o.opening, - results: o.results - }; - }), - color: graphColor - } - }; - }), - familyData = [], - openingData = [], - i, - j, - drillDataLen; - - // Build the data arrays - for (i = 0; i < raw.length; i += 1) { - - familyData.push({ - name: raw[i].name, - y: raw[i].y, - results: raw[i].results, - color: raw[i].color - }); - - drillDataLen = raw[i].drilldown.data.length; - for (j = 0; j < drillDataLen; j += 1) { - var d = raw[i].drilldown.data[j]; - d.color = Highcharts.Color(raw[i].color).brighten(0.2 - (j / drillDataLen) / 3).get() - openingData.push(d); - } - } - return [familyData, openingData]; -} - -module.exports = { - update: function(chart, ctrl) { - var series = makeSeries(ctrl); - chart.series[0].setData(series[0]); - chart.series[1].setData(series[1]); - }, - create: function(el, ctrl) { - var series = makeSeries(ctrl); - - // Create the chart - $(el).highcharts({ - chart: { - type: 'pie', - animation: { - duration: 300 - }, - backgroundColor: null, - borderWidth: 0, - borderRadius: 0, - plotBackgroundColor: null, - plotShadow: false, - plotBorderWidth: 0 - }, - title: { - text: null - }, - yAxis: {}, - plotOptions: { - pie: { - shadow: false, - center: ['50%', '50%'], - animation: false, - point: {} - } - }, - tooltip: { - useHTML: true, - headerFormat: '{point.key}', - pointFormatter: function() { - var o = this.opening; - var r = this.results; - var acpl = r.gameSections.all.acpl.avg; - return ((o && o.formattedMoves) ? ('
    ' + o.formattedMoves + '

    ') : '') + '' + - '' + - '
    Games:' + - r.nbGames + '
    Rating:' + - signed(r.ratingDiff) + '
    ACPL:' + - (acpl === null ? '?' : acpl) + '
    '; - } - }, - series: [{ - name: 'First move', - data: series[0], - size: '60%', - dataLabels: { - formatter: function() { - return this.y > 3 ? this.point.name : null; - }, - color: 'white', - distance: -30, - }, - }, { - name: 'Openings', - data: series[1], - size: '90%', - innerSize: '60%', - dataLabels: { - formatter: function() { - return this.y > 1 ? this.point.name + ': ' + Math.round(this.y) + '%' : null; - } - }, - cursor: 'pointer', - point: { - events: { - click: function(e) { - if (e.point) { - ctrl.inspect(e.point.opening.eco); - m.redraw(); - } - } - } - } - }], - credits: { - enabled: false - }, - legend: { - enabled: false - } - }); - return $(el).highcharts(); - } -}; diff --git a/ui/coachOpening/src/sections.js b/ui/coachOpening/src/sections.js deleted file mode 100644 index a4db9e8e75..0000000000 --- a/ui/coachOpening/src/sections.js +++ /dev/null @@ -1,121 +0,0 @@ -var m = require('mithril'); - -var green = '#759900', - red = '#dc322f', - orange = '#d59120', - translucid = 'rgba(0,0,0,0.3)'; - -function makeChart(el, data) { - $(el).highcharts({ - chart: { - type: 'column', - spacing: [0, 0, 0, 0], - animation: { - duration: 300 - }, - backgroundColor: null, - borderWidth: 0, - borderRadius: 0, - plotBackgroundColor: null, - plotShadow: false, - plotBorderWidth: 0 - }, - title: { - text: null - }, - xAxis: { - tickWidth: 0, - categories: Object.keys(sectionNames).map(function(k) { - return sectionNames[k]; - }) - }, - yAxis: { - min: 0, - title: { - text: null - }, - labels: { - enabled: false - }, - lineWidth: 0, - gridLineWidth: 0 - }, - plotOptions: { - column: { - pointPadding: 0, - borderWidth: 0 - } - }, - tooltip: { - useHTML: true, - formatter: function() { - return this.point.name + '' + this.point.acpl.avg + ' centipawns
    ' + - 'over ' + this.point.acpl.nb + ' analysed games'; - }, - }, - series: [{ - name: 'ACPL', - data: data.acpls, - pointWidth: 80, - dataLabels: { - enabled: true, - format: '{point.y} ACPL' - } - }, { - name: 'Global', - data: data.globals, - color: 'rgba(0,0,0,.3)', - pointWidth: 8, - pointPlacement: 0.14 - }], - credits: { - enabled: false - }, - legend: { - enabled: false - } - }); - return $(el).highcharts(); -} - -var sectionKeys = ['opening', 'middle', 'end', 'all']; -var sectionNames = { - opening: 'Opening', - middle: 'Middlegame', - end: 'Endgame', - all: 'Overall' -}; - -function makeData(sections, isGlobal) { - return sectionKeys.map(function(key, i) { - var acpl = sections[key].acpl.avg; - return { - y: Math.min(acpl, 150) + 10, - color: isGlobal ? translucid : (acpl < 50 ? green : (acpl < 100 ? orange : red)), - acpl: sections[key].acpl, - name: isGlobal ? '[average] ' : '' - }; - }); -} - -module.exports = function(ctrl, results) { - if (!results.nbAnalysis) - return m('div.not_analysed', 'No analysis available on these games!') - var sections = results.gameSections; - var global = ctrl.data.openingResults.gameSections; - return [ - results.nbAnalysis ? m('h3', 'ACPL (Average centipawns lost) per section:') : null, - m('div.sections', { - config: function(el, isUpdate, ctx) { - var data = { - acpls: makeData(sections), - globals: makeData(global, true) - }; - if (ctx.chart) { - ctx.chart.series[0].setData(data.acpls) - ctx.chart.series[1].setData(data.globals) - } else ctx.chart = makeChart(el, data); - } - }) - ]; -}; diff --git a/ui/coachOpening/src/table.js b/ui/coachOpening/src/table.js deleted file mode 100644 index b16f0c871d..0000000000 --- a/ui/coachOpening/src/table.js +++ /dev/null @@ -1,74 +0,0 @@ -var m = require('mithril'); - -var coach = require('coach'); -var strings = coach.shared.strings; - -var headers = [ - ['name', 'Opening'], - ['nbGames', 'Games'], - ['result', 'Result', strings.result], - ['ratingDiffAvg', 'Rating', strings.ratingDiff], - ['acpl', 'ACPL', strings.acpl], - ['lastPlayed', 'Last played'] -]; - -function thead(list, ctrl) { - return m('thead', { - onclick: function(e) { - var prop = e.target.getAttribute("data-sort-by") || e.target.parentNode.getAttribute("data-sort-by"); - if (prop) ctrl.setSort(prop); - } - }, m('tr', headers.map(function(h) { - var props = { - key: h[0], - 'data-sort-by': h[0] - }; - if (ctrl.vm.sort.prop === h[0]) props['data-icon'] = ctrl.vm.sort.order === -1 ? 'R' : 'S'; - var spanProps = {}; - if (h[2]) { - spanProps.class = 'hint--top'; - spanProps['data-hint'] = h[2]; - } - return m('th', props, m('span', spanProps, h[1])); - }))); -} - -module.exports = function(ctrl) { - var d = ctrl.data; - var percent = function(nb) { - return Math.round(nb * 100 / d.openingResults.nbGames); - }; - var acplAvg = ctrl.data.colorResults.gameSections.all.acpl.avg; - return m('table.selector.slist', [ - thead(ctrl.list, ctrl), - m('tbody', ctrl.list.map(function(o, i) { - return m('tr', { - key: o.opening.eco, - onclick: function() { - ctrl.inspect(o.opening.eco); - }, - class: (ctrl.isInspecting(o.opening.eco) ? 'active' : '') - }, [ - m('td', [ - m('div.name', o.opening.name), - m('div.moves', o.opening.formattedMoves) - ]), - m('td', [ - m('div', o.results.nbGames + ' (' + percent(o.results.nbGames) + '%)') - ]), - m('td', coach.resultBar(o.results)), - m('td', coach.shared.progress(o.ratingDiffAvg)), - m('td', [ - m('span.progress', o.acpl === null ? '-' : m('span', { - class: o.acpl > acplAvg ? 'negative' : 'positive' - }, o.acpl)) - ]), - m('td', [ - m('time.moment-from-now', { - datetime: o.results.lastPlayed - }) - ]) - ]); - })) - ]); -}; diff --git a/ui/coachOpening/src/view.js b/ui/coachOpening/src/view.js deleted file mode 100644 index e0e0f832f4..0000000000 --- a/ui/coachOpening/src/view.js +++ /dev/null @@ -1,54 +0,0 @@ -var m = require('mithril'); - -var piechart = require('./piechart'); -var table = require('./table'); -var inspect = require('./inspect'); -var Slider = require('coach').slider; -var shared = require('coach').shared; - -module.exports = function(ctrl) { - if (!ctrl.nbPeriods) return m('div.content_box_top', [ - m('h1', [ - shared.userLink(ctrl.user.name), - ' openings as ', - ctrl.color, - ': ', - 'No data available' - ]), - ]); - return m('div', { - config: function() { - $('body').trigger('lichess.content_loaded'); - } - }, [ - m('div.content_box_top', { - class: 'content_box_top' + (ctrl.vm.loading ? ' loading' : '') - }, [ - ctrl.nbPeriods > 1 ? m.component(Slider, { - max: ctrl.nbPeriods, - range: ctrl.vm.range, - dates: ctrl.data ? [ctrl.data.from, ctrl.data.to] : null, - onChange: ctrl.selectPeriodRange - }) : null, - m('h1', [ - shared.userLink(ctrl.user.name), - ' openings as ', - ctrl.color, - ctrl.data ? m('div.over', [ - ' over ', - ctrl.data.colorResults.nbGames, - ' games' - ]) : null - ]), - ]), - ctrl.vm.preloading ? m('div.loader') : (!ctrl.data ? m('div.top.nodata', m('p', 'Empty period range!')) : [ - ctrl.vm.inspecting ? inspect(ctrl, ctrl.vm.inspecting) : m('div.top.chart', { - config: function(el, isUpdate, ctx) { - if (ctx.chart) piechart.update(ctx.chart, ctrl); - else ctx.chart = piechart.create(el, ctrl); - } - }), - table(ctrl) - ]) - ]); -}; diff --git a/ui/coach/gulpfile.js b/ui/insight/gulpfile.js similarity index 88% rename from ui/coach/gulpfile.js rename to ui/insight/gulpfile.js index 28eb5974b4..501f1c1cd4 100644 --- a/ui/coach/gulpfile.js +++ b/ui/insight/gulpfile.js @@ -11,14 +11,14 @@ var destination = '../../public/compiled/'; var onError = function(error) { gutil.log(gutil.colors.red(error.message)); }; -var standalone = 'LichessCoach'; +var standalone = 'LichessInsight'; gulp.task('prod', function() { return browserify('./src/main.js', { standalone: standalone }).bundle() .on('error', onError) - .pipe(source('lichess.coach.min.js')) + .pipe(source('lichess.insight.min.js')) .pipe(streamify(uglify())) .pipe(gulp.dest(destination)); }); @@ -28,7 +28,7 @@ gulp.task('dev', function() { standalone: standalone }).bundle() .on('error', onError) - .pipe(source('lichess.coach.js')) + .pipe(source('lichess.insight.js')) .pipe(gulp.dest(destination)); }); @@ -44,7 +44,7 @@ gulp.task('watch', function() { function rebundle() { return bundleStream.bundle() .on('error', onError) - .pipe(source('lichess.coach.js')) + .pipe(source('lichess.insight.js')) .pipe(gulp.dest(destination)); } diff --git a/ui/coach/package.json b/ui/insight/package.json similarity index 88% rename from ui/coach/package.json rename to ui/insight/package.json index bca1923a35..6f03f3fc8c 100644 --- a/ui/coach/package.json +++ b/ui/insight/package.json @@ -1,7 +1,7 @@ { - "name": "coach", + "name": "insight", "version": "1.0.0", - "description": "lichess.org coach deep chess insights", + "description": "lichess.org chess insights", "main": "src/main.js", "repository": { "type": "git", @@ -10,7 +10,7 @@ "keywords": [ "chess", "lichess", - "coach", + "insight", "analysis" ], "author": "ornicar", diff --git a/ui/coach/src/chart.js b/ui/insight/src/chart.js similarity index 100% rename from ui/coach/src/chart.js rename to ui/insight/src/chart.js diff --git a/ui/coach/src/ctrl.js b/ui/insight/src/ctrl.js similarity index 97% rename from ui/coach/src/ctrl.js rename to ui/insight/src/ctrl.js index 7d7f3cc30e..6dd3543a70 100644 --- a/ui/coach/src/ctrl.js +++ b/ui/insight/src/ctrl.js @@ -18,7 +18,7 @@ module.exports = function(env) { if (!this.validCombinationCurrent()) return; m.request({ method: 'post', - url: '/coach/data/' + this.userId, + url: '/insights/data/' + this.userId, data: { metric: this.vm.metric.key, dimension: this.vm.dimension.key, diff --git a/ui/coach/src/form.js b/ui/insight/src/form.js similarity index 100% rename from ui/coach/src/form.js rename to ui/insight/src/form.js diff --git a/ui/coach/src/main.js b/ui/insight/src/main.js similarity index 100% rename from ui/coach/src/main.js rename to ui/insight/src/main.js diff --git a/ui/coach/src/table.js b/ui/insight/src/table.js similarity index 100% rename from ui/coach/src/table.js rename to ui/insight/src/table.js diff --git a/ui/coach/src/throttle.js b/ui/insight/src/throttle.js similarity index 100% rename from ui/coach/src/throttle.js rename to ui/insight/src/throttle.js diff --git a/ui/coach/src/view.js b/ui/insight/src/view.js similarity index 87% rename from ui/coach/src/view.js rename to ui/insight/src/view.js index db6c8e8d3e..c9acdb2960 100644 --- a/ui/coach/src/view.js +++ b/ui/insight/src/view.js @@ -12,10 +12,10 @@ module.exports = function(ctrl) { m('div.refresh', { config: function(e, isUpdate) { if (isUpdate) return; - var $ref = $('.coach-stale'); + var $ref = $('.insight-stale'); if ($ref.length) { $(e).append($ref.show()); - lichess.refreshCoachForm(); + lichess.refreshInsightForm(); } } })