-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtemplate_python_main_script.py
495 lines (396 loc) · 18.8 KB
/
template_python_main_script.py
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
from browser import document, html
version = "##VERSION##"
raw_transcriptions = {
##RAW_TRANSCRIPTIONS##
}
characters = [
"LE DOCTEUR STOCKMANN",
"MADAME STOCKMANN",
"PETRA",
"EILIF",
"MORTEN",
"LE BOURGMESTRE",
"MORTEN KILL",
"HOVSTAD",
"MADAME BILLING",
"HORSTER",
"ASLAKSEN",
]
didascalie_str = "Didascalie"
n_actes = 5
n_parts = [4, 5, 4, 3, 5]
scene_ids = []
scene_pretty_names = {}
for i_acte in range(1, n_actes+1):
for i_part in range(1, n_parts[i_acte-1]+1):
scene_id = f"acte_{i_acte}_part_{i_part}"
scene_ids.append(scene_id)
scene_pretty_names[scene_id] = f"Acte {i_acte} - Partie {i_part}"
def deepcopy(e):
if isinstance(e, list):
return [deepcopy(a) for a in e]
elif isinstance(e, dict):
return {deepcopy(k): deepcopy(v) for k, v in e.items()}
else:
return e
# inspired from https://stackoverflow.com/questions/3062746/special-simple-random-number-generator
seed = 0
def randint(n):
global seed
seed = (1103515245 * seed + 12345) % 2**31
return seed % n
# Observer pattern implementation adapted from
# https://stackoverflow.com/questions/1904351/python-observer-pattern-examples-tips/1925836#1925836
# and https://stackoverflow.com/questions/6190468/how-to-trigger-function-on-value-change
class Observable(object):
def __init__(self, value):
self._old_value = None
self._value = value
self.callbacks = []
@property
def old_value(self):
return self._old_value
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._old_value = self._value
self._value = value
self.fire()
def subscribe(self, callback):
self.callbacks.append(callback)
def fire(self):
for fn in self.callbacks:
fn(self)
def get_question_button_action(panel, button_index, callback):
def question_button_action(event):
panel.remove()
callback(button_index)
return question_button_action
def create_question_panel(message, button_contents, callback):
panel = html.DIV()
panel <= message
panel <= html.HR()
for button_index, button_content in enumerate(button_contents):
button = html.BUTTON(button_content)
button.bind("click", get_question_button_action(panel, button_index, callback))
panel <= button
return panel
class SelectionScreen:
def __init__(self, transcriptions):
self.transcriptions = transcriptions
self.character_observable = Observable(None)
self.scene_observable = Observable(None)
self.start_observable = Observable(False)
self.html_root = self.create_root()
self.character_observable.fire()
self.scene_observable.fire()
def get_html_component(self):
return self.html_root
def create_root(self):
root = html.DIV()
root <= html.DIV(html.B(f"Replicator - Un Ennemi du peuple ({version})")) + html.BR()
table = html.TABLE()
table <= html.TR(html.TD("1. Sélectionner le personnage", Id="char_sel_panel_title")
+ html.TD("2. Sélectionner la scène", Id="scene_sel_panel_title"))
table <= html.TR(html.TD(self.create_character_selection_panel(), Id="char_sel_panel_td")
+ html.TD(self.create_scene_selection_panel(), Id="scene_sel_panel_td"))
root <= table
root <= html.HR()
root <= html.DIV("3. Jouer", Id="start_game_panel_title")
root <= html.DIV(self.create_start_game_panel(), Id="start_game_panel")
return root
def remove_root(self):
self.html_root.remove()
def create_character_selection_panel(self):
character_selection_panel = html.DIV(Id="char_sel")
for character in characters:
button = html.BUTTON(character, Class="char_button")
def get_button_action(character):
def button_action(event):
self.character_observable.value = character
self.scene_observable.value = None
# print(self.character_observable.value)
return button_action
button.bind("click", get_button_action(character))
def get_character_callback(button, character):
def callback(observable):
if character == observable.value:
if "char_button_selected" not in button.classList:
button.classList.add("char_button_selected")
else:
if "char_button_selected" in button.classList:
button.classList.remove("char_button_selected")
return callback
self.character_observable.subscribe(get_character_callback(button, character))
character_selection_panel <= html.DIV(button)
return character_selection_panel
def create_scene_selection_panel(self):
scene_selection_panel = html.DIV()
for scene_id in scene_ids:
scene_pretty_name = scene_pretty_names[scene_id]
if scene_id in self.transcriptions:
button = html.BUTTON(scene_pretty_name, Class="scene_button")
def get_button_action(scene_id):
def button_action(event):
self.scene_observable.value = scene_id
return button_action
button.bind("click", get_button_action(scene_id))
def get_character_callback(button, scene_id):
def callback(observable):
if observable.value in self.transcriptions[scene_id]["characters"]:
if "disabled" in button.attrs:
del button.attrs["disabled"]
else:
button.attrs["disabled"] = None
return callback
self.character_observable.subscribe(get_character_callback(button, scene_id))
def get_scene_callback(button, scene_id):
def callback(observable):
if scene_id == observable.value:
if "scene_button_selected" not in button.classList:
button.classList.add("scene_button_selected")
else:
if "scene_button_selected" in button.classList:
button.classList.remove("scene_button_selected")
return callback
self.scene_observable.subscribe(get_scene_callback(button, scene_id))
scene_selection_panel <= html.DIV(button)
else:
scene_selection_panel <= html.DIV(f"({scene_pretty_name} - manquant)", Class="missing_scene")
return scene_selection_panel
def create_start_game_panel(self):
start_game_panel = html.DIV()
button = html.BUTTON("Commencer !", Id="start_game_button")
def button_action(event):
self.start_observable.value = True
button.bind("click", button_action)
def get_scene_callback(button):
def callback(observable):
if observable.value:
if "disabled" in button.attrs:
del button.attrs["disabled"]
else:
button.attrs["disabled"] = None
return callback
self.scene_observable.subscribe(get_scene_callback(button))
start_game_panel <= button
return start_game_panel
class App:
HISTORY_LENGTH = 4
def __init__(self):
self.selected_character = None
self.selected_scene = None
self.selected_blocs = None
self.selected_bloc_lines = None
self.sequential_progress = None
self.base_line_scores = None
self.base_line_score_resume = None
self.total_line_scores = None
self.final_line_scores = None
self.final_line_score_resume = None
self.transcriptions = {}
for scene, raw_transcription in raw_transcriptions.items():
blocs = []
scene_characters = set()
new_character = True
for file_line in raw_transcription["text"].split("\n"):
file_line = file_line.strip()
if not file_line:
new_character = True
elif file_line[0] != "#":
if new_character:
line_characters = file_line.split(" & ")
blocs.append({"characters": line_characters, "lines": []})
scene_characters = scene_characters.union(line_characters)
new_character = False
else:
blocs[-1]["lines"].append(file_line)
self.transcriptions[scene] = {
"blocs": blocs,
"characters": scene_characters
}
def start(self):
self.start_selection()
def start_selection(self):
selection_screen = SelectionScreen(self.transcriptions)
def start_observable_callback(observable):
if observable.value:
selection_screen.remove_root()
self.selected_character = selection_screen.character_observable.value
self.selected_scene = selection_screen.scene_observable.value
self.selected_blocs = self.transcriptions[self.selected_scene]["blocs"]
self.selected_bloc_lines = [(bloc_index, line_index)
for bloc_index, bloc in enumerate(self.selected_blocs)
if self.selected_character in bloc["characters"]
for line_index in range(len(bloc["lines"]))]
self.base_evaluation_introduction()
selection_screen.start_observable.subscribe(start_observable_callback)
document <= selection_screen.get_html_component()
def base_evaluation_introduction(self):
message = html.DIV("""\
Le jeu commence d'abord en mode séquentiel.
Les répliques du personnage sont présentées dans l'ordre.""")
button_contents = ["C'est parti !"]
def callback(button_index):
self.base_evaluation()
document <= create_question_panel(message, button_contents, callback)
def base_evaluation(self):
def callback(line_scores):
self.base_line_scores = line_scores
self.total_line_scores = deepcopy(self.base_line_scores)
self.set_random_seed()
self.show_base_scores()
self.evaluation(callback)
def set_random_seed(self):
global seed
seed = 0
for line_score in self.base_line_scores.values():
seed *= 3
seed += line_score["almost"] + 2*line_score["ko"]
seed %= 2**31
def evaluation(self, parent_callback, bloc_line_index=0, line_scores=None):
if line_scores is None:
line_scores = {line: {"perfect": 0, "almost": 0, "ko": 0} for line in self.selected_bloc_lines}
if bloc_line_index < len(self.selected_bloc_lines):
def callback(result):
line_scores[self.selected_bloc_lines[bloc_line_index]][result] += 1
self.evaluation(parent_callback, bloc_line_index=bloc_line_index+1, line_scores=line_scores)
self.line_evaluation(callback, bloc_line_index)
else:
parent_callback(line_scores)
def line_evaluation(self, parent_callback, bloc_line_index):
# construct text
text = []
bloc_index, line_index = self.selected_bloc_lines[bloc_line_index]
for back_count in range(self.HISTORY_LENGTH):
raw_line = self.selected_blocs[bloc_index]["lines"][line_index]
end = raw_line.find(')')
div = html.DIV(Class=f"bloc_line_back_{back_count}")
div <= html.EM(raw_line[:end+1]) + html.SPAN(raw_line[end+1:])
bloc_characters = self.selected_blocs[bloc_index]["characters"]
if didascalie_str in bloc_characters:
div.classList.add("didascalie")
text.insert(0, div)
if line_index == 0 or back_count == self.HISTORY_LENGTH - 1:
if didascalie_str not in bloc_characters:
characters_text = " & ".join(bloc_characters)
text.insert(0, html.DIV(characters_text, Class="character_in_text"))
text.insert(0, html.BR())
if line_index > 0:
line_index -= 1
else:
if bloc_index > 0:
bloc_index -= 1
line_index = len(self.selected_blocs[bloc_index]["lines"]) - 1
else:
break
def autonote_callback(button_index):
result = ["perfect", "almost", "ko"][button_index]
if button_index == 0:
parent_callback(result)
else:
text.extend([html.HR(), html.DIV("Répéter les phrases en couleur jusqu'à les connaître par cœur.")])
button_contents = ["C'est fait."]
document <= create_question_panel(text, button_contents, lambda i: parent_callback(result))
def question_callback(button_index):
text[-1].classList.remove("hidden")
if button_index == 0:
button_contents = ["J'ai tout bon 😁", "Presque 😊", "Pas bon 😓"]
document <= create_question_panel(text, button_contents, autonote_callback)
else:
autonote_callback(2)
# question
question_button_contents = ["Je me souviens de la suite et je l'ai récitée.",
"Je ne me souviens pas de la suite 😔"]
text[-1].classList.add("hidden")
document <= create_question_panel(text, question_button_contents, question_callback)
def show_base_scores(self):
self.base_line_score_resume = {"perfect": 0, "almost": 0, "ko": 0}
for v in self.base_line_scores.values():
for k in self.base_line_score_resume:
self.base_line_score_resume[k] += v[k]
# print("show_base_scores")
# print(self.base_line_scores)
# print(self.base_line_score_resume)
message = html.DIV()
message <= html.DIV("Voici votre score de base :")
message <= html.DIV(f"😁 (Parfait !!!) : {self.base_line_score_resume['perfect']}")
message <= html.DIV(f"😊 (Presque !) : {self.base_line_score_resume['almost']}")
message <= html.DIV(f"😔 (À réviser) : {self.base_line_score_resume['ko']}")
message <= html.BR() + html.DIV("Maintenant, le programme va vous soumettre des répliques dans un ordre "
"aléatoire, en privilégiant celles posant difficultés.")
button_contents = ["Ok, c'est parti !"]
def callback(button_index):
self.random_focus(0, None)
document <= create_question_panel(message, button_contents, callback)
def random_focus(self, progress, last_bloc_line_index):
progress += 1
if progress > 2 * len(self.selected_bloc_lines):
self.show_final_evaluation_message()
else:
print("choosing random bloc line")
print(self.total_line_scores)
# choose bloc line. Lexicographic order. Don't choose last bloc/line (except if only one)
worst_score_number = 1000000000
worst_indexes = [last_bloc_line_index]
for bloc_line_index, (bloc, line) in enumerate(self.selected_bloc_lines):
if bloc_line_index == last_bloc_line_index:
continue
score = self.total_line_scores[(bloc, line)]
score_number = score["perfect"]*1000000 + score["almost"]*1000 + score["ko"]
if score_number < worst_score_number:
worst_score_number = score_number
worst_indexes = []
if score_number == worst_score_number:
worst_indexes.append(bloc_line_index)
bloc_line_index = worst_indexes[randint(len(worst_indexes))]
print(self.selected_bloc_lines[bloc_line_index])
def callback(result):
self.total_line_scores[self.selected_bloc_lines[bloc_line_index]][result] += 1
self.random_focus(progress, bloc_line_index)
self.line_evaluation(callback, bloc_line_index)
def show_final_evaluation_message(self):
message = html.DIV()
message <= html.DIV("On y est presque !")
message <= html.BR()
message <= html.DIV("La dernière étape reprend le texte dans l'ordre pour remettre les choses en place "
"et évaluer la progression.")
button_contents = ["Allons-y ! [Alonzo]"]
def callback(button_index):
self.final_evaluation()
document <= create_question_panel(message, button_contents, callback)
def final_evaluation(self):
def callback(line_scores):
self.final_line_scores = line_scores
for k, v in line_scores.items():
for result in ["perfect", "almost", "ko"]:
self.total_line_scores[k][result] += v[result]
self.show_final_scores()
self.evaluation(callback)
def show_final_scores(self):
self.final_line_score_resume = {"perfect": 0, "almost": 0, "ko": 0}
for v in self.final_line_scores.values():
for k in self.final_line_score_resume:
self.final_line_score_resume[k] += v[k]
message = html.DIV()
message <= html.DIV("Bravo ! Vous êtes allé jusqu'au bout !")
message <= html.BR()
message <= html.DIV("Voici votre progression :")
message <= html.DIV(f"😁 (Parfait !!!) : {self.base_line_score_resume['perfect']} => {self.final_line_score_resume['perfect']}")
message <= html.DIV(f"😊 (Presque !) : {self.base_line_score_resume['almost']} => {self.final_line_score_resume['almost']}")
message <= html.DIV(f"😔 (À réviser) : {self.base_line_score_resume['ko']} => {self.final_line_score_resume['ko']}")
message <= html.BR()
message <= html.DIV("Vous pouvez maintenant continuer à travailler sur cette scène ou revenir à l'écran de "
"sélection.")
button_contents = ["Continuer avec cette scène", "Revenir à l'écran de sélection"]
def callback(button_index):
if button_index == 0:
self.random_focus(0, None)
else:
self.start_selection()
document <= create_question_panel(message, button_contents, callback)
if __name__ == '__main__':
app = App()
app.start()