-
Notifications
You must be signed in to change notification settings - Fork 125
Expand file tree
/
Copy pathmastery.jme
More file actions
239 lines (191 loc) · 7.58 KB
/
mastery.jme
File metadata and controls
239 lines (191 loc) · 7.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Mastery diagnostic script
// The student must answer every question correctly.
// They start with a topic that has no dependencies.
// After answering a question, if they get it correct, it's done forever.
// If it's incorrect, the question is put on the end of that topic's "queue",
// so they'll be asked it again later.
// Once all the questions in the topic are answered correctly, the next topic
// with no unmet dependencies is picked.
//////////////
// Functions
//////////////
update_where (Update items in a list which satisfy the given predicate, applying the given function to them):
((predicate, action, list) -> (if(predicate(x), action(x), x) for: x of: list))
question_queue_for_topic (When starting a topic, this function makes a queue of questions which must be answered):
(topic) -> (
["question": q, "status": "unknown"]
for: q
of: topic["topic"]["questions"]
)
start_topic (A function to update the state, setting the current topic and filling the question queue from that topic):
(state,topic) ->
merge(
state,
[
"current_topic": topic,
"question_queue": question_queue_for_topic(topic)
]
)
get_next_question (A function to get the next question from the queue):
(state) ->
let(
queue, state["question_queue"],
if(len(queue)>0,
queue[0]["question"],
nothing
)
)
next_topic (The next topic to assess):
(state) ->
let(
topics, state["topics"], // List of the state object for each topic
topicdict, dict([t["topic"]["name"],t] for: t of: topics), // A mapping from topic names to topic state objects
available_topics, // Topics that we can move to next: either no dependencies, or all their dependencies have been passed.
filter(
t -> let(
all_deps_passed, all(topicdict[topicname]["status"] <> "unknown" for: topicname of: t["topic"]["depends_on"]),
all_deps_passed and t["status"]="unknown"
)
, topics
),
if(len(available_topics)>0,available_topics[0],nothing)
)
/////////////////////
// Initial variables
/////////////////////
first_topic (The first topic to assess):
// Picks the first topic which doesn't depend on anything.
let(
topics, pre_state["topics"],
filter(t -> len(t["topic"]["depends_on"])=0, topics)[0]
)
first_question (The first question to show the student):
get_next_question(state)
pre_state (A template for the `state` variable, which will be filled in with the chosen start topic):
[
"topics": // For each topic, both the given info about that topic and a status, either "passed" or "unknown".
[
"topic": topic,
"status": if(len(topic["questions"])=0,"passed","unknown") // A topic is "passed" if there are no questions left unasked.
]
for: topic
of: values(topics)
,
"finished": false // Is the exam over?
]
state (The initial state variable):
start_topic(pre_state, first_topic)
/////////////////////////////
// Notes used when moving on
/////////////////////////////
correct (Did the student get the current question right?):
current_question["credit"]=1
after_answering (The state after the student answers a question):
let(
queue, state["question_queue"],
empty_queue, len(queue) = 0,
nquestion,
// Set the status of this question in the queue.
if(not empty_queue,
merge(
queue[0],
["status": if(correct,"passed","failed")]
),
nothing
),
nqueue,
// Change the queue: either remove the current question if correct, or add it to the end.
queue[1..len(queue)] + if(correct or empty_queue, [], [nquestion]),
ntopics,
// Update the list of topics, setting the current topic to "passed" if the queue is now empty.
if(len(nqueue)=0,
update_where(t -> t=state["current_topic"], t -> t+["status": "passed"], state["topics"]),
state["topics"]
),
merge(
// Return a new state with the new list of topics and question queue
state,
["topics": ntopics, "question_queue": nqueue]
)
)
///////////
// Actions
///////////
action_next_question_same_topic (Move to the next question in the queue):
[
"label": translate("diagnostic.move to next question in topic"),
"state": after_answering,
"next_question": get_next_question(after_answering)
]
action_next_topic (Move to the next topic):
let(
state, after_answering, // Start with the state we get from answering the question.
topic, next_topic(state), // Pick a new topic.
nstate,
if(topic <> nothing,
start_topic(state, topic) // Update the state with the new topic.
,
state // Otherwise, there's no next topic, so this action won't be used.
),
[
"label": translate("diagnostic.move to next topic"),
"state": nstate,
"next_question": get_next_question(nstate)
]
)
next_actions (The list of possible actions after answering a question):
let(
state, after_answering,
queue_empty, len(state["question_queue"])=0,
actions,
switch(
not queue_empty,
[action_next_question_same_topic] // Move to the next question in the queue
, next_topic(state) <> nothing,
[action_next_topic] // Move to the next topic
,
[] // End the exam
),
[
"feedback": "",
"actions": actions
]
)
after_exam_ended (The state after the exam has finished):
merge(
after_answering,
["finished": true]
)
//////////////////
// Feedback notes
//////////////////
progress (Summarise the student's progress through the exam):
let(
passed_topics, filter(t -> t["status"] = "passed", state["topics"])
, num_passed_topics, len(passed_topics)
, num_topics, len(state["topics"])
, exam_progress, num_passed_topics/num_topics
, topic_credit, 1-len(state["question_queue"])/len(state["current_topic"]["topic"]["questions"])
, current_topic, state["current_topic"]["topic"]["name"]
, lo_progress,
let(
ltopics, filter(t -> lo["name"] in t["topic"]["learning_objectives"], state["topics"]),
passed, filter(t -> t["status"]="passed", ltopics),
p, len(passed)/len(ltopics),
["name": lo["name"], "progress": p, "credit": p]
)
for: lo
of: learning_objectives
, topic_progress, [["name": "Current topic: {current_topic}", "progress": topic_credit, "credit": topic_credit]]
, if(state["finished"], [], topic_progress)
+ lo_progress
+ [
["name": translate("control.total"), "progress": exam_progress, "credit": exam_progress]
]
)
feedback (A text description of the current state):
if(state["finished"],
translate("diagnostic.complete")
,
translate("diagnostic.studying topic", ["topic": state["current_topic"]["topic"]["name"]])
)