word/memory: card game to find matching pairs
authorMischa POSLAWSKY <perl@shiar.org>
Mon, 25 Oct 2021 20:57:11 +0000 (22:57 +0200)
committerMischa POSLAWSKY <perl@shiar.org>
Tue, 9 Nov 2021 03:14:15 +0000 (04:14 +0100)
Another quiz concept well suited to this image dataset.  Initially populated
with distinct sets of grebes (currently the only untranslated references) to
experiment with variance as a somewhat unique selling point.

Makefile
word/memory.js [new file with mode: 0644]
word/memory.plp [new file with mode: 0644]

index ad2f7a02d126e1716a13d9a487946e8864e6ebc1..0742e97654e9cd629c1ef3f6c4ecf9f4b10511e3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ sitemap.xml: tools/mksitemap
 light.css: tools/stripcss base.css
        $(call cmdsave,$^)
 
-word: word/put.js data/wordlist.en.json data/wordlist.nl.json data/wordlist.ru.json
+word: word/put.js data/wordlist.en.json data/wordlist.nl.json data/wordlist.ru.json data/wordpairs.json
 
 word/put.js: $(download)
        tools/wget-ifmodified https://github.com/kriszyp/put-selector/raw/master/put.js $@
@@ -73,9 +73,12 @@ data/wordlist.en.inc.pl: tools/mkwordlist data/wordlist.version.txt
        $(call cmdsave,$<)
 data/wordlist.%.inc.pl: tools/mkwordlist data/wordlist.version.txt
        $(call cmdsave,$< $*)
-data/wordlist.%.json: data/wordlist.%.inc.pl
+data/word%.json: data/word%.inc.pl
        $(call cmdsave,perl -MJSON=encode_json -E "print encode_json(do \$$ARGV[0])" ./$<)
 
+data/wordpairs.inc.pl: data/wordlist.version.txt
+       @perl -I. -MShiar_Sheet::DB -MData::Dump=pp -E 'say pp(Shiar_Sheet::DB->connect->select("word w JOIN word a ON w.id=a.ref" => "w.id, a.id", {"a.lang"=>undef})->map or exit 1)' >$@
+
 .SECONDARY: data/font/%.ttf
 data/font/%.ttf:
        find /usr/share/fonts/truetype/ ~/.fonts/ -iname "$(@F)" | head -1 | xargs -i ln -sf {} $@
diff --git a/word/memory.js b/word/memory.js
new file mode 100644 (file)
index 0000000..0e523a1
--- /dev/null
@@ -0,0 +1,56 @@
+class WordMemory {
+       turn(click) {
+               let target = click.currentTarget;
+               if (!target.classList.contains('turn')) {
+                       // show an open card
+                       this.turned.push(target);
+                       put(target, '.turn');
+               }
+               else if (this.turned.length < 2) {
+                       return; // keep open
+               }
+
+               if (this.turned.length <= 1) {
+                       return; // first choice
+               }
+
+               // compare two cards
+               let match = this.pairs[this.turned[0].id] == this.turned[1].id
+                       || this.pairs[this.turned[1].id] == this.turned[0].id;
+               if (!match && !this.turned[0].classList.contains('bad')) {
+                       put(this.turned[0], '.bad'); // indicate failure on first card
+                       return;
+               }
+
+               if (match) {
+                       // lock both as correct
+                       this.turned.forEach(card => put(card, '.good![onclick]'));
+                       this.turned = [];
+                       return;
+               }
+
+               // fold back earlier cards
+               this.turned.splice(0, 2)
+               .forEach(card => put(card, '!.turn!.bad'));
+       }
+
+       constructor() {
+               this.dataurl = '/data/wordpairs.json';
+               fetch(this.dataurl).then(res => res.json()).then(pairs => {
+                       this.turned = [];
+                       this.pairs = pairs;
+                       this.form = document.getElementById('quiz');
+                       this.cards = Object.entries(pairs).flat()
+                               .map(e => e.toString())
+                               .sort(() => {return .5 - Math.random()}) // shuffle
+                       this.cards.forEach(word => {
+                               put(this.form,
+                                       'figure>img[src=$]<', `/data/word/en/${word}.jpg`,
+                                       {onclick: e => this.turn(e), id: word}
+                               );
+                       });
+               });
+       }
+};
+
+new WordMemory();
diff --git a/word/memory.plp b/word/memory.plp
new file mode 100644 (file)
index 0000000..d12cc75
--- /dev/null
@@ -0,0 +1,48 @@
+<(../common.inc.plp)><:
+
+Html({
+       raw => <<'EOT',
+<script src="/word/put.js"></script>
+<script src="/word/memory.js"></script>
+<style>
+/* cards */
+figure {
+       display: inline-block;
+       background: #224;
+       border: 1px solid #888;
+       margin: 2px;
+       perspective: 100em;
+       height: 300px;
+       position: relative;
+}
+figure:not(.turn):hover {
+       cursor: pointer;
+}
+figure, img {
+       transition: all .5s ease-in;
+}
+
+/* card faces */
+figure img {
+       height: 100%;
+       width: auto;
+       backface-visibility: hidden;
+       transform: rotateY(180deg); /* back */
+       transform-style: preserve-3d;
+       float: left; /* ff workaround to prevent click selection */
+}
+
+/* turn results */
+figure.turn img {
+       transform: rotateY(0deg);
+}
+figure.bad img {
+       filter: sepia(.5) hue-rotate(-45deg) saturate(2); /* red tint */
+}
+figure.good {
+       opacity: .8;
+}
+</style>
+EOT
+});
+say '<h1>memory</h1><p id="quiz"></p>';