Webapp: implemented list of guesses

This commit is contained in:
Francesco 2022-05-01 17:40:38 +02:00
parent 130c9dac23
commit 6128093102
5 changed files with 167 additions and 20 deletions

View File

@ -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",
}, },
}; };

View File

@ -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;
}
}

View File

@ -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">
<div v-if="guessError" class="error">{{ guessError }} <br /></div> Enter a guess: <input type="text" v-model="guessFieldValue" ref="guessField" /> <br />
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span> <div v-if="guessError" class="error">{{ guessError }} <br /></div>
</form> <span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
</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."));
});
}, },
}, },
}); });

View File

@ -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>

View File

@ -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>