pages/static/anatomy-quiz.html

314 lines
9.5 KiB
HTML

<!DOCTYPE html>
<body>
<h1>MillironX's Anatomy Quiz Generator</h1>
<h2>Quiz material weighting</h2>
<div id="weights"></div>
<hr />
<div>
<label for="num-questions">Number of questions </label>
<input id="num-questions" type="number" value="15" />
</div>
<hr />
<button id="generator">Generate!</button>
<hr />
<h2>Quiz</h2>
<ul id="quiz-terms"></ul>
<script>
const BoldTerms = {
bones: {
scapula: {
asymmetric: true,
features: {
"glenoid cavity": {
articulation: "head of humerus",
},
spine: {},
acromion: {
attachment: "deltoideus",
},
"supraspinous fossa": {},
"infraspinous fossa": {},
"serrated face": {
insertion: "serratus ventralis",
},
"subscapular fossa": {
attachment: "subscapularis",
},
"cranial border": {},
"scapular notch": {},
"cranial angle": {},
"dorsal border": {},
"caudal angle": {
attachment: "rhomboideus",
},
"caudal border": {
attachment: "teres major",
},
"infraglenoid tubercle": {
attachment: "teres minor and long head of the triceps",
},
"ventral angle": {},
neck: {},
"supraglenoid tubercle": {},
"coracoid process": {
attachment: "coracobrachialis",
},
},
},
humerus: {
asymmetric: true,
features: {
head: {
articulation: "glenoid cavity",
},
"intertubercular groove": {
originates: "biceps brachii",
},
"greater tubercle": {
insertion: "infraspinatus",
},
neck: {},
"cranial surface": {
attachment: "brachiocephalicus",
},
"crest of the greater tubercle": {
insertion: "pectorals and cleidobrachialis",
},
"lateral surface": {},
"deltoid tuberosity": {
insertion: "deltoideus",
},
"tricipital line": {
attachment: "lateral head of triceps",
},
"tuberosity of the teres minor": {},
"brachialis groove": {},
"lateral supracondylar crest": {
attachment: "extensor carpi radialis and anconeus",
},
"caudal surface": {},
"crest of the lesser tubercle": {},
"medial surface": {},
"teres major tuberosity": {
insertion: "teres major and latissimus dorsi",
},
"humeral condyle": {},
trochlea: {
articulation: "radius and ulna",
},
capitulum: { articulation: "head of the radius" },
"lateral epicondyle": {
originates:
"common digital extensor, lateral digital extensor, ulnaris lateralis, supinator",
attachment: "lateral collateral ligament",
},
"medial epicondyle": {
originates:
"flexor carpi radialis, flexor carpi ulnaris, pronator teres, superficial and deep digital flexor",
attachment: "medial collateral ligament",
},
"olecranon fossa": { articulation: "anconeal process of ulna" },
"radial fossa": {},
"supratrochlear foramen": {},
},
},
radius: {
asymmetric: true,
features: {
head: {},
"fovea capitis": {
articulation: "capitulum of humerus",
},
"articular circumference": {
articulation: "radial notch of ulna",
},
"radial tuberosity": {
insertion: "biceps brachii and brachialis",
},
body: {},
trochlea: {},
"ulnar notch": { articulation: "ulna" },
"styloid process": { attachment: "medial collateral ligament" },
},
},
},
muscles: {
"superficial pectoral": {
features: {
"descending pectoral": {
origin: "cranial sternebrae",
insertion: "greater tubercle of humerus",
action: "adduct forelimb",
},
"transverse pectoral": {
origin: "cranial sternebrae",
insertion: "greater tubercle of humerus",
action: "adduct forelimb",
},
},
},
"deep pectoral": {
origin: "ventral sternum",
insertion: "fleshy",
action: "flex and extend shoulder joint",
},
brachiocephalicus: {
features: {
cleidobrachialis: {
origin: "clavicle",
insertion: "cranial border of humerus",
action: "extend shoulder joint",
},
cleidocephalicus: {
origin: "clavicle",
insertion: "occipital bone",
action: "extend shoulder joint",
},
},
},
},
joints: {
humeral: {},
cubiti: {},
metacarpophalangeal: {},
},
other: {
"superficial cervical lymph node": {},
"carotid sheath": {},
},
};
function structure_question(structure, props) {
let quiz_list = [];
quiz_list.push(`id: ${structure}`);
let questions = [];
if ("features" in props) {
for (feature in props["features"]) {
questions = feature_question(
`${feature} of ${structure}`,
props["features"][feature]
);
}
} else {
questions = feature_question(structure, props);
}
for (const question of questions) {
quiz_list.push(question);
}
return quiz_list;
}
function feature_question(feature, props) {
let quiz_list = [];
quiz_list.push(`id: ${feature}`);
for (const prop in props) {
if (prop != "asymmetric") {
quiz_list.push(property_question(feature, prop, props[prop]));
}
}
return quiz_list;
}
function property_question(feature, prop_name, prop_content) {
return `${prop_name}: ${feature} (${prop_content})`;
}
const weights_spinner_div = document.getElementById("weights");
for (structure_type in BoldTerms) {
const form_div = document.createElement("div");
const weight_spinner = document.createElement("input");
weight_spinner.setAttribute("id", `${structure_type}-weight`);
weight_spinner.setAttribute("type", "number");
weight_spinner.setAttribute("value", "10");
const weight_spinner_label = document.createElement("label");
weight_spinner_label.setAttribute("for", `${structure_type}-weight`);
weight_spinner_label.textContent = `${structure_type} `;
form_div.appendChild(weight_spinner_label);
form_div.appendChild(weight_spinner);
weights_spinner_div.append(form_div);
}
// Shamelessly stolen from
// https://github.com/trekhleb/javascript-algorithms/blob/master/src/algorithms/statistics/weighted-random/weightedRandom.js
function weighted_random(items, weights) {
const cumulativeWeights = [];
for (let i = 0; i < weights.length; i += 1) {
cumulativeWeights[i] = weights[i] + (cumulativeWeights[i - 1] || 0);
}
const maxCumulativeWeight =
cumulativeWeights[cumulativeWeights.length - 1];
const randomNumber = maxCumulativeWeight * Math.random();
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
if (cumulativeWeights[itemIndex] >= randomNumber) {
return items[itemIndex];
}
}
}
// Shamelessly stolen from
// https://stackoverflow.com/a/15106541
function random_child(object) {
const keys = Object.keys(object);
const i = Math.floor(Math.random() * keys.length);
return {
key: keys[i],
object: object[keys[i]],
};
}
const terms_list = document.getElementById("quiz-terms");
function generate_quiz() {
terms_list.innerHTML = "";
let weights = [];
let types = [];
for (structure_type in BoldTerms) {
weights.push(
parseInt(document.getElementById(`${structure_type}-weight`).value)
);
types.push(structure_type);
}
const num_questions = document.getElementById("num-questions").value;
for (let i = 0; i <= num_questions; i += 1) {
// Pick the random category
const structure_type = weighted_random(types, weights);
// Pick a random structure from that category
const rand_structure = random_child(BoldTerms[structure_type]);
// Get the list of questions for that structure
const questions = structure_question(
rand_structure.key,
rand_structure.object
);
// Get a random question from that list
const rand_question =
questions[Math.floor(Math.random() * questions.length)];
// Add that structure to the list
let term_item = document.createElement("li");
term_item.innerHTML = rand_question;
terms_list.appendChild(term_item);
}
}
document
.getElementById("generator")
.addEventListener("click", generate_quiz);
generate_quiz();
</script>
</body>