Webapp: implemented list of guesses
This commit is contained in:
parent
130c9dac23
commit
6128093102
|
|
@ -13,8 +13,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
indent: ["error", "tab"],
|
indent: ["error", "tab"],
|
||||||
|
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||||
quotes: ["error", "double"],
|
quotes: ["error", "double"],
|
||||||
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
"comma-dangle": ["error", "always-multiline"],
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { Concept } from "@/Concept";
|
||||||
|
|
||||||
|
export class Guess {
|
||||||
|
uri: string;
|
||||||
|
displayName: string;
|
||||||
|
distance: number;
|
||||||
|
|
||||||
|
constructor(uri: string, displayName: string, distance: number) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static score(concept: Concept, nbEntities: number): number {
|
||||||
|
return (1 - (concept.extensionalDistance - 1) / nbEntities) * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<form v-if="entities.length > 0" @submit.prevent="submitGuess">
|
<div class="game" v-if="entities.length > 0">
|
||||||
Enter a guess: <input type="text" v-model="guessFieldValue" /> <br />
|
<form @submit.prevent="submitGuess">
|
||||||
|
Enter a guess: <input type="text" v-model="guessFieldValue" ref="guessField" /> <br />
|
||||||
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
|
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
|
||||||
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
|
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="row">
|
||||||
|
<guess-list :guesses="guesses" class="col-xl-6"></guess-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
Error: component instantiated but entities are empty.
|
Error: component instantiated but entities are empty.
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,10 +19,13 @@
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
import type { NamedEntity } from "@/NamedEntity";
|
import type { NamedEntity } from "@/NamedEntity";
|
||||||
import type { Concept } from "@/Concept";
|
import type { Concept } from "@/Concept";
|
||||||
|
import GuessList from "@/components/guessList/GuessList.vue";
|
||||||
|
import { Guess } from "@/Guess";
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "MainComponent",
|
name: "MainComponent",
|
||||||
|
components: { GuessList },
|
||||||
props: {
|
props: {
|
||||||
entities: {
|
entities: {
|
||||||
type: Object as PropType<Array<NamedEntity>>,
|
type: Object as PropType<Array<NamedEntity>>,
|
||||||
|
|
@ -39,6 +47,7 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
guessFieldValue: "",
|
guessFieldValue: "",
|
||||||
guessError: null as string | null,
|
guessError: null as string | null,
|
||||||
|
guesses: [] as Guess[],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -68,21 +77,50 @@ export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
submitGuess() {
|
submitGuess() {
|
||||||
this.guessError = null;
|
this.guessError = null;
|
||||||
if (!this.entitiesByLowerName.has(this.guessFieldValue.toLowerCase())) {
|
this.entitiesWithName(this.guessFieldValue)
|
||||||
this.guessError = `${(this.guessFieldValue)} is unknown to me...`;
|
.then(entities => {
|
||||||
return;
|
if (entities.some(e => e.uri == this.target))
|
||||||
}
|
alert("You won!");
|
||||||
const matchingEntities = this.entitiesByLowerName.get(this.guessFieldValue.toLowerCase()) as NamedEntity[];
|
return entities;
|
||||||
const matchingUris = new Set(matchingEntities.map(e => e.uri));
|
})
|
||||||
console.debug(matchingUris);
|
.then(this.firstConceptMatchingEntities)
|
||||||
const matchingConcept = this.concepts.find(concept => concept.elements.some(uri => matchingUris.has(uri)));
|
.then(match => {
|
||||||
if (typeof matchingConcept == "undefined") {
|
const entity = match.matchingEntity;
|
||||||
this.guessError = "Entity is known but concept can't be found: this should not happen...";
|
const displayName = this.entitiesByLowerName.get(entity.name.toLowerCase())!.length > 1 ? `${entity.name} (${entity.type})` : entity.name;
|
||||||
return;
|
this.guesses.push(new Guess(
|
||||||
}
|
entity.uri,
|
||||||
console.info("Found matching guess concept");
|
displayName,
|
||||||
console.info(matchingConcept);
|
Guess.score(match.concept, this.entities.length),
|
||||||
|
));
|
||||||
this.guessFieldValue = "";
|
this.guessFieldValue = "";
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.guessError = err.message;
|
||||||
|
(this.$refs.guessField as HTMLInputElement).select();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
entitiesWithName(name: string): Promise<NamedEntity[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const result = this.entitiesByLowerName.get(name.toLowerCase());
|
||||||
|
if (result === undefined)
|
||||||
|
reject(new Error(`${name} is unknown to me...`));
|
||||||
|
else
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Return the first concept that matches any one of the given entities, along with the matched entity.
|
||||||
|
*/
|
||||||
|
firstConceptMatchingEntities(entities: NamedEntity[]): Promise<{ matchingEntity: NamedEntity, concept: Concept }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const entitiesByTheirURI: Map<string, NamedEntity> = new Map(entities.map(e => [e.uri, e]));
|
||||||
|
for (const concept of this.concepts) {
|
||||||
|
const matchingURI = concept.elements.find(el => entitiesByTheirURI.has(el));
|
||||||
|
if (matchingURI !== undefined)
|
||||||
|
return resolve({ matchingEntity: entitiesByTheirURI.get(matchingURI)!, concept: concept });
|
||||||
|
}
|
||||||
|
return reject(new Error("This should not happen: impossible to find concept for entity."));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<table v-if="guesses.length > 0" class="table">
|
||||||
|
<thead>
|
||||||
|
<guess-table-row :guess="latestGuess" :index="sortedGuesses.indexOf(latestGuess)+1"></guess-table-row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<guess-table-row v-for="guess in sortedGuesses" :key="guess.uri"
|
||||||
|
:guess="guess" :index="guesses.indexOf(guess) + 1"
|
||||||
|
:class="{'table-active': guess.uri === latestGuess.uri}">
|
||||||
|
</guess-table-row>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, type PropType } from "vue";
|
||||||
|
import type { Guess } from "@/Guess";
|
||||||
|
import GuessTableRow from "@/components/guessList/GuessTableRow.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "GuessList",
|
||||||
|
components: { GuessTableRow },
|
||||||
|
props: {
|
||||||
|
guesses: {
|
||||||
|
type: Object as PropType<Guess[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedGuesses(): Guess[] {
|
||||||
|
return this.guesses.concat().sort((a, b) => b.distance - a.distance);
|
||||||
|
},
|
||||||
|
latestGuess(): Guess {
|
||||||
|
return this.guesses[this.guesses.length - 1];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "../../../node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
table {
|
||||||
|
@extend .mt-4;
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<tr>
|
||||||
|
<td>{{ index }}</td>
|
||||||
|
<td>{{ guess.displayName }}</td>
|
||||||
|
<td>{{ guess.distance.toFixed(2) }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, type PropType } from "vue";
|
||||||
|
import type { Guess } from "@/Guess";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "GuessTableRow",
|
||||||
|
props: {
|
||||||
|
guess: {
|
||||||
|
type: Object as PropType<Guess>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue