From c082d716e3a59d4ccb084fcb3ee68e29f44fcf74 Mon Sep 17 00:00:00 2001
From: Jan Maximilian Michal <j.michal@stud.uni-goettingen.de>
Date: Wed, 23 Nov 2016 02:57:04 +0000
Subject: [PATCH] Added three new scripts. Selection gaps implemented and other
 minor improvements.

---
 grim.py                                       |  41 +++++--
 hallgrim/IliasXMLCreator/gap.py               |  65 ++++++++---
 hallgrim/IliasXMLCreator/multi.py             |  13 ++-
 hallgrim/IliasXMLCreator/packer.py            |   5 +-
 hallgrim/IliasXMLCreator/single.py            |   4 +-
 hallgrim/IliasXMLCreator/xmlBuildingBlocks.py |  68 +++++++++---
 hallgrim/parser.py                            |  44 +++++++-
 scripts/feldarbeit_param.py                   |  63 +++++++++++
 scripts/nur_sterne_sehen.py                   |  42 +++++++
 scripts/originale_kopien.py                   |  33 ++++++
 scripts/zeilen_sind_anders.py                 | 105 ++++++++++++++++++
 11 files changed, 430 insertions(+), 53 deletions(-)
 create mode 100644 scripts/feldarbeit_param.py
 create mode 100644 scripts/nur_sterne_sehen.py
 create mode 100644 scripts/originale_kopien.py
 create mode 100644 scripts/zeilen_sind_anders.py

diff --git a/grim.py b/grim.py
index 346c9b8..325ab5d 100755
--- a/grim.py
+++ b/grim.py
@@ -20,6 +20,8 @@ def type_selector(type):
         return 'MULTIPLE CHOICE QUESTION'
     if 'single' in type:
         return 'SINGLE CHOICE QUESTION'
+    if 'gap' in type:
+        return 'CLOZE QUESTION'
 
 
 def file_exists(path):
@@ -28,8 +30,7 @@ def file_exists(path):
         raise argparse.ArgumentTypeError(msg)
     return path
 
-def script_is_valid(script):
-    required = ['meta', 'task', 'choices', 'feedback']
+def script_is_valid(script, required):
     for field in required:
         if not hasattr(script, field):
             error("script does not export '{}' field.".format(field))
@@ -93,16 +94,42 @@ def parseme():
     args = parser.parse_args()
 
     if args.command == 'gen':
-        handle_choice_questions(args.out, args.input, args.instances)
+        delegator(args.out, args.input, args.instances)
     if args.command == 'new':
         handle_new_script(args.name, args.type, args.author, args.points)
     if args.command == None:
         parser.print_help()
 
-
-def handle_choice_questions(output, script_name, instances):
+def delegator(output, script_name, instances):
     script = importlib.import_module(file_to_module(script_name))
