convert autoplay.js

This commit is contained in:
Niklas Fiekas 2017-04-06 00:47:02 +02:00
parent c31da78c4d
commit 7abb6195a8
9 changed files with 132 additions and 118 deletions

View file

@ -1,79 +0,0 @@
var control = require('./control');
var m = require('mithril');
module.exports = function(ctrl) {
var timeout;
this.delay = null;
var move = function() {
if (control.canGoForward(ctrl)) {
control.next(ctrl);
m.redraw();
return true;
}
this.stop();
m.redraw();
return false;
}.bind(this);
var evalToCp = function(node) {
if (!node.eval) return node.ply % 2 ? 990 : -990; // game over
return node.eval.mate ? 990 * Math.sign(node.eval.mate) : node.eval.cp;
};
var nextDelay = function() {
if (typeof(this.delay) === 'string') {
// in a variation
if (!ctrl.vm.onMainline) return 1500;
if (this.delay === 'realtime') {
if (ctrl.vm.node.ply < 2) return 1000;
var time = ctrl.data.game.moveCentis[ctrl.vm.node.ply - ctrl.tree.root.ply];
// estimate 130ms of lag to improve playback.
return time * 10 + 130 || 2000;
} else {
var slowDown = this.delay === 'cpl_fast' ? 10 : 30;
if (ctrl.vm.node.ply >= ctrl.vm.mainline.length - 1) return 0;
var currPlyCp = evalToCp(ctrl.vm.node);
var nextPlyCp = evalToCp(ctrl.vm.node.children[0]);
return Math.max(500,
Math.min(10000,
Math.abs(currPlyCp - nextPlyCp) * slowDown));
}
}
return this.delay;
}.bind(this);
var schedule = function() {
timeout = setTimeout(function() {
if (move()) schedule();
}, nextDelay());
}.bind(this);
var start = function(delay) {
this.delay = delay;
this.stop();
schedule();
}.bind(this);
this.stop = function() {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
}.bind(this);
this.toggle = function(delay) {
if (this.active(delay)) this.stop();
else {
if (!this.active())
if (!move()) ctrl.jump('');
start(delay);
}
}.bind(this);
this.active = function(delay) {
return (!delay || delay === this.delay) && !!timeout;
}.bind(this);
};

View file

@ -0,0 +1,86 @@
import { AnalyseController } from './interfaces';
import * as m from 'mithril';
import * as control from './control';
type Delay = number | 'realtime' | 'cpl_fast' | 'cpl_slow' | 'fast' | 'slow';
export default class Autoplay {
private ctrl: AnalyseController;
private timeout: number | undefined;
private delay: Delay | undefined;
constructor(ctrl: AnalyseController) {
this.ctrl = ctrl;
}
private move(): boolean {
if (control.canGoForward(this.ctrl)) {
control.next(this.ctrl);
m.redraw();
return true;
}
this.stop();
m.redraw();
return false;
}
private evalToCp(node: Tree.Node): number {
if (!node.eval) return node.ply % 2 ? 990 : -990; // game over
if (node.eval.mate) return (node.eval.mate > 0) ? 990 : -990;
return node.eval.cp!;
}
private nextDelay(): number {
if (typeof this.delay === 'string') {
// in a variation
if (!this.ctrl.vm.onMainline) return 1500;
if (this.delay === 'realtime') {
if (this.ctrl.vm.node.ply < 2) return 1000;
var time = this.ctrl.data.game.moveCentis![this.ctrl.vm.node.ply - this.ctrl.tree.root.ply];
// estimate 130ms of lag to improve playback.
return time * 10 + 130 || 2000;
} else {
var slowDown = this.delay === 'cpl_fast' ? 10 : 30;
if (this.ctrl.vm.node.ply >= this.ctrl.vm.mainline.length - 1) return 0;
var currPlyCp = this.evalToCp(this.ctrl.vm.node);
var nextPlyCp = this.evalToCp(this.ctrl.vm.node.children[0]);
return Math.max(500,
Math.min(10000,
Math.abs(currPlyCp - nextPlyCp) * slowDown));
}
}
return this.delay!;
}
private schedule(): void {
this.timeout = setTimeout(() => {
if (this.move()) this.schedule();
}, this.nextDelay());
}
start(delay: Delay): void {
this.delay = delay;
this.stop();
this.schedule();
}
stop(): void {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
}
toggle(delay: Delay): void {
if (this.active(delay)) this.stop();
else {
if (!this.active() && !this.move()) this.ctrl.jump('');
this.start(delay);
}
}
active(delay?: Delay): boolean {
return (!delay || delay === this.delay) && !!this.timeout;
}
}

