You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
499 lines
16 KiB
499 lines
16 KiB
import { nextTick, reactive, ref, computed, onMounted, onUnmounted } from "vue";
|
|
import { state } from "../stateMgr.js";
|
|
import { getQuery } from "../kirby.js";
|
|
import { initSubState, ArrowVerticalKeyHandler, ArrowHorizontalKeyHandler, NumberKeyHandler } from "../handlers.js";
|
|
|
|
|
|
const html = (v) => { return v[0] };
|
|
|
|
function initView(view){
|
|
const back = state.view;
|
|
state.back.push(() => { state.view = back });
|
|
state.backActive.push(state.activeIndex);
|
|
state.view = view;
|
|
}
|
|
|
|
const togo = {
|
|
props: ['togo'],
|
|
setup(props) {
|
|
|
|
return { }
|
|
},
|
|
template: html`
|
|
<div class="bigToGo">{{ togo }}</div>
|
|
`
|
|
}
|
|
|
|
const game = {
|
|
props: ['players', 'currentleg', 'max', 'sendvisit', 'modelValue', 'overlay'],
|
|
setup(props, context) {
|
|
const length = computed(() => props.currentleg?.visits.length );
|
|
const visits = computed(() => props.currentleg? props.currentleg.visits:undefined );
|
|
const loop = computed(() => Math.max(length.value?length.value+(length.value?length.value%2:0):0,18) );
|
|
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;
|
|
}
|
|
const check_remove = (event) => {
|
|
if (!event.repeat && event.target.value.length < 1) {
|
|
context.emit('removeLastVisit', event.target);
|
|
}
|
|
}
|
|
const setup = ref(false);
|
|
const setupEnter = () => {
|
|
setup.value = true;
|
|
}
|
|
const confirmEnter = (evt) => {
|
|
if (setup.value) {
|
|
context.emit('sendvisit', evt.target)
|
|
}
|
|
setup.value = false;
|
|
}
|
|
return { length, visits, loop, getPlayerVisits, check_remove, confirmEnter, setupEnter }
|
|
},
|
|
template: html`
|
|
<div class="game">
|
|
<div class="headding">
|
|
<div class="headding points player1">Points</div>
|
|
<div class="headding toGo player1">ToGo</div>
|
|
<div class="headding rounds">Round</div>
|
|
<div class="headding points player2">Points</div>
|
|
<div class="headding toGo player2">ToGo</div>
|
|
</div>
|
|
<div class="body">
|
|
<div class="points player1"></div>
|
|
<div class="toGo player1">{{ max }}</div>
|
|
<div class="rounds">0</div>
|
|
<div class="points player2"></div>
|
|
<div class="toGo player2">{{ max }}</div>
|
|
<template v-for="j in [1,2]" v-if="players">
|
|
<template v-for="visit, i in getPlayerVisits(players[j-1].uuid)">
|
|
<template v-if="visit.toGo === undefined">
|
|
<div :class="'points player'+j+' input'"><input v-autofocus :value="modelValue"
|
|
@input="$emit('update:modelValue', $event.target.value)" @keyup.enter="confirmEnter($event)" @keydown.enter="setupEnter($event)" @keydown.backspace="check_remove($event)" v-if="!overlay"></div>
|
|
<div :class="'toGo player'+j"></div>
|
|
</template>
|
|
<template v-if="visit.toGo !== undefined">
|
|
<div :class="'points player'+j">{{ visit.sum }}</div>
|
|
<div :class="'toGo player'+j">{{ visit.toGo[j-1] }}</div>
|
|
</template>
|
|
<div class="rounds" v-if="j == 1">{{ (i+1)*3 }}</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
const overlay = {
|
|
props: ['data'],
|
|
setup(props, context) {
|
|
const keyhandler = (event) => {
|
|
ArrowHorizontalKeyHandler(event);
|
|
NumberKeyHandler(event);
|
|
if (event.key == "Escape") {
|
|
context.emit("closeOverlay")
|
|
}
|
|
}
|
|
return { keyhandler }
|
|
},
|
|
template: html`
|
|
<div @keyup="keyhandler" class="overlay" v-if="data !== undefined" :class="{active: data.title != '' && data.text != ''}">
|
|
<div class="box">
|
|
<h1>{{ data.title }}</h1>
|
|
<p>{{ data.text }}</p>
|
|
<div class="buttons">
|
|
<component v-for="(item, index) in data.buttons" :is="item.type" v-index="index+1" @click="item.onClick" :ref="(el) => { item.ref = el }" v-bind="item.props" v-autofocus="item.autofocus"></component>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
const score = {
|
|
props: ['page', 'justlegs', 'currentset', 'currentleg'],
|
|
setup(props) {
|
|
return { }
|
|
},
|
|
template: html`
|
|
<div class="score">
|
|
<div v-if="!justlegs" class="sets">
|
|
<h2 v-for="(idx) in [0,1]">{{ currentset?.points[idx] }}</h2>
|
|
<div class="info">
|
|
<h3>Best of {{ page?.sets }}</h3>
|
|
<h2>Sets</h2>
|
|
</div>
|
|
</div>
|
|
<div class="legs">
|
|
<h2 v-for="(idx) in [0,1]">{{ currentleg?.points[idx] }}</h2>
|
|
<div class="info">
|
|
<h3>Best of {{ page?.legs }}</h3>
|
|
<h2>Legs</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
const player = {
|
|
props: ['player', 'stats', "id"],
|
|
setup(props) {
|
|
const current_set = computed( () => props.stats?.sets[props.stats.sets.length-1]);
|
|
const current_leg = computed( () => current_set.value?.legs[current_set.value.legs.length-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 "-"
|
|
}
|
|
return { current_set, current_leg, getAverage, getCheckout, getMax }
|
|
},
|
|
template: html`
|
|
<div class="player">
|
|
<img style="width: 100%" :src="player?.img? player.img : '/assets/img/placeholder_person.png'">
|
|
<h2 class="name">{{ player?.forename }} {{ player?.surname }}</h2>
|
|
<h3 class="nickname">{{ player?.nickname }}</h3>
|
|
<div class="stats">
|
|
<div class="row header">
|
|
<div>Stat</div><div>Match</div><div>Leg</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>Average:</div><div>{{ getAverage(stats?.stats[id].average) }}</div><div>{{ getAverage(current_leg?.stats[id].average) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>First 9:</div><div>{{ getAverage(stats?.stats[id].first9) }}</div><div>{{ getAverage(current_leg?.stats[id].first9) }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>60+:</div><div>{{ stats?.stats[id]["60+"] }}</div><div>{{ current_leg?.stats[id]["60+"] }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>100+:</div><div>{{ stats?.stats[id]["100+"] }}</div><div>{{ current_leg?.stats[id]["100+"] }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>140+:</div><div>{{ stats?.stats[id]["140+"] }}</div><div>{{ current_leg?.stats[id]["140+"] }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>180:</div><div>{{ stats?.stats[id]["180"] }}</div><div>{{ current_leg?.stats[id]["180"] }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div>Checkouts:</div><div>{{ getCheckout(stats?.stats[id].checkouts) }}%</div><div></div>
|
|
</div>
|
|
<div class="row">
|
|
<div>Best Checkout:</div><div>{{ getMax(stats?.stats[id].checkoutPoints) }}</div><div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
|
|
export const xoi = {
|
|
components: {
|
|
"d-togo": togo,
|
|
"d-score": score,
|
|
"d-game": game,
|
|
"d-player": player,
|
|
"d-overlay": overlay
|
|
},
|
|
setup(props, context) {
|
|
let page = ref();
|
|
const updateGame = (reset) => {
|
|
getQuery(`site.find('${state.id}')`, {
|
|
select: {
|
|
title: "page.title",
|
|
id: "page.id",
|
|
modus: "page.max",
|
|
game: "page.rounds.parseJSON",
|
|
stats: "page.stats.parseJSON",
|
|
sets: "page.sets",
|
|
legs: "page.legs",
|
|
players: {
|
|
query: "page.players.toPages",
|
|
select:{
|
|
forename: "page.forename",
|
|
surname: "page.surname",
|
|
nickname: "page.nickname",
|
|
uuid: "page.uuid",
|
|
img: "page.pic.toFile?.url"
|
|
}
|
|
}
|
|
}
|
|
}).then((res) => {
|
|
page.value = res;
|
|
if (reset != undefined) {
|
|
current_input.value = reset;
|
|
}
|
|
if (res.stats.winner){
|
|
console.log(res.stats);
|
|
let winner = "Draw";
|
|
if (res.players[0].uuid == res.stats.winner){
|
|
winner = res.players[0].forename;
|
|
} else if (res.players[1].uuid == res.stats.winner) {
|
|
winner = res.players[1].forename;
|
|
}
|
|
overlay.value = {
|
|
"title": "Game Ended",
|
|
"text": `The winner is ${winner}`,
|
|
"buttons": []
|
|
};
|
|
}
|
|
});
|
|
};
|
|
updateGame();
|
|
|
|
let game = computed(() => page.value ? page.value.game: undefined)
|
|
let current_set = computed(() => {
|
|
if (game.value != undefined) {
|
|
return game.value.sets[game.value.sets.length-1]
|
|
}
|
|
return undefined;
|
|
})
|
|
let current_leg = computed(() => {
|
|
if (current_set.value != undefined) {
|
|
return current_set.value.legs[current_set.value.legs.length-1]
|
|
}
|
|
return undefined;
|
|
})
|
|
let current_toGo = computed(() => {
|
|
if (current_leg.value != undefined) {
|
|
if (current_leg.value.visits.length < 2) {
|
|
return [page.value.modus, page.value.modus]
|
|
} else {
|
|
|
|
return current_leg.value.visits[current_leg.value.visits.length-2].toGo;
|
|
}
|
|
}
|
|
return [0,0];
|
|
})
|
|
let current_set_points = computed(() => {
|
|
if (current_set.value != undefined) {
|
|
return current_set.value.points;
|
|
}
|
|
return undefined;
|
|
});
|
|
let current_leg_points = computed(() => {
|
|
if (current_leg.value != undefined) {
|
|
return current_leg.value.points;
|
|
}
|
|
return undefined;
|
|
});
|
|
let current_player = computed(() => {
|
|
if (current_leg.value != undefined) {
|
|
const len = current_leg.value.visits.length;
|
|
return current_leg.value.visits[len-1].player == page.value.players[1].uuid;
|
|
}
|
|
return undefined;
|
|
});
|
|
const current_input = ref("");
|
|
const overlay = ref();
|
|
return { page, state, game, current_set, current_leg, current_toGo, current_set_points, current_leg_points, current_player, updateGame, current_input, overlay }
|
|
},
|
|
methods: {
|
|
async removeLastVisit(){
|
|
let last = this.current_leg.visits.length-2 >= 0 ? this.current_leg.visits[this.current_leg.visits.length-2].throws : [""];
|
|
last = last.join(",");
|
|
const response = await fetch(`/${state.id}`, {
|
|
method: "POST",
|
|
cache: "no-cache",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
"action": "deleteLastThrow",
|
|
"visit": true,
|
|
}),
|
|
});
|
|
const ret = await response.json();
|
|
if (ret.status == "ok"){
|
|
this.updateGame(last);
|
|
} else {
|
|
console.log(ret);
|
|
}
|
|
},
|
|
sum(tr){
|
|
const val = tr.trim();;
|
|
if (val == "") {
|
|
return 0;
|
|
}
|
|
if (val == "SB"){
|
|
return 25;
|
|
}
|
|
if (val == "DB"){
|
|
return 50;
|
|
}
|
|
if (val[0] == "S" || val[0] == "O" || val[0] == "I"){
|
|
return parseFloat(val.substring(1));
|
|
}
|
|
if (val[0] == "D"){
|
|
return 2*parseFloat(val.substring(1));
|
|
}
|
|
if (val[0] == "T"){
|
|
return 3*parseFloat(val.substring(1));
|
|
}
|
|
if (val[0] == "M"){
|
|
return 0;
|
|
} else {
|
|
// TODO: Check for Na
|
|
return parseFloat(val);
|
|
}
|
|
},
|
|
verify(sum){
|
|
if (this.current_toGo[this.current_player*1]-sum == 0){
|
|
return 0;
|
|
} else if (sum > 180 || [179, 178, 176, 175, 173, 172, 169, 166, 163].indexOf(sum) > -1) {
|
|
return -1
|
|
} else if (this.current_toGo[this.current_player*1]-sum <= 50) {
|
|
return 1
|
|
}
|
|
},
|
|
openOverlay(overlay){
|
|
this.overlay = overlay;
|
|
},
|
|
closeOverlay(){
|
|
this.overlay = undefined;
|
|
},
|
|
checkout_question(throws){
|
|
const buttons = [];
|
|
for (var i = 1; i <= 3; i++) {
|
|
const x = i;
|
|
buttons.push({
|
|
"type": "input",
|
|
"props" : {
|
|
"type": "button",
|
|
"value": `${x} (${x})`
|
|
},
|
|
"onClick": () => {
|
|
this.closeOverlay();
|
|
this.checkouttries_question(throws, x, false);
|
|
}
|
|
})
|
|
}
|
|
this.openOverlay({
|
|
"title": "Congratulations!",
|
|
"text": `How many darts did you need?` ,
|
|
"buttons": buttons
|
|
});
|
|
},
|
|
checkouttries_question(throws, numDarts, zero=true){
|
|
const buttons = [];
|
|
for (var i = 1; i <= numDarts; i++) {
|
|
const x = i;
|
|
buttons.push({
|
|
"type": "input",
|
|
"props" : {
|
|
"type": "button",
|
|
"value": `${x} (${x})`
|
|
},
|
|
"autofocus": (i==1 && !zero),
|
|
"onClick": () => {
|
|
this.closeOverlay();
|
|
this.send_visit(throws, numDarts, x)
|
|
}
|
|
})
|
|
}
|
|
if (zero) {
|
|
buttons.push({
|
|
"type": "input",
|
|
"props" : {
|
|
"type": "button",
|
|
"value": `0 (${numDarts+1})`
|
|
},
|
|
"autofocus": true,
|
|
"onClick": () => {
|
|
this.closeOverlay();
|
|
this.send_visit(throws, numDarts, 0)
|
|
}
|
|
})
|
|
}
|
|
this.openOverlay({
|
|
"title": "Checkout Tries",
|
|
"text": `How many tries on a Checkout?` ,
|
|
"buttons": buttons
|
|
});
|
|
},
|
|
async send_visit(throws, numDarts=3, checkoutTries=0){
|
|
const response = await fetch(`/${state.id}`, {
|
|
method: "POST",
|
|
cache: "no-cache",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
"action": "addThrows",
|
|
"throws": throws.value.split(","),
|
|
"checkoutTries": checkoutTries,
|
|
"numDarts": numDarts,
|
|
"done": true
|
|
}),
|
|
});
|
|
const ret = await response.json();
|
|
if (ret.status == "ok"){
|
|
this.updateGame("");
|
|
} else {
|
|
console.log(ret);
|
|
}
|
|
},
|
|
async preprocess_visit(throws){
|
|
const tr = throws.value.split(",");
|
|
let sum = 0;
|
|
tr.forEach((t, i) => {
|
|
sum += this.sum(t);
|
|
});
|
|
const res = this.verify(sum);
|
|
if (res == 0){
|
|
// Ask for num Darts
|
|
this.checkout_question(throws);
|
|
return;
|
|
} else if (res == 1){
|
|
// Ask for checkoutTries
|
|
this.checkouttries_question(throws, 3);
|
|
return;
|
|
} else if (res == -1){
|
|
this.openOverlay({
|
|
"title": "Impossible",
|
|
"text": `A score of ${sum} is not possible`,
|
|
"buttons": [{
|
|
"type": "input",
|
|
"props" : {
|
|
"type": "button",
|
|
"value": "ok"
|
|
},
|
|
"onClick" : this.closeOverlay
|
|
}]
|
|
});
|
|
return;
|
|
}
|
|
this.send_visit(throws);
|
|
}
|
|
},
|
|
template: html`
|
|
<div class="xoi">
|
|
<d-overlay @closeOverlay="closeOverlay" :data="overlay"></d-overlay>
|
|
<d-togo :togo="current_toGo[0]" class="one" :class="{'active' : current_player==0 }"></d-togo>
|
|
<d-togo :togo="current_toGo[1]" class="two" :class="{'active' : current_player==1 }"></d-togo>
|
|
<d-score :page="page" :justlegs="page && page.sets == 1" :currentset="current_set" :currentleg="current_leg"></d-score>
|
|
<d-game :overlay="overlay!=undefined" :players="page?.players" :max="page?.modus" @sendvisit="preprocess_visit" @removeLastVisit="removeLastVisit" :currentleg="current_leg" v-model="current_input"></d-game>
|
|
<d-player class="player1" :player="page?.players[0]" :stats="page?.stats" :id="0"></d-player>
|
|
<d-player class="player2" :player="page?.players[1]" :stats="page?.stats" :id="1"></d-player>
|
|
<div class="navi"></div>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
export const initXoiView = (app) => {
|
|
app.component('d-xoi', xoi)
|
|
}
|
|
|