From 28602e13b7c2e3fba032d32060c6fb74483fb354 Mon Sep 17 00:00:00 2001 From: Rui00Barata Date: Fri, 14 Apr 2023 11:59:13 +0100 Subject: [PATCH 1/2] feat(src): Added the abillity to grade multiple choice questions in a particular format --- src/app/learnocaml_exercise_main.ml | 82 ++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/app/learnocaml_exercise_main.ml b/src/app/learnocaml_exercise_main.ml index ad4187c39..ad7fa2b4f 100644 --- a/src/app/learnocaml_exercise_main.ml +++ b/src/app/learnocaml_exercise_main.ml @@ -173,6 +173,65 @@ let () = (* ---- toplevel pane ------------------------------------------------- *) init_toplevel_pane toplevel_launch top toplevel_buttons_group toplevel_button ; (* ---- text pane ----------------------------------------------------- *) + let mcs = Hashtbl.create 3 in + let find_mc str = + let regex = Str.regexp "let mc[^\n\n]+\n\n" in + try + let start_pos = Str.search_forward regex str 0 in + let end_pos = Str.match_end() in + (start_pos, end_pos) + with Not_found -> (-1, -1) + in + let add_answers str mcid start_pos = + let rec aux id = + let c = str.[id] in + match c with + | '\"' -> () + | _ -> Hashtbl.add mcs mcid c; aux (id+1) + in + aux (start_pos+11) + in + let remove_substring str (start_pos, end_pos) id = + let prefix = String.sub str 0 start_pos in + let suffix = String.sub str end_pos (String.length str - end_pos) in + add_answers str id start_pos; + prefix ^ suffix + in + let rec remove_all_mc solution count = + let mc = find_mc solution in + if fst mc <> -1 then + let count = count + 1 in + let solution = remove_substring solution mc count in + remove_all_mc solution count + else + solution + in + + let solution = remove_all_mc solution 0 in + + let tick_mc id form = + let rec tick = function + | [] -> () + | h::t -> + match h with + | 'A' -> form##.A##setAttribute "checked" "checked"; tick t + | 'B' -> form##.B##click (); tick t + | 'C' -> (form##.C)##click (); tick t + | _ -> (form##.D)##click (); tick t + in + let id = id + 1 in + let answers = Hashtbl.find_all mcs id in + tick answers + in + let restore_mcs () = + let document = Js.Unsafe.global##.document in + let text_div = document##getElementById ("learnocaml-exo-tab-text") in + let iframe = text_div##.lastChild in + let doc = iframe##.contentDocument in + let forms = Js.to_array doc##.forms in + Array.iteri (tick_mc) forms; + in + let text_container = find_component "learnocaml-exo-tab-text" in let text_iframe = Dom_html.createIframe Dom_html.document in Manip.replaceChildren text_container @@ -237,7 +296,27 @@ let () = show_loading ~id:"learnocaml-exo-loading" [ messages ; abort_message ] @@ fun () -> Lwt_js.sleep 1. >>= fun () -> - let solution = Ace.get_contents ace in + + let form_results = Buffer.create 5 in + let isChecked checkbox = checkbox##.checked in + let getCheckedValues i f = + let output = Buffer.create 5 in + if isChecked (f##.A) then Buffer.add_string output ("A"); + if isChecked (f##.B) then Buffer.add_string output ("B"); + if isChecked (f##.C) then Buffer.add_string output ("C"); + if isChecked (f##.D) then Buffer.add_string output ("D"); + if Buffer.length output <> 0 then + Buffer.add_string form_results ("let mc" ^ string_of_int (i+1) ^ " = \"" ^ (Buffer.contents output) ^ "\"\n\n") + in + let document = Js.Unsafe.global##.document in + let text_div = document##getElementById ("learnocaml-exo-tab-text") in + let iframe = text_div##.lastChild in + let doc = iframe##.contentDocument in + let forms = Js.to_array (doc##.forms) in + Array.iteri (getCheckedValues) forms; + + let solution = (Buffer.contents form_results) ^ (Ace.get_contents ace) in + Learnocaml_toplevel.check top solution >>= fun res -> match res with | Toploop_results.Ok ((), _) -> @@ -311,5 +390,6 @@ let () = (* ---- return -------------------------------------------------------- *) toplevel_launch >>= fun _ -> typecheck false >>= fun () -> + restore_mcs (); Lwt.return () >>= fun () -> hide_loading ~id:"learnocaml-exo-loading" () ; Lwt.return () From a6b5528c6942491db60d06a6e78f64d516307649 Mon Sep 17 00:00:00 2001 From: Rui00Barata Date: Mon, 17 Apr 2023 09:53:38 +0100 Subject: [PATCH 2/2] docs: Added a tutorial on how to write a multiple choice question exercise --- docs/howto-write-exercises.md | 2 ++ docs/tutorials/step-10.md | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 docs/tutorials/step-10.md diff --git a/docs/howto-write-exercises.md b/docs/howto-write-exercises.md index a61200a85..de6dbdf65 100644 --- a/docs/howto-write-exercises.md +++ b/docs/howto-write-exercises.md @@ -71,3 +71,5 @@ get the files for the second step, and so on and so forth. - Separating the grader code [Step 9 : Introspection of students code](tutorials/step-9.md) + +[Step 10 : Create a trivial multiplce choice question exercise](tutorials/step-10.md) diff --git a/docs/tutorials/step-10.md b/docs/tutorials/step-10.md new file mode 100644 index 000000000..eb66826f8 --- /dev/null +++ b/docs/tutorials/step-10.md @@ -0,0 +1,50 @@ +# Step 10: Create a trivial multiplce choice question exercise + +In this step, we'll create a simple multiple choice question that only requires the student to answer interactively. + +Let's start with the descr.md/html file: + +```html +
+
+ 1. The correct answer is A! + Choose me!
+ Don't choose me!
+ Don't choose me!
+ Don't choose me!
+
+
+``` + +The way that we chose to present multiple choice questions was with html forms. Each form has four possible options represented with the name `A`, `B`, `C` or `D`. +The student then select one or more options directly in the exercise description. + +The student can then complete the rest of the exercise or grade it to check if his choice was the right one. The grader then verifies which choice was clicked and compares it to the solution file in the form of a string. + +The `solution.ml`, for this example should have: + +```ocaml +let mc1 = "A" +``` + +Having the solutions in the form of a string the exercise can have multiple right answer. If thats the case the solution must be written alphabetecly. + +Finally, the test function is just a normal function that compares two variables. +In this example the `test.ml` should be: + +```ocaml +open Test_lib +open Report + +let multipleChoice1_test = + set_progress "A corrigir pergunta 1" ; + Section ([ Text "ExercĂ­cio 1: " ; Code "solution" ], + test_variable_against_solution + [%ty: string] + "mc1") + +let () = + set_result @@ + ast_sanity_check code_ast @@ fun () -> + [ multipleChoice1_test ] +```