Completely rework justone

This commit is contained in:
Schmop 2023-07-16 18:33:18 +02:00
parent 6242b6a9bb
commit e2a2507281
36 changed files with 7518 additions and 52 deletions

14
.eslintrc.cjs Normal file
View File

@ -0,0 +1,14 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/justone.iml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/justone.iml" filepath="$PROJECT_DIR$/.idea/justone.iml" />
</modules>
</component>
</project>

19
.idea/php.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

46
README.md Normal file
View File

@ -0,0 +1,46 @@
# justone
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -1,48 +1,13 @@
<script>
const filenames = [
'laura.txt',
'becker.txt',
'junior.txt',
'senior.txt',
'simon.txt',
'lars.txt'
];
let titles = [];
function refresh() {
document.getElementById('num').innerText = `${titles.length} Begriffe`;
}
async function loadFiles() {
filenames.forEach(async filename => {
const response = await fetch(filename);
if (response.status !== 200) {
return;
}
const text = await response.text();
titles.push(...text.split('\n'));
refresh();
});
}
loadFiles();
function random(max) {
return Math.floor(Math.random()*max);
}
function generate() {
if (titles.length === 0) {
alert("No titles left");
return;
}
const title = titles.splice(random(titles.length), 1)[0];
document.getElementById('title').innerText = title;
refresh();
}
</script>
<button id="generate" type="button" onclick="generate()">Generate</button>
<div id="title">No title</div>
<div id="num">0 Begriffe</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

6521
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "justone",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint justone --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@chenfengyuan/vue-number-input": "^2.0.1",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tsconfig/node18": "^2.0.1",
"@types/node": "^18.16.17",
"@vitejs/plugin-vue": "^4.2.3",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.11.0",
"npm-run-all": "^4.1.5",
"typescript": "~5.0.4",
"vite": "^4.3.9",
"vue-tsc": "^1.6.5"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

22
public/wordlists/ab18.txt Normal file
View File

@ -0,0 +1,22 @@
Zärtlichkeiten
Hämorrhoiden
Weltenbummler
Domina
One Night Stand
Strippen/ Stripper
Orgie
Kondom
Blowjob
Zungenkuss
Busensex
SM-Spiele
Intimpiercing
Vibrator
Intimrasur
69er-Stellung
Analsex
Besetzungscouch
Milf
Fußerotik
Missionarsstellung
Kamasutra

347
public/wordlists/filme.txt Normal file
View File