-    script_is_valid(script)
+    handler = {
+        'gap' : handle_gap_questions,
+        'single choice' : handle_choice_questions,
+        'multiple choice' : handle_choice_questions
+    }[script.meta['type']]
+
+    handler(output, script, instances)
+
+def handle_gap_questions(output, script, instances):
+    script_is_valid(script, required=['meta', 'task', 'feedback'])
+    data = {
+        'type': type_selector(script.meta['type']),
+        'description': "_description",
+        'gap_list': gap_parser(script.task),
+        'author': script.meta['author'],
+        'title': script.meta['title'],
+        'shuffle': script.meta['shuffle'] if 'shuffle' in script.meta else True,
+        'feedback': markdown(script.feedback),
+        'gap_length': script.meta['gap_length'] if 'gap_length' in script.meta else 20,
+    }
+
+    output = os.path.join(
+        'output', script.meta['title']) + '.xml' if not output else output
+    packer.convert_and_print(data, output, instances)
+    info('Processed "{}" and wrote xml to "{}".'.format(script.__name__, output))
+
+def handle_choice_questions(output, script, instances):
+    script_is_valid(script, required=['meta', 'task', 'choices', 'feedback'])
     data = {
         'type': type_selector(script.meta['type']),
         'description': "_description",
@@ -118,7 +145,7 @@ def handle_choice_questions(output, script_name, instances):
     output = os.path.join(
         'output', script.meta['title']) + '.xml' if not output else output
     packer.convert_and_print(data, output, instances)
-    info('Processed "{}" and wrote xml to "{}".'.format(script_name, output))
+    info('Processed "{}" and wrote xml to "{}".'.format(script.__name__, output))
 
 
 def handle_new_script(name, type, author, points):
diff --git a/hallgrim/IliasXMLCreator/gap.py b/hallgrim/IliasXMLCreator/gap.py
index fbe51a9..61470b7 100644
--- a/hallgrim/IliasXMLCreator/gap.py
+++ b/hallgrim/IliasXMLCreator/gap.py
@@ -2,32 +2,38 @@ import xml.etree.ElementTree as et
 
 from hallgrim.IliasXMLCreator.xmlBuildingBlocks import *
 
+def xml_print(element, **kwargs):
+    import xml.dom.minidom
+
+    xml = xml.dom.minidom.parseString(et.tostring(element, encoding='utf8', method='xml')) # or xml.dom.minidom.parseString(xml_string)
+    print( xml.toprettyxml(), **kwargs )
+
 
 ###
 # Solution 1
 # ['text', 'text', 'text']
-# [('gap_solution', points, length), ..., (['one', 'two', 'three'], points, length)]
+# [('gap_solution', points), ..., (['one', 'two', 'three'], points)]
 #
 # str : str_gap
 # list : choice_gap
 # tuple : num_gap (x, min, max)
 #
 # Solution 2
-# ['text', ('gap_solution', points, length), ('gap_solution', points, length), 'text', ...]
+# ['text', ('gap_solution', points), ('gap_solution', points), 'text', ...]
 #
 ###
 
 class GapQuestion:
     """docstring for GapQuestion"""
-    def __init__(self, type, description, gap_list, author, title, questions, feedback, gapLength):
+    def __init__(self, type, description, gap_list, author, title, shuffle, feedback, gap_length):
         self.type           = type
         self.description    = description
         self.gap_list       = gap_list
         self.author         = author
         self.title          = title
-        self.questions      = questions
+        self.shuffle        = shuffle
         self.feedback       = feedback
-        self.gapLength      = gapLength
+        self.gap_length     = gap_length
 
         self.itemmetadata       = self.itemmetadata(feedback_setting=1)
         self.presentation       = self.presentation()
@@ -45,7 +51,7 @@ class GapQuestion:
         subroot.append(qtimetadatafield('additional_cont_edit_mode', 'default'))
         subroot.append(qtimetadatafield('externalId', '99.99'))
         subroot.append(qtimetadatafield('textgaprating', 'ci'))
-        subroot.append(qtimetadatafield('fixedTextLength', str(self.gapLength)))
+        subroot.append(qtimetadatafield('fixedTextLength', str(self.gap_length)))
         subroot.append(qtimetadatafield('identicalScoring', '1'))
         subroot.append(qtimetadatafield('combinations', 'W10='))
         root = et.Element('itemmetadata')
@@ -62,24 +68,49 @@ class GapQuestion:
             if type(item) == str:
                 f = material(item)
             if type(item) == tuple:
-                if type(item[0] == list):
-                    f = response_choice(gap_ident, item[0])
-                if type(item[0] == str):
-                    f = response_str(gap_ident, self.gapLength)
-                if type(item[0] == tuple):
-                    f = response_num(gap_ident, self.gapLength, item[1], item[2]):
+                if type(item[0]) == list:
+                    f = response_choice("gap_{}".format(gap_ident), item[0])
+                if type(item[0]) == str:
+                    f = response_str("gap_{}".format(gap_ident), self.gap_length)
+                if type(item[0]) == tuple:
+                    f = response_num("gap_{}".format(gap_ident), self.gap_length, item[1], item[2])
                 gap_ident += 1
             flow.append(f)
+
+
         return root
 
     ############################################################################
     def resprocessing(self):
         root = et.Element('resprocessing')
         outcomes = et.Element('outcomes')
-        outcomes.append(simple_elemet('decvar'))
+        outcomes.append(simple_element('decvar'))
         root.append(outcomes)
-        is_gap = lambda t: type(t) = tuple
-        for i, (_, correct, points) in enumerate(filter(is_gap, self.gap_list)):
-            root.append(respcondition(points if correct else 0, i, True))
-            root.append(respcondition(points if not correct else 0, i, False))
+        is_gap = lambda t: type(t) == tuple
+        for i, (answer, points) in enumerate(filter(is_gap, self.gap_list)):
+            if type(answer) == list:
+                for j, (choice, points) in enumerate(answer): # verschattung
+                    root.append(respcondition_gap(points, i, choice, j))
+            if type(answer) == str:
+                root.append(respcondition_gap(points, i, answer))
+            if type(answer) == tuple:
+                root.append(respcondition_gap(points, i, answer[0]))
         return root
+
+    ### returns the final object ###############################################
+    def create_item(self):
+        """ This method stacks all the previously created structures together"""
+        item = et.Element('item', attrib={
+            'ident': 'undefined',
+            'title': self.title,
+            'maxattempts': "99"
+        })
+
+        item.append(simple_element('description', text=self.description))
+        item.append(simple_element('duration', text='P0Y0M0DT0H30M0S')) # 30 min
+        item.append(self.itemmetadata)
+        item.append(self.presentation)
+        item.append(self.resprocessing)
+        item.append(itemfeedback('response_allcorrect', self.feedback))
+        item.append(itemfeedback('response_onenotcorrect', self.feedback))
+        return item
diff --git a/hallgrim/IliasXMLCreator/multi.py b/hallgrim/IliasXMLCreator/multi.py
index 7836a22..9c9b91e 100644
--- a/hallgrim/IliasXMLCreator/multi.py
+++ b/hallgrim/IliasXMLCreator/multi.py
@@ -2,6 +2,7 @@ import xml.etree.ElementTree as et
 
 from hallgrim.IliasXMLCreator.xmlBuildingBlocks import *
 
+
 class MultipleChoiceQuestion:
     """docstring for MultipleChoiceQuestion"""
     def __init__(self, type, description, question_text, author, title, maxattempts, questions, feedback, shuffle=True):
@@ -30,7 +31,7 @@ class MultipleChoiceQuestion:
         subroot = et.Element('qtimetadata')
         subroot.append(qtimetadatafield('ILIAS_VERSION', '5.1.8 2016-08-03'))
         subroot.append(qtimetadatafield('QUESTIONTYPE', self.type))
-        subroot.append(qtimetadatafield('AUTHOR',  self.author))
+        subroot.append(qtimetadatafield('AUTHOR', self.author))
         subroot.append(qtimetadatafield('additional_cont_edit_mode', 'default'))
         subroot.append(qtimetadatafield('externalId', '99.99'))
         subroot.append(qtimetadatafield('thumb_size', None))
@@ -62,11 +63,11 @@ class MultipleChoiceQuestion:
     def resprocessing(self):
         root = et.Element('resprocessing')
         outcomes = et.Element('outcomes')
-        outcomes.append(simple_elemet('decvar'))
+        outcomes.append(simple_element('decvar'))
         root.append(outcomes)
         for i, (_, correct, points) in enumerate(self.questions):
-            root.append(respcondition(points if correct else 0, i, True))
-            root.append(respcondition(points if not correct else 0, i, False))
+            root.append(respcondition(points if correct else 0, 'MCMR', i, True))
+            root.append(respcondition(points if not correct else 0, 'MCMR', i, False))
         return root
 
     ### returns the final object ###############################################
@@ -78,8 +79,8 @@ class MultipleChoiceQuestion:
             'maxattempts': self.maxattempts
         })
 
