removed some constraints on quiz (uniqueness of puzzles, number of bits etc), added animation when the answer is wrong

This commit is contained in:
2022-11-11 10:28:52 +01:00
parent 82a9184b46
commit 2dc2f93e64
10 changed files with 2291 additions and 154 deletions

71
TODO.md
View File

@@ -1,35 +1,56 @@
- add progression # Content
- add explanation/tutorial - explanation/tutorial
- timed - 'about'/contact
- add column labels
- add spacing beyond X places ## Curriculum
- add color and font - how to negate a signed binary integer?
- optional super mario tyype sounds - how to convert decimal to binary?
- gamification via character upgrades - how to convert hex to binary?
- how to convert binary to hex?
- how many unsigned integers can be represented in ____? (a byte, nibble, word)
- how many _signed_ integers can be represented in ____? (a byte, nibble, word)
- how to determine if a binary int is even/odd?
- what can be said of a binary integer who ____ bits are 0?
- if all positions in a binary integer are 0 except position ____, what number does it represent?
- if all positions in a binary integer are 1 except position ____, what number does it represent?
- memorize powers of 2 up to 16 (speed round)
- what happens to the represented integer after shifting to the left?
- what happens to the represented integer after shifting to the right?
- what is the binary for the value 2^n - 1?
- what is sign extension? how do you do it?
- what is contraction?
- what is zero extension?
# Design
- color and font
- toggleable column labels
- animations
- [x] 'nuh-uh' shake for wrong answers
- [ ] nice reward animations for right answers
- [ ] make the placeholders become real text for a split second before starting
- processor graphic/animation - processor graphic/animation
- diff processors for different bits - diff processors for different bits
- mobile
## mobile
- make autofocus work on quiz - make autofocus work on quiz
- remove "[enter]" from start button - remove "[enter]" from start button
- on-screen keyboard, like wordle
- make it all on one line - make it all on one line
- make the font smaller or the div wider - make the font smaller or the div wider
- put a 'submit' button in - put a 'submit' button in (or auto-submit?)
- add a range slider for easier input? - add a range slider for easier input?
- make text inputs big enough for MAX_DIGITS_PROBLEMS and MAX_DIGITS_BITS
- show a message on submit # Features
- keyboard shortcuts
- ctrl-c to go back to menu
- specify what you already know, then prove it (modular learning)
- save progress
- local storage
- login
- timed
- keyboard shortcuts with help overlay
- leaderboard
- show warning when specifying invalid number of bits or problems - show warning when specifying invalid number of bits or problems
- add contact info
- add 'about'
- add mailing list
- prompt at the end of a quiz to sign up for it
- uncomment PDF part - uncomment PDF part
- add PDF export in pure front end - add PDF export in pure front end
- add animations - cheat/human detection
- 'nuh-uh' shake for wrong answers
- nice reward animations for right answers
- make the placeholders become real text for a split second before starting
- super mario land [pixellation animation](http://blog.swishscripts.com/2019/06/19/snes-mosaic-effect/) ("mosaic")
- add user accounts using Userbase
- required for higher numbers
- required for PDF output?
- required after X uses?

2226
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,15 @@
"@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-node-resolve": "^8.0.0",
"rollup": "^2.3.4", "rollup": "^2.3.4",
"rollup-plugin-livereload": "^1.0.0", "rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-svelte": "^5.0.3", "rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-terser": "^5.1.2", "rollup-plugin-terser": "^5.1.2",
"svelte": "^3.0.0" "svelte": "^3.53.0"
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"sirv-cli": "^0.4.4" "coc-svelte": "^0.4.2",
"g": "^2.0.1",
"sirv-cli": "^0.4.4",
"tree-sitter-svelte": "^0.10.1"
} }
} }

View File