@ -0,0 +1,347 @@
12 Years a Slave
1917
2001 - Odyssee im Weltraum
3 Idiots
50 Shades of Grey
A Beautiful Mind: Genie und Wahnsinn
Aladdin
Alf
Alice im Wunderland ( Lewis Carroll)
Alien
Alien: Das unheimliche Wesen aus einer fremden Welt
Aliens: Die Rückkehr
Alles steht Kopf
Alles über Eva
Amadeus
Amelie
American Beauty
American History X
Amores perros
Anna, Elsa oder Olaf von der Schneekönigin (Disneys Frozen)
Apocalypse Now
Arielle die Meerjungfrau
Aschenputtel
Avengers: Endgame
Avengers: Infinity War
Ballade vom Weg
Barry Lyndon
Batman
Batman Begins
Before Sunrise
Before Sunset
Bei Anruf Mord
Ben Hur
Bob der Baumeister
Boulevard der Dämmerung
Braveheart
Bridget Jones Schokolade zum Frühstück
Bube Dame König grAS
Capernaum: Stadt der Hoffnung
Casablanca
Casino
Catch Me If You Can
Charlie und die Schokoladenfabrik
Chihiros Reise ins Zauberland
Chinatown
Cinderella
Cinema Paradiso
Citizen Kane
City of God
Click
Clockwork Orange
Clueless
Coco: Lebendiger als das Leben
Dangal: Die Hoffnung auf den großen Sieg
Das Appartement
Das Bildnis von Dorian Gray ( Oscar Wilde)
Das Boot
Das Ding aus einer anderen Welt
Das Dschungelbuch
Das Fenster zum Hof
Das Leben der Anderen
Das Leben des Brian
Das Leben ist schön
Das Mädchen mit dem Drachentattoo
Das Schweigen der Lämmer
Das siebente Siegel
Das tapfere Schneiderlein
Das wandelnde Schloss
Departed: Unter Feinden
Der Angsthase
Der Blade Runner
Der Clou
Der Club der toten Dichter
Der Davinci code
Der Elefantenmensch
Der Exorzist
Der Froschkönig
Der Froschprinz
Der General
Der Gigant aus dem All
Der Hase und der Igel
Der Herr der Ringe: Die Gefährten
Der Herr der Ringe: Die Rückkehr des Königs
Der Herr der Ringe: Die zwei Türme
Der König der Löwen
Der Pate
Der Pate 2
Der Pianist
Der Rattenfänger von Hamel
Der Schatz der Sierra Madre
Der Soldat James Ryan
Der Terminator
Der Teufel trägt Prada
Der Unbeugsame
Der Untergang
Der Vagabund und das Kind
Der Wolf und die sieben Geisslein
Der Zauberer von Oz
Der dritte Mann
Der große Diktator
Der mit dem Wolf tanzt
Der unsichtbare Dritte
Der weiße Hai
Die Abendteuer von Tom Sayer und Huckleberry Finn (Mark Twain)
Die Bremer Stadtmusikanten
Die Brücke am Kwai
Die Faust im Nacken
Die Frau die singt - Incendies
Die Jagd
Die Monster AG
Die Passion der Jeanne d'Arc
Die Päpstin
Die Reise nach Tokio
Die Ritter der Kokosnuß
Die Schneekönigin
Die Schöne und das Biest
Die Taschendiebin
Die Truman Show
Die Unglaublichen
Die Verurteilten
Die Vögel
Die besten Jahre unseres Lebens
Die drei kleinen Schweinchen
Die durch die Hölle gehen
Die fabelhafte Welt der Amelie
Die kleine Raupe nimmersatt
Die letzten Glühwürmchen
Die sieben Samurai
Die zwölf Geschworenen
Die üblichen Verdächtigen
Django
Django Unchained
Don Quijote
Donald Duck
Dornröschen
Dr. Seltsam oder: Wie ich lernte, die Bombe zu lieben
Drachenzähmen leicht gemacht
Du sollst mein Glücksstern sein
Einer flog über das Kuckucksnest
Erbarmungslos
Es geschah in einer Nacht
Es war einmal in Amerika
Fahrraddiebe
Fantomas mit Louis de Funès
Fargo: Blutiger Schnee
Fight Club
Findet Nemo
Fluch der Karibik
Forrest Gump
Frau Holle
Frau ohne Gewissen
Früchte des Zorns
Full Metal Jacket
Für ein paar Dollar mehr
Geh und sieh
Gesprengte Ketten
Ghostbusters
Gladiator
Goldrausch
Gone Girl - Das perfekte Opfer
Good Will Hunting: Der gute Will Hunting
GoodFellas - Drei Jahrzehnte in der Mafia
Goodfellas
Gran Torino
Grand Budapest Hotel
Green Book
Gremlins
Guardians of the Galaxy Vol. 3
Hachiko - Eine wunderbare Freundschaft
Hacksaw Ridge - Die Entscheidung
Hamilton
Hans im Glück
Harakiri
Harry Potter
Harry Potter und die Heiligtümer des Todes - Teil 2
Hass
Heat
Herr der Ringe
Hotel Ruanda
Hänsel und Gretel
Ikiru: Einmal wirklich leben
Im Namen des Vaters
In ihren Augen
Inception
Indiana Jones und der letzte Kreuzzug
Inglourious Basterds
Interstellar
Into the Wild
Iron Man
Ist das Leben nicht schön?
Jai Bhim
Joker
Jurassic Park
Jäger des verlorenen Schatzes
Kermit der Frosch
Kevin allein zu Haus
Kill Bill - Vol. 1
Kinder des Himmels
Klaus
Krieg und Frieden
Kung-Fu Panda
König Drosselbart
König der Löwen
L.A. Confidential
Lassie
Lawrence von Arabien
Le Mans 66 - Gegen jede Chance
Leo Lausemaus
Lichter der Großstadt
Life of Pi
Logan: The Wolverine
Lohn der Angst
Lola rennt
Léon: Der Profi
M: Eine Stadt sucht einen Mörder
Mad Max: Fury Road
Malcolm mittendrin
Manche mögen's heiß
Mary & Max, oder - Schrumpfen Schafe, wenn es regnet
Matrix
Max und Moritz
Mein Nachbar Totoro
Mein Vater und mein Sohn
Meine Lieder, meine Träume
Memento
Memories of Murder
Metropolis
Mickey Mouse
Million Dollar Baby
Minions
Miss Piggy
Moderne Zeiten
Mr. Smith geht nach Washington
Mrs. Doubtfire
Nader und Simin - eine Trennung
Network
No Country for Old Men
Oben
Oldboy
Oliver Twist (Charles Dickens)
Onkel Dagobert
Otto (Walkes) der Film
Pans Labyrinth
Parasite
Per Anhalter durch die Galaxis
Persona
Pinnocio
Pippi Langstrumpf
Platoon
Prestige - Die Meister der Magie
Prinzessin Mononoke
Prisoners
Psycho
Pulp Fiction
Ran
Rapunzel
Rashomon - Das Lustwäldchen
Ratatouille
Raum
Rebecca
Ren und Stimpy Show
Requiem for a Dream
Reservoir Dogs - Wilde Hunde
Robinson Crusoe
Rocky
Romeo und Julia
Rotkäppchen
Rugrats
Rumpelstilzchen
Rush: Alles für den Sieg
Saw
Scarface: Toni, das Narbengesicht
Schindlers Liste
Schlacht um Algier
Schneekönigin
Schneeweisschen und Rosenrot
Sein oder Nichtsein
Sesame Strasse
Sherlock Holmes
Sherlock Holmes Jr.
Shining
Shrek
Shutter Island
Sie küßten und sie schlugen ihn
Sie nannten ihn Mücke ( mit Bud Spencer)
Sieben
Snatch: Schweine und Diamanten
Spider-Man: A New Universe
Spider-Man: Across the Spider-Verse
Spider-Man: No Way Home
Spiel mir das Lied vom Tod
Spongebob Schwammkopf
Spotlight
Stand by Me: Das Geheimnis eines Sommers
Star Wars: Episode IV - Eine neue Hoffnung
Star Wars: Episode V - Das Imperium schlägt zurück
Star Wars: Episode VI - Die Rückkehr der Jedi-Ritter
Steppenwolf
Stirb langsam
Taare Zameen Par: Ein Stern auf Erden
Taxi Driver
Terminator
Terminator 2: Tag der Abrechnung
The Big Lebowski
The Dark Knight
The Dark Knight Rises
The Departed
The Father
The Green Mile
The Help
The Matrix
The Simpsons
The Sixth Sense - Nicht jede Gabe ist ein Segen
The Wolf of Wall Street
There Will Be Blood
Three Billboards Outside Ebbing, Missouri
Tischchen deck dich, Goldesel und Knüppel aus dem Sack
Titanic
Top Gun: Maverick
Toy Story
Toy Story 3
Trainspotting - Neue Helden
Twilight
Uhrwerk Orange
Und täglich grüßt das Murmeltier
Urteil von Nürnberg
V wie Vendetta
Vergiss mein nicht
Vertigo: Aus dem Reich der Toten
Vom Winde verweht
Wall e
Warrior
Wege zum Ruhm
Wer die Nachtigall stört
Whiplash
Wie ein wilder Stier
Wild Tales: Jeder dreht mal durch!
Wilde Erdbeeren
Winnie Puuh
X-Men
Yojimbo - Der Leibwächter
Your Name.
Zeugin der Anklage
Ziemlich beste Freunde
Zurück in die Zukunft
Zwei glorreiche Halunken
Zwischen Himmel und Hölle

