import { nextTick, watch, reactive, ref, computed, onMounted, onUnmounted } from "vue";
import { getQuery, setKirby } from "../../kirby.js";
import { state } from "../../stateMgr.js";
import { handleActive, ArrowVerticalKeyHandler, ArrowHorizontalKeyHandler, NumberKeyHandler } from "../../handlers.js";
import { overlayAndGet, powerStateMachine, overlayAndPop, popLastElem } from "../../componentPromise.js";
import { getGameProps, initGame, initStats, storeVisit, formatDate, removeLastVisit, extension, getFinalWinner } from "./logic.js";
const html = (v) => { return v[0] };
////////////////////////////////////////////////////////////////////////////////
// Components
//////////////////////////in//////////////////////////////////////////////////////
const pregame = {
props: ['page','active', 'stack'],
setup(props, context) {
handleActive(props, [ArrowVerticalKeyHandler]);
const selectPlayer = async (i) => {
if (props.active) {
const [result, error] = await overlayAndPop("d-playerSelect", { players: props.page.participants, class:"overlay"}, props.stack);
if (error === undefined) {
props.page.players[i] = result;
}
}
}
const selectX01 = async () => {
if (props.active) {
const [result, error] = await overlayAndPop("d-select", { options: [ "301", "501" ], class:"overlay"}, props.stack);
if (error === undefined) {
props.page.modus = result;
}
}
}
const selectIn = async () => {
if (props.active) {
const [result, error] = await overlayAndPop("d-select", { options: [ "Straight", "Double" ], class:"overlay"}, props.stack);
if (error === undefined) {
props.page.in = result;
}
}
}
return { selectPlayer, selectX01, selectIn }
},
template: html`
`
}
export const bullselect = {
props: ['players','active', 'stack'],
setup(props, { emit }) {
handleActive(props, [ArrowHorizontalKeyHandler, NumberKeyHandler]);
const children = computed(() => {
const items = [];
for (let i in props.players){
const player = props.players[i];
items.push({
component: "d-squareElem",
props: {
text: player.forename + " " + player.surname,
icon: player.img ? player.img : '/assets/img/placeholder_person.png'
},
onClick: () => {
emit("resolve", player);
}
})
}
return items;
});
return { children }
},
template: html`
Who won bull?
`
}
const score = {
props: ['page', 'justlegs', 'current_set_points', 'current_leg_points'],
setup(props) {
return { }
},
template: html`
{{ current_set_points[idx] }}
Best of {{ page.sets }}
Sets
{{ current_leg_points[idx] }}
Best of {{ page.legs }}
Legs
`
}
const player = {
props: ['page', 'id', 'current_stat'],
setup(props) {
const addStats = (stat1, stat2) => {
let res = {
average: [stat1.average[0]+stat2.average[0], stat1.average[1]+stat2.average[1]],
first9: [stat1.first9[0]+stat2.first9[0], stat1.first9[1]+stat2.first9[1]],
"60+": stat1["60+"]+stat2["60+"],
"100+": stat1["100+"]+stat2["100+"],
"140+": stat1["140+"]+stat2["140+"],
"180": stat1["180"]+stat2["180"],
"checkouts": [stat1.checkouts[0]+stat2.checkouts[0], stat1.checkouts[1]+stat2.checkouts[1]],
"checkoutPoints": [...stat1.checkoutPoints, ...stat2.checkoutPoints]
}
if ("checkins" in stat1 && "checkins" in stat2){
res["checkins"] = [stat1.checkins[0]+stat2.checkins[0], stat1.checkins[1]+stat2.checkins[1]];
res["checkinPoints"] = [...stat1.checkinPoints, ...stat2.checkinPoints];
}
return res;
}
const inspect = !!props.page.enddate;
const tourStats = computed(() => {
if (props.page.tournamentStats.length != 2){
return [
props.page.stats.stats[0],
props.page.stats.stats[1]
];
}
if (inspect) {
return [
props.page.tournamentStats[0],
props.page.tournamentStats[1]
];
}
return [
addStats(props.page.tournamentStats[0], props.page.stats.stats[0]),
addStats(props.page.tournamentStats[1], props.page.stats.stats[1])
];
});
const getAverage = (avg) => {
return avg && avg[1] != 0 ? ((3*avg[0])/avg[1]).toFixed(1) : "-";
}
const getCheckout = (checkout) => {
return checkout && checkout[1] != 0 ? Math.round(1000*checkout[0]/checkout[1])/10 : "- "
}
const getMax = (checkouts) => {
if (checkouts && checkouts.length != 0) {
return Math.max(...checkouts);
}
return "-"
}
const player = computed(() => props.page.players[props.id])
return { tourStats, getAverage, getCheckout, getMax, player }
},
template: html`
{{ player.forename }} {{ player.surname }}
{{ player.nickname }}
Avg:
{{ getAverage(tourStats[id].average) }}
{{ getAverage(page.stats.stats[id].average) }}
{{ getAverage(current_stat.stats[id].average) }}
First 9:
{{ getAverage(tourStats[id].first9) }}
{{ getAverage(page.stats.stats[id].first9) }}
{{ getAverage(current_stat.stats[id].first9) }}
60+:
{{ tourStats[id]["60+"] }}
{{ page.stats.stats[id]["60+"] }}
{{ current_stat.stats[id]["60+"] }}
100+:
{{ tourStats[id]["100+"] }}
{{ page.stats.stats[id]["100+"] }}
{{ current_stat.stats[id]["100+"] }}
140+:
{{ tourStats[id]["140+"] }}
{{ page.stats.stats[id]["140+"] }}
{{ current_stat.stats[id]["140+"] }}
180:
{{ tourStats[id]["180"] }}
{{ page.stats.stats[id]["180"] }}
{{ current_stat.stats[id]["180"] }}
Ch. I. %:
{{ getCheckout(tourStats[id].checkins) }}%
{{ getCheckout(page.stats.stats[id].checkins) }}%
{{ getCheckout(current_stat.stats[id].checkins) }}%
Best Ch.:
{{ getMax(tourStats[id].checkinPoints) }}
{{ getMax(page.stats.stats[id].checkinPoints) }}
{{ getMax(current_stat.stats[id].checkinPoints) }}
Ch. O. %:
{{ getCheckout(tourStats[id].checkouts) }}%
{{ getCheckout(page.stats.stats[id].checkouts) }}%
Best Ch.:
{{ getMax(tourStats[id].checkoutPoints) }}
{{ getMax(page.stats.stats[id].checkoutPoints) }}
`
}
const game = {
props: ['active', 'stack', 'players','current_leg', 'max'],
setup(props, context) {
const visits = computed(() => props.current_leg ? props.current_leg.visits:undefined );
const getPlayerVisits = (uuid) => {
const vs = visits.value.filter((v) => v.player == uuid);
if (vs.length < 9) {
for (var i = vs.length; i < 9; i++) {
vs.push({ "sum": "", "toGo":["",""]})
}
} else if (vs.length*2 < visits.value.length) {
vs.push({ "sum": "", "toGo":["",""]});
}
return vs;
}
return { getPlayerVisits }
},
template: html`
Points
ToGo
Round
Points
ToGo
{{ max }}
0
{{ max }}
{{ visit.sum }}
{{ visit.toGo[j-1] }}
{{ (i+1)*3 }}
`
}
const gameinput = {
props: ['input','active', 'stack'],
setup(props, context) {
handleActive(props, []);
const check_remove = (event) => {
if (!event.repeat && event.target.value.length < 1) {
event.preventDefault();
context.emit('reject', -2);
}
}
const keyhandler = (e) => {
if (e.key == "F1" || e.keyCode == 112) {
e.preventDefault();
context.emit('resolve', "60");
} else if (e.key == "F2" || e.keyCode == 113) {
e.preventDefault();
context.emit('resolve', "45");
} else if (e.key == "F3" || e.keyCode == 114) {
e.preventDefault();
context.emit('resolve', "41");
} else if (e.key == "F4" || e.keyCode == 115) {
e.preventDefault();
context.emit('resolve', "26");
}
}
return { check_remove, keyhandler }
},
template: html`
`
}
const xoi = {
props: ['page', 'active', 'stack', 'inspect'],
components: {
"d-player": player,
"d-score": score,
"d-game": game
},
setup(props, context) {
const gamestack = reactive([]);
const inspectstack = reactive([]);
let current_set, current_leg;
const set_id = ref(props.page.game.sets.length - 1);
const leg_id = ref(props.page.game.sets[set_id.value].legs.length - 1);
let current_stat;
if (props.inspect) {
current_set = computed(() => props.page.game.sets[set_id.value]);
current_leg = computed(() => current_set.value.legs[leg_id.value]);
current_stat = computed( () => props.page.stats?.sets[set_id.value].legs[leg_id.value]);
}
const computedProps = getGameProps(props.page, current_set, current_leg);
if (!props.inspect) {
current_stat = computed( () => props.page.stats?.sets[props.page.stats.sets.length-1].legs[computedProps.current_set.value.legs.length-1]);
}
const mounted = onMounted(async () => {
if (props.inspect) {
} else {
const winner = await gameHandler(gamestack, props.stack, props.page, computedProps)
context.emit('resolve', winner);
}
})
return { ...computedProps, set_id, leg_id, gamestack, inspectstack, current_stat }
},
template: html`
{{ current_toGo[0] }}
{{ current_toGo[1] }}
Set:Leg:
Shortkeys:60 (F1)45 (F2)41 (F3)26 (F4)
`
}
////////////////////////////////////////////////////////////////////////////////
// Dialogs
////////////////////////////////////////////////////////////////////////////////
const numDartsDialog = () => {
const buttons = [];
for (var i = 1; i <= 3; i++) {
buttons.push({
"component": "d-plainElem",
"props" : {
"text": `${i}`
},
"result" : i
})
}
return {
"withshortkey": true,
"title": "Congratulations!",
"text": `How many darts did you need?`,
"buttons": buttons
};
}
const numCheckoutTriesDialog = (numDarts, start=1) => {
const buttons = [];
for (var i = start; i <= numDarts; i++) {
buttons.push({
"component": "d-plainElem",
"props" : {
"text": `${i}`,
"autofocus": i == 0,
"data-tabindex": i
},
"result" : i
})
}
return {
"withshortkey": true,
"title": "Checkout Tries",
"text": `How many tries on a Checkout?` ,
"buttons": buttons
};
}
const numCheckinTriesDialog = (numDarts, start=1) => {
const buttons = [];
for (var i = start; i <= numDarts; i++) {
buttons.push({
"component": "d-plainElem",
"props" : {
"text": `${i}`,
"autofocus": i == 0,
"data-tabindex": i
},
"result" : i
})
}
return {
"withshortkey": true,
"title": "Checkin Tries",
"text": `How many tries on a Checkin?` ,
"buttons": buttons
};
}
const impossibleDialog = (sum) => {
return {
"withshortkey": true,
"title": "Impossible",
"text": `A score of ${sum} is not possible`,
"buttons": [{
"component": "d-plainElem",
"props" : {
"text": "ok"
},
"result" : "ok"
}]}
}
const gameOverDialog = (winner, points, breaks) => {
return {
"withshortkey": true,
"title": `Game Over`,
"text": `${winner != "DRAW" ? "The winner is": ""} ${winner} with ${points[0]}-${points[1]} (Breaks: ${breaks[0]}-${breaks[1]})`,
"buttons": [
{
"component": "d-plainElem",
"props" : {
"text": "End Game"
},
"result" : "end"
},{
"component": "d-plainElem",
"props" : {
"text": "Continue"
},
"result" : "continue"
}]
}
}
////////////////////////////////////////////////////////////////////////////////
// API
////////////////////////////////////////////////////////////////////////////////
function getGame(id){
return getQuery(`site.find('${id}')`, {
select: {
title: "page.title",
id: "page.id",
modus: "page.max.toInt",
in: "page.in",
out: "page.out",
game: "page.rounds.parseJSON",
stats: "page.stats.parseJSON",
sets: "page.sets.toInt",
legs: "page.legs.toInt",
startdate: "page.Startdate",
enddate: "page.Enddate",
tournamentStats: "page.tournamentStats",
participants: {
query: "page.parent.participants.toPages.sortBy('forename')",
select:{
forename: "page.forename",
surname: "page.surname",
nickname: "page.nickname",
uuid: "page.uuid",
img: "page.pic.toFile?.url"
}
},
players: {
query: "page.players.toPages",
select:{
forename: "page.forename",
surname: "page.surname",
nickname: "page.nickname",
uuid: "page.uuid",
img: "page.pic.toFile?.thumbnail(350).url"
}
}
}
})
}
function savePregame(page){
return setKirby(page.id, {
sets: page.sets,
legs: page.legs,
max: page.modus,
in: page.in,
players: page.players.map((p) => p.uuid),
startdate: page.startdate,
rounds: page.game ? JSON.stringify(page.game) : "",
stats: page.stats ? JSON.stringify(page.stats) : ""
});
}
function saveGame(page){
return setKirby(page.id, {
rounds: page.game ? JSON.stringify(page.game) : "",
stats: page.stats ? JSON.stringify(page.stats) : "",
enddate: page.enddate,
});
}
////////////////////////////////////////////////////////////////////////////////
// State Machine
////////////////////////////////////////////////////////////////////////////////
// Checkout Pipeline
const checkoutPipeline = (stack) => {
return {
0: async (sum, reGet) => {
// Ask for num Darts
const [numDarts, error] = await overlayAndGet("d-dialog", numDartsDialog(), stack, reGet);
if (error != undefined) {
popLastElem(stack);
return [undefined, [-1, -1]];
}
return [1, [numDarts, 1]];
},
1: async ([numDarts, start], reGet) => {
// Ask for checkoutTries
if (numDarts <= start) {
// Store with one checkout try
popLastElem(stack);
return [undefined, [1, 1]];
}
const [tries, error] = await overlayAndPop("d-dialog", numCheckoutTriesDialog(numDarts, start), stack);
if (error != undefined) {
if (start == 0) {
return [undefined, [-1, -1]];
}
return [-1, undefined];
}
if (start == 1) {
popLastElem(stack);
}
return [undefined, [numDarts, tries]];
}
};
};
// Game State Machine
const gameStateMachine = (gamestack, stack, page, computedProps) => {
return {
0: async (input, reGet) => {
// Dispatcher
if (page.stats.winner){
return [undefined, page.stats.winner];
}
// Check for Game State
return [1, undefined];
},
1: async (input, reGet) => {
// Normal Game Loop
// Get Game Input
const [visit, error] = await overlayAndPop("d-gameinput", { input: input }, gamestack);
// back/delete last throw
if (error != undefined) {
const val = removeLastVisit(page);
saveGame(page);
if (val == undefined) return [1, val];
return [1, val.join(",")];
}
// Validate throw
let numDarts, tries;
const [ret, sum] = computedProps.checkVisit(visit);
if (ret == -1) {
// Impossible
const [_, error] = await overlayAndPop("d-dialog", impossibleDialog(sum), stack);
return [1, visit];
} else if (ret == -2) {
// Bust TODO
const [_, error] = await overlayAndPop("d-dialog", impossibleDialog(sum), stack);
return [1, visit];
} else if (ret == 3) {
// Checkins
const [ret, error] = await overlayAndPop("d-dialog", numCheckinTriesDialog(3), stack);
if (error) {
return [1, visit];
}
storeVisit(page, visit.split(","), sum, 3, 0, ret);
saveGame(page);
return [1, undefined]
} else if (ret == 2) {
// Normal
storeVisit(page, visit.split(","), sum, 3, 0);
saveGame(page);
return [1, undefined]
}
// checkout:
if (ret == 0) {
// Checkout: Ask for num Darts
[numDarts, tries] = await powerStateMachine(checkoutPipeline(stack), stack, /*initState=*/0, /*initInput=*/sum);
} else if (ret == 1) {
// <=50: Ask for checkout tries
[numDarts, tries] = await powerStateMachine(checkoutPipeline(stack), stack, /*initState=*/1, /*initInput=*/[3,0]);
}
if (numDarts == -1) {
// Error/Back
return [1, visit];
} else {
const points = storeVisit(page, visit.split(","), sum, numDarts, tries);
if (points){
// Game Over
const winner = getFinalWinner(page);
let name = "DRAW"
if (winner != -2){
name = page.players[winner].forename;
}
const [answer, error] = await overlayAndPop("d-dialog", gameOverDialog(name, points[1], computedProps.breaks.value), stack);
if (answer == "end") {
saveGame(page);
return [undefined, page.stats.winner];
} else {
extension(page);
saveGame(page);
}
} else {
saveGame(page);
}
return [1, undefined];
}
},
};
}
export const gameHandler = async (gamestack, stack, page, computedProps) => {
const sm = gameStateMachine(gamestack, stack, page, computedProps);
return powerStateMachine(sm, gamestack);
}
// General State Machine
const stateMachine = (stack, page) => {
return {
0: async (input, reGet) => {
// Dispatcher
// Check Game State:
if (page.players.length != 2 || page.startdate === undefined || page.startdate === ""){
// Pre Game
return [1, page];
} else if (page.enddate === undefined || page.enddate === ""){
if (page.game === "" || page.game === undefined || page.game === null) {
// Who won Bull?
return [2, page];
} else {
// In Game
return [3, page];
}
} else {
// Post Game
return [4, undefined];
}
},
1: async (page, reGet) => {
// Pre Game
const [result, error] = await overlayAndPop("d-pregame", { page: page}, stack);
if (error != undefined) {
var re = /^https?:\/\/[^/]+/i;
window.setTimeout(() => {
window.location.href = re.exec(window.location.href)[0];
return false;
},1);
return [undefined, undefined];
}
page.startdate = formatDate(new Date(Date.now()));
// Update game in database
const ret = await savePregame(page);
if (ret.status != "ok") {
console.error("Error save page:", ret.status, ret.error);
}
return [0, page];
},
2: async (page, reGet) => {
// Ask for Bull
const [result, error] = await overlayAndPop("d-bullselect", { players: page.players}, stack);
if (error != undefined) {
return [1, page];
}
// reorderPlayer
if (result !== page.players[0]){
page.players = [page.players[1], page.players[0]];
}
// Setup Game
initGame(page);
initStats(page);
const ret = await savePregame(page);
return [3, page];
},
3: async (page, reGet) => {
// In Game
const [result, error] = await overlayAndPop("d-xoi", { page: page, inspect: false }, stack);
return [4, result];
},
4: async (winnerUUID, reGet) => {
const [res, e] = await overlayAndPop("d-xoi", { page: page, inspect: true }, stack);
var re = /^https?:\/\/[^/]+/i;
window.setTimeout(() => {
window.location.href = re.exec(window.location.href)[0];
return false;
},1);
return [4, res];
}
};
}
////////////////////////////////////////////////////////////////////////////////
// Exports
////////////////////////////////////////////////////////////////////////////////
export const initXoiView = (app) => {
app.component('d-pregame', pregame).component('d-xoi', xoi).component('d-bullselect', bullselect).component('d-gameinput', gameinput)
}
export const xoiHandler = async (stack, id) => {
const page = reactive(await getGame(id));
const sm = stateMachine(stack, page);
await powerStateMachine(sm, stack);
}