View file

@ -2,7 +2,7 @@ var opposite = require('chessground/util').opposite;
var tree = require('tree');
var keyboard = require('./keyboard');
var actionMenu = require('./actionMenu').controller;
var autoplay = require('./autoplay');
var autoplay = require('./autoplay').default;
var promotion = require('./promotion');
var util = require('./util');
var chessUtil = require('chess');

View file

@ -1,3 +1,5 @@
import { GameData } from 'game';
export interface AnalyseController {
study?: Study;
studyPractice?: StudyPractice;
@ -6,6 +8,10 @@ export interface AnalyseController {
jumpToIndex(index: number): void;
userJumpIfCan(path: Tree.Path): void;
userJump(path: Tree.Path): void;
jump(path: Tree.Path): void;
data: GameData;
tree: any; // TODO: Tree.Tree;
}
export interface AnalyseOpts {

View file

@ -1,35 +1,35 @@
import { Data, Player } from './interfaces';
import { GameData, Player } from './interfaces';
import * as status from './status';
export function playable(data: Data): boolean {
export function playable(data: GameData): boolean {
return data.game.status.id < status.ids.aborted && !imported(data);
}
export function isPlayerPlaying(data: Data): boolean {
export function isPlayerPlaying(data: GameData): boolean {
return playable(data) && !data.player.spectator;
}
export function isPlayerTurn(data: Data): boolean {
export function isPlayerTurn(data: GameData): boolean {
return isPlayerPlaying(data) && data.game.player == data.player.color;
}
export function mandatory(data: Data): boolean {
export function mandatory(data: GameData): boolean {
return !!data.tournament || !!data.simul;
}
export function playedTurns(data: Data): number {
export function playedTurns(data: GameData): number {
return data.game.turns - data.game.startedAtTurn;
}
export function bothPlayersHavePlayed(data: Data): boolean {
export function bothPlayersHavePlayed(data: GameData): boolean {
return playedTurns(data) > 1;
}
export function abortable(data: Data): boolean {
export function abortable(data: GameData): boolean {
return playable(data) && !bothPlayersHavePlayed(data) && !mandatory(data);
}
export function takebackable(data: Data): boolean {
export function takebackable(data: GameData): boolean {
return playable(data) &&
data.takebackable &&
!data.tournament &&
@ -39,75 +39,75 @@ export function takebackable(data: Data): boolean {
!data.opponent.proposingTakeback;
}
export function drawable(data: Data): boolean {
export function drawable(data: GameData): boolean {
return playable(data) &&
data.game.turns >= 2 &&
!data.player.offeringDraw &&
!hasAi(data);
}
export function resignable(data: Data): boolean {
export function resignable(data: GameData): boolean {
return playable(data) && !abortable(data);
}
// can the current player go berserk?
export function berserkableBy(data: Data): boolean {
export function berserkableBy(data: GameData): boolean {
return !!data.tournament &&
data.tournament.berserkable &&
isPlayerPlaying(data) &&
!bothPlayersHavePlayed(data);
}
export function moretimeable(data: Data): boolean {
export function moretimeable(data: GameData): boolean {
return !!data.clock && isPlayerPlaying(data) && !mandatory(data);
}
export function imported(data: Data): boolean {
export function imported(data: GameData): boolean {
return data.game.source === 'import';
}
export function replayable(data: Data): boolean {
export function replayable(data: GameData): boolean {
return imported(data) || status.finished(data) ||
(status.aborted(data) && bothPlayersHavePlayed(data));
}
export function getPlayer(data: Data, color: Color): Player;
export function getPlayer(data: Data, color?: Color): Player | null {
export function getPlayer(data: GameData, color: Color): Player;
export function getPlayer(data: GameData, color?: Color): Player | null {
if (data.player.color == color) return data.player;
if (data.opponent.color == color) return data.opponent;
return null;
}
export function hasAi(data: Data): boolean {
export function hasAi(data: GameData): boolean {
return data.player.ai || data.opponent.ai;
}
export function userAnalysable(data: Data): boolean {
export function userAnalysable(data: GameData): boolean {
return playable(data) && (!data.clock || !isPlayerPlaying(data));
}
export function isCorrespondence(data: Data): boolean {
export function isCorrespondence(data: GameData): boolean {
return data.game.speed === 'correspondence';
}
export function setOnGame(data: Data, color: Color, onGame: boolean): void {
export function setOnGame(data: GameData, color: Color, onGame: boolean): void {
var player = getPlayer(data, color);
onGame = onGame || player.ai;
player.onGame = onGame;
if (onGame) setIsGone(data, color, false);
}
export function setIsGone(data: Data, color: Color, isGone: boolean): void {
export function setIsGone(data: GameData, color: Color, isGone: boolean): void {
var player = getPlayer(data, color);
isGone = isGone && !player.ai;
player.isGone = isGone;
if (!isGone && player.user) player.user.online = true;
}
export function nbMoves(data: Data, color: Color): number {
export function nbMoves(data: GameData, color: Color): number {
return Math.floor((data.game.turns + (color == 'white' ? 1 : 0)) / 2);
}
export function isSwitchable(data: Data): boolean {
export function isSwitchable(data: GameData): boolean {
return !hasAi(data) && (!!data.simul || isCorrespondence(data));
}

View file

@ -1,4 +1,4 @@
export interface Data {
export interface GameData {
game: Game;
player: Player;
opponent: Player;
@ -19,6 +19,7 @@ export interface Game {
speed: Speed;
variant: Variant;
winner?: Color;
moveCentis?: number[];
}
export interface Status {
@ -66,7 +67,7 @@ export interface User {
}
export interface Ctrl {
data: Data;
data: GameData;
trans: Trans;
}

View file

@ -1,7 +1,7 @@
/// <reference types="types/lichess" />
/// <reference types="types/mithril" />
import { GameView } from './interfaces';
import { GameData, GameView } from './interfaces';
import * as game from './game';
import * as status from './status';
@ -9,7 +9,7 @@ import * as router from './router';
import viewStatus from './view/status';
import * as viewMod from './view/mod';
export { game, status, router };
export { GameData, game, status, router };
export const view: GameView = {
status: viewStatus,

View file

@ -1,17 +1,17 @@
import { Data, ContinueMode } from './interfaces';
import { GameData, ContinueMode } from './interfaces';
export function player(data: Data): string {
export function player(data: GameData): string {
return '/' + data.game.id + data.player.id;
}
export function game(data: Data, color?: Color, embed?: boolean): string {
export function game(data: GameData, color?: Color, embed?: boolean): string {
return (embed ? '/embed/' : '/') + (data.game ? data.game.id : data) + (color ? '/' + color : '');
}
export function forecasts(data: Data): string {
export function forecasts(data: GameData): string {
return player(data) + '/forecasts';
}
export function cont(data: Data, mode: ContinueMode): string {
export function cont(data: GameData, mode: ContinueMode): string {
return game(data) + '/continue/' + mode;
}

View file

@ -1,4 +1,4 @@
import { Data } from './interfaces';
import { GameData } from './interfaces';
// https://github.com/ornicar/scalachess/blob/master/src/main/scala/Status.scala
@ -17,18 +17,18 @@ export const ids = {
variantEnd: 60
};
export function started(data: Data): boolean {
export function started(data: GameData): boolean {
return data.game.status.id >= ids.started;
}
export function finished(data: Data): boolean {
export function finished(data: GameData): boolean {
return data.game.status.id >= ids.mate;
}
export function aborted(data: Data): boolean {
export function aborted(data: GameData): boolean {
return data.game.status.id === ids.aborted;
}
export function playing(data: Data): boolean {
export function playing(data: GameData): boolean {
return started(data) && !finished(data) && !aborted(data);
}