View File

@ -0,0 +1,69 @@
Fernbedienung
Rasierschaum
Laderampe
Lichterkette
Raufasertapete
Tannenbaumständer
Siebträgermaschine
Lampenfassung
Schneeketten
Konfettiregen
Geschenkpapier
Wollschal
Schleifenbank
Rundumleuchte
Kugelbahn
Schaukelpferd
Verhaltensmuster
Handcreme
Räuchermännchen
Querschläger
Adventskalender
Brillenputztuch
Hauptgewinn
Wäscheklammer
Spiegelreflexkamera
Pralinenschachtel
Drehbuch
Kinderstuhl
Sonnenblumenöl
Christbaumkugel
Zungenkuss
Ladegerät
Schreihals
Glühwein
Osterhase
Teppichklopfer
Schokoriegel
Vollpfosten
Sprudelwasser
Eierlikör
Neujahresgruß
Glatteis
Weihnachtsgans
Lebkuchenherz
Trostpreis
Versuchskaninchen
Regalboden
Mistelzweig
Hebamme
Manschettenknopf
Schneegestöber
Rentier
Winterstiefel
Flussufer
Pfauenfeder
Ohrensessel
Verlobungsring
Nassrasur
Garagentür
Weihnachtspost
Glasreiniger
Intimpiercing
Gießkanne
Hackebeil
Tiefkühlfach
Treppenhaus
Fahrradschlauch
Kreißsaal
Mitesser