-        item.append(simple_elemet('description', text=self.description))
-        item.append(simple_elemet('duration', text='P0Y0M0DT0H30M0S')) # 30 min
+        item.append(simple_element('description', text=self.description))
+        item.append(simple_element('duration', text='P0Y0M0DT0H30M0S')) # 30 min
         item.append(self.itemmetadata)
         item.append(self.presentation)
         item.append(self.resprocessing)
diff --git a/hallgrim/IliasXMLCreator/packer.py b/hallgrim/IliasXMLCreator/packer.py
index 0226593..f3cccb6 100644
--- a/hallgrim/IliasXMLCreator/packer.py
+++ b/hallgrim/IliasXMLCreator/packer.py
@@ -1,7 +1,6 @@
 import xml.etree.ElementTree as et
 
-from hallgrim.IliasXMLCreator import multi, single
-
+from hallgrim.IliasXMLCreator import multi, single, gap
 
 def create_xml_tree(item_list):
     root = et.Element('questestinterop')
@@ -16,6 +15,8 @@ def convert_and_print(data, output, instances=1):
         item_list = [multi.MultipleChoiceQuestion(**data)() for _ in range(instances)]
     if data['type'] == 'SINGLE CHOICE QUESTION':
         item_list = [single.SingleChoiceQuestion(**data)() for _ in range(instances)]