@@ -101,3 +101,27 @@ input[type=number] {
.problem input[type=number] { .problem input[type=number] {
border: none; border: none;
} }
/* borrowed from https://stackoverflow.com/a/15991184/4386191 */
.nuhuh {
animation: shake .5s linear;
}
@keyframes shake {
8%, 41% {
transform: translateX(-10px);
}
25%, 58% {
transform: translateX(10px);
}
75% {
transform: translateX(-5px);
}
92% {
transform: translateX(5px);
}
0%, 100% {
transform: translateX(0);
}
}

View File

@@ -1,7 +1,7 @@
<script> <script>
import Inputs from "./components/Inputs.svelte"; import Inputs from "./components/Inputs.svelte";
import Quiz from "./components/Quiz.svelte"; import Quiz from "./components/Quiz.svelte";
import {activeQuiz} from './stores' import { activeQuiz } from './stores'
</script> </script>
<main style="margin: 2em;"> <main style="margin: 2em;">
<h1>Binary Quiz!</h1> <h1>Binary Quiz!</h1>

View File

@@ -1,11 +1,11 @@
<script> <script>
import download from '../download' import download from '../download.js'
import { import {
MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS, MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS,
MAX_DIGITS_BITS, MAX_DIGITS_PROBLEMS MAX_DIGITS_BITS, MAX_DIGITS_PROBLEMS
} from '../config' } from '../config.js'
import { bits, num_problems, valid, activeQuiz, problems } from '../stores' import { bits, num_problems, valid, activeQuiz, problems } from '../stores.js'
import {generateProblems} from "../problems"; import { generateProblems } from "../problems.js";
const downloadAndClear = () => { const downloadAndClear = () => {
download($bits || DEFAULT_BITS, $num_problems || DEFAULT_NUM_PROBLEMS) download($bits || DEFAULT_BITS, $num_problems || DEFAULT_NUM_PROBLEMS)
@@ -54,7 +54,7 @@
</label> </label>
</div> </div>
<input class="primary-button" disabled={!$valid} type=submit value="Start Quiz [enter]" > <input class="primary-button" disabled={!$valid} type=submit value="Start Quiz [enter]" >
<!-- <input class="button" disabled={!$valid} type=button on:click={downloadAndClear} value="Download PDFs">--> <!-- <input class="button" disabled={!$valid} type=button on:click={downloadAndClear} value="Download PDFs">-->
</form> </form>
<style> <style>

View File

@@ -1,30 +1,47 @@
<script> <script>
import {checkAnswer} from "../problems"; import {checkAnswer} from "../problems";
import {problems, bits, activeProblemIndex, tally, num_problems, activeQuiz} from '../stores' import { problems, bits, activeProblemIndex, tally, num_problems, activeQuiz } from '../stores';
import Tally from "./Tally.svelte"; import Tally from "./Tally.svelte";
$: problem = $problems[$activeProblemIndex] $: problem = $problems[$activeProblemIndex];
let displaySummary = false let displaySummary = false;
let solution let solution;
let userInput;
let form;
let visible = true;
const check = () => { function shakeAndClear() {
userInput.style.color = 'red';
userInput.classList.add('nuhuh');
setTimeout(function() {
userInput.classList.remove('nuhuh');
userInput.value = '';
userInput.style.color = '#333';
}, 550)
}
function check() {
if (!checkAnswer(solution, problem)) { if (!checkAnswer(solution, problem)) {
class_ = "incorrect" shakeAndClear();
} else { } else {
tally.update(() => $tally + 1) tally.update(() => $tally + 1);
if ($num_problems === $activeProblemIndex + 1) { if ($num_problems === $activeProblemIndex + 1) {
displaySummary = true displaySummary = true;
} else { } else {
activeProblemIndex.update(() => $activeProblemIndex + 1) visible = false;
setTimeout(function() {
activeProblemIndex.update(() => $activeProblemIndex + 1);
solution = null;
visible = true;
}, 550)
} }
solution = null
} }
} }
const reset = () => { const reset = () => {
activeQuiz.update(() => false)
activeProblemIndex.update(() => 0) activeProblemIndex.update(() => 0)
tally.update(() => 0) tally.update(() => 0)
activeQuiz.update(() => false)
bits.update(() => null) bits.update(() => null)
num_problems.update(() => null) num_problems.update(() => null)
displaySummary = false displaySummary = false
@@ -43,12 +60,13 @@
</div> </div>
{:else} {:else}
<Tally /> <Tally />
<form on:submit|preventDefault={check} class="problem"> {#if visible}
<label> <form bind:this={form} on:submit|preventDefault={check} class="problem">
{problem} = <label>
<input autofocus type=number bind:value={solution}> {problem} =
</label> <input bind:this={userInput} autofocus type=number bind:value={solution} id="problem">
<input type=submit style="visibility: hidden"> </label>
</form> <input type=submit style="visibility: hidden">
</form>
{/if}
{/if} {/if}

View File

@@ -1,13 +1,9 @@
export const MIN_BITS = 3 export const MIN_BITS = 2
export const MAX_BITS = 16 export const MAX_BITS = 16
export const MIN_PROBLEMS = 2 export const MIN_PROBLEMS = 1
export const MAX_PROBLEMS = 99 export const MAX_PROBLEMS = 99
export const MAX_DIGITS_PROBLEMS = MAX_PROBLEMS.toString().length export const MAX_DIGITS_PROBLEMS = MAX_PROBLEMS.toString().length
export const MAX_DIGITS_BITS = MAX_BITS.toString().length export const MAX_DIGITS_BITS = MAX_BITS.toString().length
export const DEFAULT_BITS = 4 export const DEFAULT_BITS = 4
export const DEFAULT_NUM_PROBLEMS = 10 export const DEFAULT_NUM_PROBLEMS = 10
export const getMaxPermutations = (bits) => {
return Math.pow(2, bits)
}

View File

@@ -1,23 +1,20 @@
import {DEFAULT_BITS, DEFAULT_NUM_PROBLEMS} from "./config";
export const generateProblems = (bits, num_problems) => { export function generateProblems(bits, num_problems) {
let probs = [] return new Array(num_problems)
let counter = 0 .fill(null)
.map(function() {
while (counter < (num_problems || DEFAULT_NUM_PROBLEMS)) { return generateProblem(bits)
let problem = generateProblem(bits || DEFAULT_BITS) })
if (!probs.includes(problem)) {
probs.push(problem)
counter++
}
}
return probs
} }
export const generateProblem = bits => Array.from(Array(bits)).map((_, i) => Math.random() >= .5 ? '1' : '0').join('') export function generateProblem(bits) {
return Array.from(Array(bits)).map((_, i) => Math.random() >= .5 ? '1' : '0').join('');
}
export const solveProblem = problem => parseInt(problem, 2) export function solveProblem(problem) {
return parseInt(problem, 2);
export const checkAnswer = (answer, problem) => solveProblem(problem) === answer }
export function checkAnswer(answer, problem) {
return solveProblem(problem) === answer
}

View File

@@ -1,6 +1,5 @@
import {derived, writable} from 'svelte/store' import { derived, writable } from 'svelte/store'
import {generateProblem} from "./problems" import { MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS } from "./config"
import {getMaxPermutations, MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS} from "./config"
export const bits = writable() export const bits = writable()
export const num_problems = writable() export const num_problems = writable()
@@ -14,6 +13,5 @@ export const valid = derived([bits, num_problems], ([$bits, $num_problems]) => (
&& ($bits || DEFAULT_BITS) >= MIN_BITS && ($bits || DEFAULT_BITS) >= MIN_BITS
&& ($num_problems || DEFAULT_NUM_PROBLEMS) >= MIN_PROBLEMS && ($num_problems || DEFAULT_NUM_PROBLEMS) >= MIN_PROBLEMS
&& ($num_problems || DEFAULT_NUM_PROBLEMS) <= MAX_PROBLEMS && ($num_problems || DEFAULT_NUM_PROBLEMS) <= MAX_PROBLEMS
&& ($num_problems || DEFAULT_NUM_PROBLEMS) <= getMaxPermutations($bits || DEFAULT_BITS)
)) ))