View File

@ -1,4 +0,0 @@
To use start a python server in this directory with
``python3 -m http.server``
Then access http://localhost:8000

20
src/App.vue Normal file
View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style>
body {
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
const emits = defineEmits<{
(e: 'again'): void;
(e: 'wordlist'): void;
}>();
</script>
<template>
<div class="container">
<h1>🎉Game over! Another one?</h1>
<button @click="emits('again')"> Play again!</button>
<button @click="emits('wordlist')">🛠 New wordlist</button>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
button {
margin-top: 10px;
padding: 16px;
width: 300px;
height: 150px;
border-radius: 5px;
cursor: pointer;
background-color: #59a;
border: 1px solid black;
font-size: x-large;
box-shadow: 2px 2px 5px black;
}
</style>

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import { random } from "../wordlist";
import { computed, ref, watch } from "vue";
const props = defineProps<{
words: string[],
numWords: number,
}>();
const emits = defineEmits<{
(e: 'end-game'): void;
}>();
const wordsLeft = ref<string[]>(props.words.slice());
const currentWord = ref<string>('');
const gameStartTime = performance.now();
const gameTimeString = ref<string>('');
const numWordsLeft = computed(() => {
const maxWordsLeft = props.numWords - (props.words.length - wordsLeft.value.length);
return Math.min(maxWordsLeft, wordsLeft.value.length);
});
const interval = setInterval(() => {
const gameDurationSeconds = (performance.now() - gameStartTime) / 1000;
const minutes = Math.floor(gameDurationSeconds / 60);
const seconds = Math.floor(gameDurationSeconds) % 60;
gameTimeString.value = `${minutes}:${seconds.toString().padStart(2, '0')} minute`;
}, 1000);
function selectNextWord() {
const nextWord = wordsLeft.value.splice(random(wordsLeft.value.length), 1)[0];
if (undefined === nextWord || numWordsLeft.value <= 0) {
clearInterval(interval);
emits('end-game');
return;
}
currentWord.value = nextWord;
}
</script>
<template>
<div class="container">
<h2>You word:</h2>
<h1>&gt;&gt; {{currentWord}} &lt;&lt;</h1>
<button @click="selectNextWord">Next word 😏</button>
<p>{{numWordsLeft}} words left</p>
<p>{{gameTimeString}}</p>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
button {
margin-top: 10px;
padding: 16px;
width: 200px;
border-radius: 5px;
cursor: pointer;
background-color: #5a5;
border: 1px solid black;
font-size: x-large;
box-shadow: 2px 2px 5px black;
}
</style>

View File

@ -0,0 +1,96 @@
<script setup lang="ts">
import { wordlists } from "../wordlist";
// Assume the file exists in ./public/wordlists/<name>.txt
import { ref } from "vue";
import VueNumberInput from '@chenfengyuan/vue-number-input';
const emits = defineEmits<{
(e: 'start-game', selectedWordLists: string[], numWords: number): void;
}>();
const numWords = ref<number>(21);
const selectedWordLists = ref<Set<string>>(new Set<string>([]));
function toggleInWordlist(name: string) {
if (selectedWordLists.value.has(name)) {
selectedWordLists.value.delete(name);
} else {
selectedWordLists.value.add(name);
}
}
function mouseover(event: MouseEvent, name: string) {
if (event.type === 'mouseover' && event.buttons === 0) {
return;
}
toggleInWordlist(name);
}
function startGame() {
emits(
'start-game',
[...selectedWordLists.value].map(name => `wordlists/${name.toLowerCase()}.txt`),
numWords.value
);
}
</script>
<template>
<div class="container">
<h1>Just One</h1>
<p>How many words?</p>
<VueNumberInput
type="number"
v-model="numWords"
:min="10"
:max="100"
inline
controls
/>
<p>Choose your wordlists:</p>
<div
v-for="name in wordlists"
:class="['card', { selected: selectedWordLists.has(name) }]"
@mouseover="mouseover($event, name)"
@mousedown="mouseover($event, name)"
>
{{ name }}
</div>
<button @click="startGame">Start Game 💨</button>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
user-select: none;
}
.card {
border: 1px solid black;
border-radius: 5px;
padding: 5px;
margin: 5px;
cursor: pointer;
width: 200px;
text-align: center;
}
.selected {
background-color: #afa;
box-shadow: 2px 2px 3px black;
}
button {
margin-top: 10px;
padding: 16px;
width: 200px;
border-radius: 5px;
cursor: pointer;
background-color: #5a5;
border: 1px solid black;
font-size: x-large;
box-shadow: 2px 2px 5px black;
}
</style>