+    if data['type'] == 'CLOZE QUESTION':
+        item_list = [gap.GapQuestion(**data)() for _ in range(instances)]
 
     tree = create_xml_tree(item_list)
     tree.write(output, encoding="utf-8", xml_declaration=True)
diff --git a/hallgrim/IliasXMLCreator/single.py b/hallgrim/IliasXMLCreator/single.py
index e69b9a5..747fd68 100644
--- a/hallgrim/IliasXMLCreator/single.py
+++ b/hallgrim/IliasXMLCreator/single.py
@@ -10,8 +10,8 @@ class SingleChoiceQuestion(MultipleChoiceQuestion):
     def resprocessing(self):
         root = et.Element('resprocessing')
         outcomes = et.Element('outcomes')
-        outcomes.append(simple_elemet('decvar'))
+        outcomes.append(simple_element('decvar'))
         root.append(outcomes)
         for i, (_, correct, points) in enumerate(self.questions):
-            root.append(respcondition(points if correct else 0, i, True))
+            root.append(respcondition(points if correct else 0, 'MCSR', i, True))
         return root
diff --git a/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py b/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py
index 428f184..c35ebac 100644
--- a/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py
+++ b/hallgrim/IliasXMLCreator/xmlBuildingBlocks.py
@@ -10,7 +10,14 @@ import xml.etree.ElementTree as et
 
 ##########################################################################
 
-def simple_elemet(name, text=None, attrib={}):
+def xml_print(element, **kwargs):
+    import xml.dom.minidom
+
+    xml = xml.dom.minidom.parseString(et.tostring(element, encoding='utf8', method='xml')) # or xml.dom.minidom.parseString(xml_string)
+    print( xml.toprettyxml(), **kwargs )
+
+
+def simple_element(name, text=None, attrib={}):
     if not text:
         return et.Element(name, attrib=attrib)
     node = et.Element(name, attrib=attrib)
@@ -20,14 +27,14 @@ def simple_elemet(name, text=None, attrib={}):
 
 def qtimetadatafield(label, entry):
     root = et.Element('qtimetadatafield')
-    root.append(simple_elemet('fieldlabel', text=label))
-    root.append(simple_elemet('fieldentry', text=entry))
+    root.append(simple_element('fieldlabel', text=label))
+    root.append(simple_element('fieldentry', text=entry))
     return root
 
 
 def material(content):
     material = et.Element('material')
