314 lines
9.5 KiB
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>
|