192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
import config from './config';
|
|
import CurrentPuzzle from 'puz/current';
|
|
import makePromotion from 'puz/promotion';
|
|
import sign from 'puz/sign';
|
|
import throttle from 'common/throttle';
|
|
import { Api as CgApi } from 'chessground/api';
|
|
import { Clock } from 'puz/clock';
|
|
import { Combo } from 'puz/combo';
|
|
import { getNow, puzzlePov, sound } from 'puz/util';
|
|
import { makeCgOpts, onBadMove, onGoodMove } from 'puz/run';
|
|
import { parseUci } from 'chessops/util';
|
|
import { Promotion, Run } from 'puz/interfaces';
|
|
import { prop, Prop } from 'common';
|
|
import { RacerOpts, RacerData, RacerVm, RacerPrefs, Race, UpdatableData } from './interfaces';
|
|
import { Role } from 'chessground/types';
|
|
|
|
export default class StormCtrl {
|
|
private data: RacerData;
|
|
private redraw: () => void;
|
|
race: Race;
|
|
pref: RacerPrefs;
|
|
run: Run;
|
|
vm: RacerVm;
|
|
trans: Trans;
|
|
promotion: Promotion;
|
|
ground = prop<CgApi | false>(false) as Prop<CgApi | false>;
|
|
|
|
constructor(opts: RacerOpts, redraw: (data: RacerData) => void) {
|
|
this.data = opts.data;
|
|
this.race = this.data.race;
|
|
this.pref = opts.pref;
|
|
this.redraw = () => redraw(this.data);
|
|
this.trans = lichess.trans(opts.i18n);
|
|
this.run = {
|
|
pov: puzzlePov(this.data.puzzles[0]),
|
|
moves: 0,
|
|
errors: 0,
|
|
current: new CurrentPuzzle(0, this.data.puzzles[0]),
|
|
clock: new Clock(config),
|
|
history: [],
|
|
combo: new Combo(config),
|
|
modifier: {
|
|
moveAt: 0,
|
|
},
|
|
};
|
|
this.vm = {
|
|
signed: prop(undefined),
|
|
};
|
|
this.promotion = makePromotion(this.withGround, this.cgOpts, this.redraw);
|
|
if (this.data.key) setTimeout(() => sign(this.data.key!).then(this.vm.signed), 1000 * 40);
|
|
lichess.socket = new lichess.StrongSocket(`/racer/${this.race.id}`, false);
|
|
lichess.pubsub.on('socket.in.racerState', this.serverUpdate);
|
|
this.startCountdown();
|
|
this.simulate();
|
|
console.log(this.race);
|
|
}
|
|
|
|
serverUpdate = (data: UpdatableData) => {
|
|
this.data.players = data.players;
|
|
this.data.startsIn = data.startsIn;
|
|
this.startCountdown();
|
|
this.redraw();
|
|
};
|
|
|
|
players = () => this.data.players;
|
|
|
|
isPlayer = () => this.data.players.filter(p => p.name == this.data.player.name).length > 0;
|
|
|
|
canJoin = () => this.data.players.length < 10;
|
|
|
|
isRacing = () => !this.data.finished && this.vm.startsAt && this.vm.startsAt < new Date();
|
|
|
|
join = throttle(1000, () => {
|
|
if (!this.isPlayer()) lichess.pubsub.emit('socket.send', 'racerJoin');
|
|
});
|
|
|
|
private startCountdown = () => {
|
|
if (this.data.startsIn) {
|
|
this.vm.startsAt = new Date(Date.now() + this.data.startsIn);
|
|
const countdown = () => {
|
|
const diff = this.vm.startsAt.getTime() - Date.now();
|
|
if (diff > 0) setTimeout(countdown, (diff % 1000) + 100);
|
|
else {
|
|
this.run.clock.start();
|
|
this.withGround(g => g.set(this.cgOpts()));
|
|
}
|
|
this.redraw();
|
|
};
|
|
setTimeout(countdown);
|
|
}
|
|
};
|
|
|
|
countdownSeconds = (): number | undefined =>
|
|
this.vm.startsAt && this.vm.startsAt > new Date()
|
|
? Math.min(9, Math.ceil((this.vm.startsAt.getTime() - Date.now()) / 1000))
|
|
: undefined;
|
|
|
|
end = (): void => {
|
|
this.run.history.reverse();
|
|
this.run.endAt = getNow();
|
|
this.ground(false);
|
|
this.redraw();
|
|
sound.end();
|
|
this.redrawSlow();
|
|
};
|
|
|
|
endNow = (): void => {
|
|
this.pushToHistory(false);
|
|
this.end();
|
|
};
|
|
|
|
userMove = (orig: Key, dest: Key): void => {
|
|
if (!this.promotion.start(orig, dest, this.playUserMove)) this.playUserMove(orig, dest);
|
|
};
|
|
|
|
playUserMove = (orig: Key, dest: Key, promotion?: Role): void => {
|
|
this.run.moves++;
|
|
this.promotion.cancel();
|
|
const puzzle = this.run.current;
|
|
const uci = `${orig}${dest}${promotion ? (promotion == 'knight' ? 'n' : promotion[0]) : ''}`;
|
|
const pos = puzzle.position();
|
|
const move = parseUci(uci)!;
|
|
let captureSound = pos.board.occupied.has(move.to);
|
|
pos.play(move);
|
|
if (pos.isCheckmate() || uci == puzzle.expectedMove()) {
|
|
puzzle.moveIndex++;
|
|
onGoodMove(this.run);
|
|
lichess.pubsub.emit('socket.send', 'racerMoves', this.run.moves);
|
|
if (puzzle.isOver()) {
|
|
this.pushToHistory(true);
|
|
if (!this.incPuzzle()) this.end();
|
|
} else {
|
|
puzzle.moveIndex++;
|
|
captureSound = captureSound || pos.board.occupied.has(parseUci(puzzle.line[puzzle.moveIndex]!)!.to);
|
|
}
|
|
sound.move(captureSound);
|
|
} else {
|
|
lichess.sound.play('error');
|
|
this.pushToHistory(false);
|
|
onBadMove(config)(this.run);
|
|
if (this.run.clock.flag()) this.end();
|
|
else if (!this.incPuzzle()) this.end();
|
|
}
|
|
this.redraw();
|
|
this.redrawQuick();
|
|
this.redrawSlow();
|
|
this.withGround(g => g.set(this.cgOpts()));
|
|
lichess.pubsub.emit('ply', this.run.moves);
|
|
};
|
|
|
|
private redrawQuick = () => setTimeout(this.redraw, 100);
|
|
private redrawSlow = () => setTimeout(this.redraw, 1000);
|
|
|
|
private cgOpts = () => makeCgOpts(this.run, this.isRacing());
|
|
|
|
private pushToHistory = (win: boolean) =>
|
|
this.run.history.push({
|
|
puzzle: this.run.current.puzzle,
|
|
win,
|
|
millis: this.run.history.length ? getNow() - this.run.current.startAt : 0, // first one is free
|
|
});
|
|
|
|
private incPuzzle = (): boolean => {
|
|
const index = this.run.current.index;
|
|
if (index < this.data.puzzles.length - 1) {
|
|
this.run.current = new CurrentPuzzle(index + 1, this.data.puzzles[index + 1]);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
countWins = (): number => this.run.history.reduce((c, r) => c + (r.win ? 1 : 0), 0);
|
|
|
|
withGround = <A>(f: (cg: CgApi) => A): A | false => {
|
|
const g = this.ground();
|
|
return g && f(g);
|
|
};
|
|
|
|
private simulate = () => {
|
|
this.data.players = [];
|
|
for (let i = 0; i < 10; i++)
|
|
this.data.players.push({
|
|
name: `Player${i}`,
|
|
moves: 0,
|
|
});
|
|
setInterval(() => {
|
|
if (this.isRacing()) this.data.players[Math.floor(Math.random() * 10)].moves++;
|
|
this.redraw();
|
|
}, 150);
|
|
};
|
|
}
|