-    material.append(simple_elemet(
+    material.append(simple_element(
         'mattext',
         text=content,
         attrib={'texttype': 'text/xhtml'}
@@ -40,14 +47,16 @@ def response_label(content, count):
     response_label.append(material(content))
     return response_label
 
+def displayfeedback(gap=False):
+    pass
 
-def respcondition(points, count, correct=True):
+def respcondition(points, respident, count, correct=True):
     root = et.Element('respcondition', attrib={'continue': 'Yes'})
     conditionvar = et.Element('conditionvar')
-    varequal = simple_elemet(
+    varequal = simple_element(
         'varequal',
         text=str(count),
-        attrib={'respident': 'MCMR'}
+        attrib={'respident': respident}
     )
 
     if correct:
@@ -59,7 +68,7 @@ def respcondition(points, count, correct=True):
 
     root.append(conditionvar)
 
-    setvar = simple_elemet(
+    setvar = simple_element(
         'setvar',
         text=str(points),
         attrib={'action': 'Add'}
@@ -85,30 +94,63 @@ def itemfeedback(ident, content='NONE'):
     return root
 
 ### gap specific #########################################################
+def respcondition_gap(points, resp_count, answer, count=0):
+    root = et.Element('respcondition', attrib={'continue': 'Yes'})
+    conditionvar = et.Element('conditionvar')
+    varequal = simple_element(
+        'varequal',
+        text=answer,
+        attrib={'respident': 'gap_{}'.format(resp_count)}
+    )
+
+    conditionvar.append(varequal)
+    setvar = simple_element(
+        'setvar',
+        text=str(points),
+        attrib={'action': 'Add'}
+    )
+
+    displayfeedback = et.Element(
+        'displayfeedback',
+        attrib={'feedbacktype': 'Response',
+                'linkrefid': '{}_Response_{}'.format(resp_count, count)})
+
+    root.append(conditionvar)
+    root.append(setvar)
+    root.append(displayfeedback)
+    return root
+
+
 def material_raw(content):
     material = et.Element('material')
-    material.append(simple_elemet(
+    material.append(simple_element(
         'mattext',
         text=content
     ))
     return material
 
 def response_str(ident, columns):
-    response_str = et.Element('response_str', attrib={'ident': indent, 'rcardinality': 'Single'})
+    response_str = et.Element('response_str', attrib={'ident': ident, 'rcardinality': 'Single'})
     render_fib   = et.Element('render_fib', attrib={'columns': str(columns), 'fibtype': "String", 'prompt': "Box"})
     response_str.append(render_fib)
     return response_str
 
 def response_choice(ident, answers):
-    response_str = et.Element('response_str', attrib={'ident': indent, 'rcardinality': 'Single'})
+    response_str = et.Element('response_str', attrib={'ident': str(ident), 'rcardinality': 'Single'})
     render_choice = et.Element('render_choice', attrib={'shuffle': 'Yes'})
-    for i, answer in enumerate(answers):
+    response_str.append(render_choice)
+    for i, (answer, _) in enumerate(answers):
         response_label = et.Element('response_label', attrib={'ident': str(i)})
         response_label.append(material_raw(answer))
+        render_choice.append(response_label)
     return response_str
 
 def response_num(ident, columns, _min, _max, numtype='Decimal'):
     response_num = et.Element('response_num', attrib={'ident': ident, 'numtype': numtype, 'rcardinality': 'Single'})
     render_fib   = et.Element('render_fib', attrib={'columns': columns, 'fibtype': numtype, 'maxnumber': _min, 'minnumber': _max, 'prompt': "Box"})
     response_num.append(render_fib)
-    return response_num
\ No newline at end of file
+    return response_num
+
+
+# xml_print(response_choice(2, ['a', 'b', 'c']))
+
diff --git a/hallgrim/parser.py b/hallgrim/parser.py
index b9c5c55..0ad49d0 100644
--- a/hallgrim/parser.py
+++ b/hallgrim/parser.py
@@ -4,16 +4,25 @@ try:
     from mistune import Renderer, InlineLexer, Markdown, escape
     from pygments import highlight
     from pygments.lexers import get_lexer_by_name
-    from pygments.formatters import html, HtmlFormatter
+    from pygments.formatters import HtmlFormatter
 except ImportError as err:
     print("Please install mistune to make use of markdown parsing.")
     print("\t pip install mistune")
 
+## TODO get lexer elsewhere
+
+
+no_copy = "-webkit-touch-callout: none; \
+           -webkit-user-select: none; \
+           -khtml-user- select: none; \
+           -moz-user-select: none;\
+           -ms-user-select: none;\
+           user-select: none;"
 
-no_copy = "-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;"
 
 def box(content, color):
-    return '<div style="background-color: #ffedc9; border: 1px solid {}; padding: 10px; font-size: smaller;">{}</div>'.format(color, content)
+    return '<div style="background-color: #ffedc9; border: 1px solid {}; \
+    padding: 10px; font-size: smaller;">{}</div>'.format(color, content)
 
 
 def yellow_box(content):
@@ -23,11 +32,13 @@ def yellow_box(content):
 def blue_box(content):
     return box(content, '#9999ff')
 
+
 def markdown(value):
     renderer = HighlightRenderer()
     markdown = Markdown(renderer=renderer)
     return markdown(value)
 
+
 class LaTeXRenderer(Renderer):
 
     def latex(self, formula):
@@ -56,7 +67,7 @@ class LaTeXInlineLexer(InlineLexer):
         self.default_rules.insert(3, 'latex')
 
     def output_latex(self, m):
-        formula = m.group(1)
+        formula = m.group(1).replace('\n', ' ')
         return self.renderer.latex(formula)
 
 
@@ -68,6 +79,9 @@ def get_custom_markdown():
     inline.enable_latex()
     return Markdown(renderer, inline=inline)
 
+markdown = get_custom_markdown()
+
+
 
 def choice_parser(raw_choices, points):
     """ Parse the multiple choice answers and form an array that has the
@@ -85,8 +99,26 @@ def choice_parser(raw_choices, points):
     final = [(
         markdown(text),
         True if mark != ' ' else False,
-        float(mark) if mark not in ' X' else points) for mark, text in parse]
+        float(mark) if mark not in ' X' else points)
+    for mark, text in parse]
     return final
 
+def gap_parser(task):
+    r = re.compile('\[select\]([\w\W]+?)\[\/select\]', re.MULTILINE)
+    m = re.findall(r, task)
 
-markdown = get_custom_markdown()
+    final = []
+    for s in m:
+        lines = s.strip().split('\n')
+        regex = re.compile('\[(\d|X| )\]\s+([\w\W]+)', re.MULTILINE)
+        parse = [re.search(regex, line).groups() for line in lines]
+        final.append([(text.strip(), float(points) if not points == ' ' else 0) for points, text in parse])
+
+    sep = '  !"§$%&/(XCVBNM;  '
+    no_gaps   = re.sub(r, sep, task)
+    text_only = [markdown(text) for text in no_gaps.split(sep)]
+
+    for i, s in enumerate(final):
+        text_only.insert(2*i+1, (s, -1))
+
+    return text_only
diff --git a/scripts/feldarbeit_param.py b/scripts/feldarbeit_param.py
new file mode 100644
index 0000000..b803eca
--- /dev/null
+++ b/scripts/feldarbeit_param.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+from random import shuffle, sample
+
+meta = {
+    'author': 'Jan Maximilian Michal',
+    'title': 'Feldarbeit (I1-ID: p0kaixs0lyg0)',
+    'type': 'single choice',
+    'points': 1,  # per correct choice
+}
+
+
+def list_to_str(lst):
+    return str(lst).strip('[]')
+
+
+N = 8
+a = sample(range(20), N)
+o = list(a)
+for i in range(N):
+    a[i] = a[(i + 1) % N]
+
+c = a[:-1] + [o[0]]
+
+task = """
+Welche Ausgabe erzeugt folgender Code:
+
+```java
+public class Feldarbeit {
+    public static void main(String[] args) {
+        int[] a = { %s };
+        int N = a.length;
+        for (int i = 0; i < N; i++) {
+            a[i] = a[(i+1) %% N];
+        }
+        for (int i = 0; i < N; i++)
+            System.out.print(a[i] + " ");
+        System.out.println();
+    }
+}
+```
+""" % list_to_str(o)
+
+choices = """
+[4] `%s`
+[2] `%s`
+[ ] `1 1 1 1 1 1 1 1`
+[ ] `%s`
+""" % (list_to_str(a), list_to_str(c), list_to_str(shuffle(o)))
+
+feedback = """
+
+Betrachten Sie die folgenden Zeilen:
+```java
+for (int i = 0; i < N; i++) {
+    a[i] = a[(i+1) %% N];
+}
+```
+
+Der Inhalt jeder Zelle des Arrays jeweils durch den linken Nachbarn ersetzt,
+wobei an den Rändern zyklisch verfahren wird. Dadurch, dass *in-place* verfahren
+wird, also keine Kopie erstellt wird, wird der neue Inhalt der 0. Zelle in die
+letzte geschrieben, daher enthält das neue Array keine %d mehr.""" % o[0]
diff --git a/scripts/nur_sterne_sehen.py b/scripts/nur_sterne_sehen.py
new file mode 100644
index 0000000..8f7473e
--- /dev/null
+++ b/scripts/nur_sterne_sehen.py
@@ -0,0 +1,42 @@
+meta = {
+    'author': 'Jan Maximilian Michal',
+    'title': 'Nur Sterne sehen (I1-ID: cgu8rno0kyg0)',
+    'type': 'single choice',
+    'points': 1, # per correct choice
+}
+
+task = """
+
+Das folgende Programm (nach [Sedgewick/Wayne]) erzeugt als Ausgaben mehrere
+Zeilen mit Ziffern (hier genau vier):
+
+```java
+public class Ruler {
+    public static void main(String[] args) {
+        String ruler1 = "1";                   System.out.println(ruler1);
+        String ruler2 = ruler1 + "2" + ruler1; System.out.println(ruler2);
+        String ruler3 = ruler2 + "3" + ruler2;or System.out.println(ruler3);
+        String ruler4 = ruler3 + "4" + ruler3; System.out.println(ruler4);
+        // usw.
+    }
+}```
+
+Wäre `main` auf genügend Codezeilen nach dem gleichen Schema ergänzt, wieviele
+Zahlen würde bei Ausführung die n-te Ausgabezeile enthalten? """
+
+choices = """
+[ ] [[n]]
+[4] [[2^n−1]]
+[ ] [[2^n]]
+[ ] [[2n+1]]"""
+
+feedback = r""" Die erste Zeile ergibt genau die Ausgabe `1`. In jeder folgenden
+Zeile wird die Anzahl der Zahlen verdoppelt und um eine weitere Zahl ergänzt.
+Die Anzahl der Zahlen [[a_n]] im Schritt [[n]] ist daher: [[a_n = 2a_{n-1} +
+1]].
+Als natürlche Folge ergibt sich:
+
+* [[S_n = a_0 + 2a_0 + 4a_0 + \dots + 2^{n-1}a_0 = a_0 (2^n - 1)]]
+
+Weil [[a_0 = 1]] ist [[2^n−1]] die korrekte Antwort.
+"""
\ No newline at end of file
diff --git a/scripts/originale_kopien.py b/scripts/originale_kopien.py
new file mode 100644
index 0000000..cf03214
--- /dev/null
+++ b/scripts/originale_kopien.py
@@ -0,0 +1,33 @@
+meta = {
+    'author': 'Jan Maximilian Michal',
+    'title': 'Originale Kopien? (I1-ID: 0c05by80syg0)',
+    'type': 'multiple choice',
+    'points': 0.5, # per correct choice
+}
+
+
+task = """ `a`, `b` sind Variablen vom Typ `char[]` und `c`, `d` sind Variablen
+vom Typ `String`. `a`, `b`, `c` und `d` sind bereits korrekt mit Zeichenfolgen
+der Länge 2 initialisiert (d.h. die jeweiligen Felder/Strings belegen
+Speicherplatz und man kann auf ihre Inhalte zugreifen). Welcher Code ist
+geeignet, um festzustellen, ob die in a und b bzw. in c und d gespeicherten
+Zeichenfolgen gleich sind? """
+
+# Antworten bitte aus drop-down-Menü wählen lassen:
+# Auswahl jeweils geeignet/nicht geeignet
+
+choices = """
+[ ] `if (a == b) ...`
+[ ] `if (a.equals(b)) ...`
+[ ] `if (a[0] == b[0] || a[1] == b[1]) ...`
+[X] `if (a[0] == b[0] && a[1] == b[1]) ...`
+[ ] `if (c == d) ...`
+[X] `if (c.equals(d)) ...`
+[X] `if (c.compareTo(d) == 0) ...`
+[ ] `if (c[0] == d[0] && c[1] == d[1]) ...`
+"""
+
+feedback = """
+
+"""
+
diff --git a/scripts/zeilen_sind_anders.py b/scripts/zeilen_sind_anders.py
new file mode 100644
index 0000000..4aefe5d
--- /dev/null
+++ b/scripts/zeilen_sind_anders.py
@@ -0,0 +1,105 @@
+meta = {
+    'author': 'Jan Maximilian Michal',
+    'title': 'Zeilen sind anders, Spalten auch (I1-ID: lhu27691fzg0)',
+    'type': 'gap',
+}
+
+
+gap_1 = """[select]
+[1] int n_ze = m.length;
+[ ] int n_ze = m[0].length;
+[ ] int n_ze = m.length();
+[ ] int n_ze = m[0].length();
+[/select]"""
+
+gap_2 = """[select]
+[ ] int n_sp = m.length();
+[ ] int n_sp = m[0].length();
+[ ] int n_sp = m.length;
+[1] int n_sp = m[0].length;
+[/select]"""
+
+gap_3 = """[select]
+[ ] for (int i = 1; i <= n_ze; i++)
+[1] for (int i = 0; i < n_ze; i++)
+[ ] for (int i = 1; i <= n_sp; i++)
+[ ] for (int i = 0; i < n_sp; i++)
+[/select]"""
+
+gap_4 = """[select]
+[ ] ms[i] = ms[i] + m[j][i]*s[i];
+[ ] ms[i] = ms[i] + m[i][j]*s[i];
+[ ] ms[i] = ms[i] + m[j][i]*s[j];
+[1] ms[i] = ms[i] + m[i][j]*s[j];
+[/select]"""
+
+
+task = """ Folgendes Codefragment soll die Multiplikation eines Zeilenvektors mit einer Matrix sowie einer Matrix mit einem Spaltenvektor realisieren. Wählen Sie die fehlenden Zeilen unten entsprechend aus:
+
+```java
+01: int[][] m = {{ 0, 1, 2, 3},   // Matrix
+02:              { 4, 5, 6, 7},
+03:              {8, 9, 10, 11}};
+04: int[] z = { 12, 13, 14};      // Zeile
+05: int[] s = {15, 16, 17, 18};   // Spalte
+06:
+07: /* CODEZEILE AUSWÄHLEN */     // Zeilenanzahl der Matrix
+08: /* CODEZEILE AUSWÄHLEN */     // Spaltenanzahl der Matrix
+09:
+10: /* 1: Zeile mal Matrix */011: int[] zm = new int[n_sp];
+12: for (int j = 0; j < n_sp; j++) {
+13:     zm[j] = 0;
+14:     /* CODEZEILE AUSWÄHLEN */
+15:         zm[j] = zm[j] + z[i]*m[i][j];
+16: }
+17: /* 2: Matrix mal Spalte */
+18: int[] ms = new int[n_ze];
+19: for (int i = 0; i < n_ze; i++) {
+20:     ms[i] = 0;
+21:     for (int j = 0; j < n_sp; j++)
+22:         /* CODEZEILE AUSWÄHLEN */
+23:
+```
+
+**Zeile 7:**
+%s
+
+**Zeile 8:**
+%s
+
+**Zeile 14:**
+%s
+
+**Zeile 22:**
+%s
+""" % (gap_1, gap_2, gap_3, gap_4)
+
+feedback = """
+
+Der vollständige Code:
+
+```java
+int[][] m = {{ 0, 1, 2, 3},   // Matrix
+             { 4, 5, 6, 7},
+             {8, 9, 10, 11}};
+int[] z = { 12, 13, 14};      // Zeile
+int[] s = {15, 16, 17, 18};   // Spalte
+
+int n_ze = m.length;        // Zeilenanzahl der Matrix
+int n_sp = m[0].length;     // Spaltenanzahl der Matrix
+
+/* 1: Zeile mal Matrix */011: int[] zm = new int[n_sp];
+for (int j = 0; j < n_sp; j++) {
+    zm[j] = 0;
+    for (int i = 0; i < n_ze; i++)
+        zm[j] = zm[j] + z[i]*m[i][j];
+}
+/* 2: Matrix mal Spalte */
+int[] ms = new int[n_ze];
+for (int i = 0; i < n_ze; i++) {
+    ms[i] = 0;
+    for (int j = 0; j < n_sp; j++)
+        ms[i] = ms[i] + m[i][j]*s[j];
+
+```
+"""
\ No newline at end of file
-- 
GitLab