1
src/gamestate.ts Normal file
View File

@ -0,0 +1 @@
export type GAMESTATE = 'wordlist-selection' | 'play' | 'end';

9
src/main.ts Normal file
View File

@ -0,0 +1,9 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')

15
src/router/index.ts Normal file
View File

@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router'
import JustOne from '../views/JustOne.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'justone',
component: JustOne
},
]
})
export default router

35
src/views/JustOne.vue Normal file
View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import type { GAMESTATE } from "../gamestate";
import { fetchWordLists } from "../wordlist";
import { ref } from "vue";
import WordlistSelection from '../components/WordlistSelection.vue';
import GameLoop from '../components/GameLoop.vue';
import EndGame from '../components/EndGame.vue';
const gamestate = ref<GAMESTATE>('wordlist-selection');
const words = ref<string[]>([]);
const numWords = ref<number>(-1);
async function selectWordlist(wordLists: string[], _numWords: number) {
words.value = await fetchWordLists(wordLists);
numWords.value = _numWords;
gamestate.value = 'play';
}
</script>
<template>
<main>
<WordlistSelection
v-if="gamestate === 'wordlist-selection'"
@start-game="selectWordlist"
/>
<GameLoop
v-else-if="gamestate === 'play'"
:words="words"
:num-words="numWords"
@end-game="() => gamestate = 'end'"
/>
<EndGame v-else-if="gamestate === 'end'" />
<div v-else>Not implemented...</div>
</main>
</template>

33
src/wordlist.ts Normal file
View File

@ -0,0 +1,33 @@
export const wordlists = [
'Laura',
'Becker',
'Junior',
'Senior',
'Simon',
'Lars',
'Kompositwörter',
'Filme',
];
export async function fetchWordLists(filenames: string[]) {
let titles: string[] = [];
for (const filename of filenames) {
const response = await fetch(filename);
if (response.status !== 200) {
continue;
}
const text = await response.text();
const lines = text
.split('\n')
.map(title => title.trim())
.filter(title => title.length > 0)
titles.push(...lines);
}
return titles;
}
export function random(max: number): number {
return Math.floor(Math.random() * max);
}

20
tsconfig.app.json Normal file
View File

@ -0,0 +1,20 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue"
],
"exclude": [
"src/**/__tests__/*"
],
"compilerOptions": {
"composite": true,
"baseUrl": "./justone",
"paths": {
"@/*": ["./src/*"],
},
"strict": true,
"noUncheckedIndexedAccess": true,
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

15
tsconfig.node.json Normal file
View File

@ -0,0 +1,15 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"module": "ESNext",
"types": ["node"]
}
}

16
vite.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})