online quiz is the default
This commit is contained in:
1
TODO.md
1
TODO.md
@@ -1,6 +1,7 @@
|
||||
- deploy
|
||||
- now.sh, waiting for it to be purely front end
|
||||
- purely front end except for mailing list
|
||||
- remove repeated exercises
|
||||
- make text inputs big enough for MAX_DIGITS_PROBLEMS and MAX_DIGITS_BITS
|
||||
- show a message on submit
|
||||
- show warning when specifying invalid number of bits or problems
|
||||
|
||||
@@ -64,3 +64,30 @@ button:not(:disabled):active {
|
||||
button:focus {
|
||||
border-color: #666;
|
||||
}
|
||||
.inputs {
|
||||
text-align: right; width: 2em; border: none; outline: none;
|
||||
}
|
||||
|
||||
|
||||
.primary-button {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background-color: dodgerblue;
|
||||
color: white
|
||||
}
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<script>
|
||||
import Inputs from "./Inputs.svelte";
|
||||
import Quiz from "./Quiz.svelte";
|
||||
import Inputs from "./components/Inputs.svelte";
|
||||
import Quiz from "./components/Quiz.svelte";
|
||||
import Tally from "./components/Tally.svelte";
|
||||
import {activeQuiz} from './stores'
|
||||
</script>
|
||||
<main style="margin: 2em;">
|
||||
<h1>Binary Quiz!</h1>
|
||||
<Inputs />
|
||||
<Quiz />
|
||||
{#if ($activeQuiz)}
|
||||
<Tally />
|
||||
<Quiz />
|
||||
{:else}
|
||||
<Inputs />
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<script>
|
||||
import download from './utils/download.js'
|
||||
import {
|
||||
MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS ,
|
||||
MAX_DIGITS_BITS, MAX_DIGITS_PROBLEMS
|
||||
} from './config.js'
|
||||
import { bits, num_problems } from './stores.js'
|
||||
|
||||
$: valid = ($bits || DEFAULT_BITS) <= MAX_BITS
|
||||
&& ($bits || DEFAULT_BITS) >= MIN_BITS
|
||||
&& ($num_problems || DEFAULT_NUM_PROBLEMS) >= MIN_PROBLEMS
|
||||
&& ($num_problems || DEFAULT_NUM_PROBLEMS) <= MAX_PROBLEMS
|
||||
|
||||
const downloadAndClear = () => {
|
||||
download($bits || DEFAULT_BITS, $num_problems || DEFAULT_NUM_PROBLEMS)
|
||||
bits.update(() => null)
|
||||
num_problems.update(() => null)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={downloadAndClear}>
|
||||
<div id=inputs style="margin: 2em;">
|
||||
<label>
|
||||
<input
|
||||
style="text-align: right; width: 2em; border: none; outline: none"
|
||||
maxlength={MAX_DIGITS_BITS}
|
||||
placeholder={DEFAULT_BITS}
|
||||
autofocus
|
||||
type=text bind:value={$bits}
|
||||
>
|
||||
Bits
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
style="text-align: right; width: 2em; border: none; outline: none"
|
||||
maxlength={MAX_DIGITS_PROBLEMS}
|
||||
placeholder={DEFAULT_NUM_PROBLEMS}
|
||||
type=text bind:value={$num_problems}
|
||||
>
|
||||
Number of Problems
|
||||
</label>
|
||||
</div>
|
||||
<input disabled={!valid} type=submit style="cursor:pointer;" value="Download PDFs" >
|
||||
</form>
|
||||
52
src/components/Inputs.svelte
Normal file
52
src/components/Inputs.svelte
Normal file
@@ -0,0 +1,52 @@
|
||||
<script>
|
||||
import download from '../utils/download'
|
||||
import {
|
||||
MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS,
|
||||
MAX_DIGITS_BITS, MAX_DIGITS_PROBLEMS
|
||||
} from '../config'
|
||||
import { bits, num_problems, valid, activeQuiz, problems } from '../stores'
|
||||
import {generateProblems} from "../problems";
|
||||
|
||||
const downloadAndClear = () => {
|
||||
download($bits || DEFAULT_BITS, $num_problems || DEFAULT_NUM_PROBLEMS)
|
||||
bits.update(() => null)
|
||||
num_problems.update(() => null)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
const problems_ = generateProblems($bits, $num_problems)
|
||||
problems.update(() => problems_)
|
||||
activeQuiz.update(() => true)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={submit}>
|
||||
<div id=inputs style="margin: 2em;">
|
||||
<label>
|
||||
<input
|
||||
class="inputs"
|
||||
max={MAX_BITS}
|
||||
min={MIN_BITS}
|
||||
placeholder={DEFAULT_BITS}
|
||||
autofocus
|
||||
type=number
|
||||
bind:value={$bits}
|
||||
>
|
||||
Bits
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
class="inputs"
|
||||
max={MAX_PROBLEMS}
|
||||
min={MIN_PROBLEMS}
|
||||
placeholder={DEFAULT_NUM_PROBLEMS}
|
||||
type=number
|
||||
bind:value={$num_problems}
|
||||
>
|
||||
Number of Problems
|
||||
</label>
|
||||
</div>
|
||||
<input class="primary-button" disabled={!$valid} type=submit value="Start Quiz" >
|
||||
<input class="button" disabled={!$valid} type=button on:click={downloadAndClear} value="Download PDFs">
|
||||
</form>
|
||||
54
src/components/Problem.svelte
Normal file
54
src/components/Problem.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script>
|
||||
import {checkAnswer} from "../problems";
|
||||
import { bits, activeProblemIndex, tally, num_problems, activeQuiz } from '../stores'
|
||||
export let problem
|
||||
let displaySummary = false
|
||||
let solution
|
||||
let submitted = false
|
||||
let class_ = ""
|
||||
|
||||
const check = () => {
|
||||
if (checkAnswer(solution, problem)) {
|
||||
submitted = true
|
||||
if ($num_problems === $activeProblemIndex + 1) {
|
||||
displaySummary = true
|
||||
}
|
||||
activeProblemIndex.update(() => $activeProblemIndex + 1)
|
||||
tally.update(() => $tally + 1)
|
||||
} else {
|
||||
class_ = "incorrect"
|
||||
}
|
||||
solution = null
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
activeQuiz.update(() => false)
|
||||
bits.update(() => null)
|
||||
num_problems.update(() => null)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if displaySummary}
|
||||
<div>Congratulations, you've completed {$num_problems} {$bits}-bit problems!</div>
|
||||
<div><a on:click={reset}>Go back to home screen.</a></div>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={check} class="{class_} problem">
|
||||
<label>
|
||||
{problem} =
|
||||
<input autofocus type=number style="width: {$bits}em" bind:value={solution}>
|
||||
</label>
|
||||
<input type=submit style="visibility: hidden">
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.problem {
|
||||
font-size: 3em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.incorrect {
|
||||
border: dodgerblue;
|
||||
}
|
||||
</style>
|
||||
7
src/components/Quiz.svelte
Normal file
7
src/components/Quiz.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import {bits, num_problems, problems, activeProblemIndex} from '../stores.js'
|
||||
import Problem from "./Problem.svelte";
|
||||
</script>
|
||||
|
||||
<Problem problem={$problems[$activeProblemIndex]}/>
|
||||
|
||||
7
src/components/Tally.svelte
Normal file
7
src/components/Tally.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import { tally, num_problems, bits } from '../stores'
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{$tally}/{$num_problems}
|
||||
</div>
|
||||
@@ -7,3 +7,7 @@ export const MAX_DIGITS_BITS = MAX_BITS.toString().length
|
||||
export const DEFAULT_BITS = 8
|
||||
export const DEFAULT_NUM_PROBLEMS = 20
|
||||
|
||||
export const getMaxPermutations = (bits) => {
|
||||
return Math.pow(2, bits)
|
||||
}
|
||||
|
||||
|
||||
22
src/problems.js
Normal file
22
src/problems.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import {DEFAULT_BITS, DEFAULT_NUM_PROBLEMS} from "./config";
|
||||
|
||||
export const generateProblems = (bits, num_problems) => {
|
||||
let probs = []
|
||||
let counter = 0
|
||||
while (counter < (num_problems || DEFAULT_NUM_PROBLEMS)) {
|
||||
let problem = generateProblem(bits || DEFAULT_BITS)
|
||||
if (probs.includes(problem)) {
|
||||
continue
|
||||
}
|
||||
probs.push(problem)
|
||||
counter++
|
||||
}
|
||||
return probs
|
||||
}
|
||||
|
||||
export const generateProblem = bits => Array.from(Array(bits)).map((_, i) => Math.random() >= .5 ? '1' : '0').join('')
|
||||
|
||||
export const solveProblem = problem => parseInt(problem, 2)
|
||||
|
||||
export const checkAnswer = (answer, problem) => solveProblem(problem) === answer
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import {derived, writable} from 'svelte/store'
|
||||
import {generateProblem} from "./problems"
|
||||
import {getMaxPermutations, MIN_BITS, MAX_BITS, MIN_PROBLEMS, MAX_PROBLEMS, DEFAULT_BITS, DEFAULT_NUM_PROBLEMS} from "./config"
|
||||
|
||||
export const bits = writable()
|
||||
export const num_problems = writable()
|
||||
export const activeQuiz = writable(false)
|
||||
export const activeProblemIndex = writable(0)
|
||||
export const tally = writable(0)
|
||||
export const problems = writable([])
|
||||
|
||||
export const valid = derived([bits, num_problems], ([$bits, $num_problems]) => (
|
||||
($bits || DEFAULT_BITS) <= MAX_BITS
|
||||
&& ($bits || DEFAULT_BITS) >= MIN_BITS
|
||||
&& ($num_problems || DEFAULT_NUM_PROBLEMS) >= MIN_PROBLEMS
|
||||
&& ($num_problems || DEFAULT_NUM_PROBLEMS) <= MAX_PROBLEMS
|
||||
&& ($num_problems || DEFAULT_NUM_PROBLEMS) <= getMaxPermutations($bits)
|
||||
))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user