Commit 99ccba47206315e857b4af303ff38eaf3a166e0b
Exists in
master
and in
1 other branch
Merge branch 'master' into dev
Showing
31 changed files
with
414 additions
and
341 deletions
Show diff stats
BUGS.md
| 1 | 1 | ||
| 2 | # BUGS | 2 | # BUGS |
| 3 | 3 | ||
| 4 | +- se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro. | ||
| 5 | +- registar last_seen e remover os antigos de cada vez que houver um login. | ||
| 4 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) | 6 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) |
| 5 | - permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic. | 7 | - permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic. |
| 6 | - double click submits twice. | 8 | - double click submits twice. |
aprendizations/learnapp.py
| @@ -462,7 +462,7 @@ class LearnApp(object): | @@ -462,7 +462,7 @@ class LearnApp(object): | ||
| 462 | 462 | ||
| 463 | # ------------------------------------------------------------------------ | 463 | # ------------------------------------------------------------------------ |
| 464 | def get_current_public_dir(self, uid: str) -> str: | 464 | def get_current_public_dir(self, uid: str) -> str: |
| 465 | - topic: str = self.online[uid]['state'].get_current_topic() | 465 | + topic: str = self.online[uid]['state'].get_current_topic() # FIXME returns None if its the last question in the topic |
| 466 | prefix: str = self.deps.graph['prefix'] | 466 | prefix: str = self.deps.graph['prefix'] |
| 467 | return path.join(prefix, topic, 'public') | 467 | return path.join(prefix, topic, 'public') |
| 468 | 468 |
aprendizations/main.py
| @@ -140,21 +140,22 @@ def main(): | @@ -140,21 +140,22 @@ def main(): | ||
| 140 | path.join(certs_dir, 'privkey.pem')) | 140 | path.join(certs_dir, 'privkey.pem')) |
| 141 | except FileNotFoundError: | 141 | except FileNotFoundError: |
| 142 | logging.critical(f'SSL certificates missing in {certs_dir}') | 142 | logging.critical(f'SSL certificates missing in {certs_dir}') |
| 143 | - print('--------------------------------------------------------------') | ||
| 144 | - print('Certificates should be issued by a certificate authority (CA),') | ||
| 145 | - print('such as https://letsencrypt.org. ') | ||
| 146 | - print('For testing purposes a selfsigned certificate can be generated') | ||
| 147 | - print('locally by running: ') | ||
| 148 | - print(' ') | ||
| 149 | - print(' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ') | ||
| 150 | - print(' -out cert.pem -days 365 -nodes ') | ||
| 151 | - print(' ') | ||
| 152 | - print('Copy the cert.pem and privkey.pem files to: ') | ||
| 153 | - print(' ') | ||
| 154 | - print(f' {certs_dir:<62}') | ||
| 155 | - print(' ') | ||
| 156 | - print('(See README.md for more information) ') | ||
| 157 | - print('--------------------------------------------------------------') | 143 | + print('--------------------------------------------------------------', |
| 144 | + 'Certificates should be issued by a certificate authority (CA),', | ||
| 145 | + 'such as https://letsencrypt.org. ', | ||
| 146 | + 'For testing purposes a selfsigned certificate can be generated', | ||
| 147 | + 'locally by running: ', | ||
| 148 | + ' ', | ||
| 149 | + ' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ', | ||
| 150 | + ' -out cert.pem -days 365 -nodes ', | ||
| 151 | + ' ', | ||
| 152 | + 'Copy the cert.pem and privkey.pem files to: ', | ||
| 153 | + ' ', | ||
| 154 | + f' {certs_dir:<62}', | ||
| 155 | + ' ', | ||
| 156 | + 'See README.md for more information ', | ||
| 157 | + '--------------------------------------------------------------', | ||
| 158 | + sep='\n') | ||
| 158 | sys.exit(1) | 159 | sys.exit(1) |
| 159 | else: | 160 | else: |
| 160 | logging.info('SSL certificates loaded') | 161 | logging.info('SSL certificates loaded') |
| @@ -167,20 +168,21 @@ def main(): | @@ -167,20 +168,21 @@ def main(): | ||
| 167 | check=arg.check) | 168 | check=arg.check) |
| 168 | except DatabaseUnusableError: | 169 | except DatabaseUnusableError: |
| 169 | logging.critical('Failed to start application.') | 170 | logging.critical('Failed to start application.') |
| 170 | - print('--------------------------------------------------------------') | ||
| 171 | - print('Could not find a usable database. Use one of the follwing ') | ||
| 172 | - print('commands to initialize: ') | ||
| 173 | - print(' ') | ||
| 174 | - print(' initdb-aprendizations --admin # add admin ') | ||
| 175 | - print(' initdb-aprendizations -a 86 "Max Smart" # add student ') | ||
| 176 | - print(' initdb-aprendizations students.csv # add many students') | ||
| 177 | - print('--------------------------------------------------------------') | 171 | + print('--------------------------------------------------------------', |
| 172 | + 'Could not find a usable database. Use one of the follwing ', | ||
| 173 | + 'commands to initialize: ', | ||
| 174 | + ' ', | ||
| 175 | + ' initdb-aprendizations --admin # add admin ', | ||
| 176 | + ' initdb-aprendizations -a 86 "Max Smart" # add student ', | ||
| 177 | + ' initdb-aprendizations students.csv # add many students', | ||
| 178 | + '--------------------------------------------------------------', | ||
| 179 | + sep='\n') | ||
| 178 | sys.exit(1) | 180 | sys.exit(1) |
| 179 | except Exception: | 181 | except Exception: |
| 180 | - logging.critical('Failed to start application.') | 182 | + logging.critical('Failed to start backend.') |
| 181 | sys.exit(1) | 183 | sys.exit(1) |
| 182 | else: | 184 | else: |
| 183 | - logging.info('Backend application started') | 185 | + logging.info('Backend started') |
| 184 | 186 | ||
| 185 | # --- run webserver forever | 187 | # --- run webserver forever |
| 186 | run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug) | 188 | run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug) |
aprendizations/questions.py
| 1 | 1 | ||
| 2 | # python standard library | 2 | # python standard library |
| 3 | +import asyncio | ||
| 3 | import random | 4 | import random |
| 4 | import re | 5 | import re |
| 5 | from os import path | 6 | from os import path |
| @@ -21,10 +22,10 @@ class QuestionException(Exception): | @@ -21,10 +22,10 @@ class QuestionException(Exception): | ||
| 21 | pass | 22 | pass |
| 22 | 23 | ||
| 23 | 24 | ||
| 24 | -# =========================================================================== | 25 | +# ============================================================================ |
| 25 | # Questions derived from Question are already instantiated and ready to be | 26 | # Questions derived from Question are already instantiated and ready to be |
| 26 | # presented to students. | 27 | # presented to students. |
| 27 | -# =========================================================================== | 28 | +# ============================================================================ |
| 28 | class Question(dict): | 29 | class Question(dict): |
| 29 | ''' | 30 | ''' |
| 30 | Classes derived from this base class are meant to instantiate questions | 31 | Classes derived from this base class are meant to instantiate questions |
| @@ -41,7 +42,7 @@ class Question(dict): | @@ -41,7 +42,7 @@ class Question(dict): | ||
| 41 | 'comments': '', | 42 | 'comments': '', |
| 42 | 'solution': '', | 43 | 'solution': '', |
| 43 | 'files': {}, | 44 | 'files': {}, |
| 44 | - 'max_tries': 3, | 45 | + # 'max_tries': 3, |
| 45 | })) | 46 | })) |
| 46 | 47 | ||
| 47 | def correct(self) -> None: | 48 | def correct(self) -> None: |
| @@ -57,7 +58,7 @@ class Question(dict): | @@ -57,7 +58,7 @@ class Question(dict): | ||
| 57 | self.setdefault(k, v) | 58 | self.setdefault(k, v) |
| 58 | 59 | ||
| 59 | 60 | ||
| 60 | -# ========================================================================== | 61 | +# ============================================================================ |
| 61 | class QuestionRadio(Question): | 62 | class QuestionRadio(Question): |
| 62 | '''An instance of QuestionRadio will always have the keys: | 63 | '''An instance of QuestionRadio will always have the keys: |
| 63 | type (str) | 64 | type (str) |
| @@ -92,7 +93,7 @@ class QuestionRadio(Question): | @@ -92,7 +93,7 @@ class QuestionRadio(Question): | ||
| 92 | for x in range(n)] | 93 | for x in range(n)] |
| 93 | 94 | ||
| 94 | if len(self['correct']) != n: | 95 | if len(self['correct']) != n: |
| 95 | - msg = (f'Options and correct mismatch in ' | 96 | + msg = ('Number of options and correct differ in ' |
| 96 | f'"{self["ref"]}", file "{self["filename"]}".') | 97 | f'"{self["ref"]}", file "{self["filename"]}".') |
| 97 | logger.error(msg) | 98 | logger.error(msg) |
| 98 | raise QuestionException(msg) | 99 | raise QuestionException(msg) |
| @@ -138,7 +139,7 @@ class QuestionRadio(Question): | @@ -138,7 +139,7 @@ class QuestionRadio(Question): | ||
| 138 | self['grade'] = x | 139 | self['grade'] = x |
| 139 | 140 | ||
| 140 | 141 | ||
| 141 | -# =========================================================================== | 142 | +# ============================================================================ |
| 142 | class QuestionCheckbox(Question): | 143 | class QuestionCheckbox(Question): |
| 143 | '''An instance of QuestionCheckbox will always have the keys: | 144 | '''An instance of QuestionCheckbox will always have the keys: |
| 144 | type (str) | 145 | type (str) |
| @@ -218,7 +219,7 @@ class QuestionCheckbox(Question): | @@ -218,7 +219,7 @@ class QuestionCheckbox(Question): | ||
| 218 | self['grade'] = x / sum_abs | 219 | self['grade'] = x / sum_abs |
| 219 | 220 | ||
| 220 | 221 | ||
| 221 | -# =========================================================================== | 222 | +# ============================================================================ |
| 222 | class QuestionText(Question): | 223 | class QuestionText(Question): |
| 223 | '''An instance of QuestionText will always have the keys: | 224 | '''An instance of QuestionText will always have the keys: |
| 224 | type (str) | 225 | type (str) |
| @@ -251,7 +252,7 @@ class QuestionText(Question): | @@ -251,7 +252,7 @@ class QuestionText(Question): | ||
| 251 | self['grade'] = 1.0 if self['answer'] in self['correct'] else 0.0 | 252 | self['grade'] = 1.0 if self['answer'] in self['correct'] else 0.0 |
| 252 | 253 | ||
| 253 | 254 | ||
| 254 | -# =========================================================================== | 255 | +# ============================================================================ |
| 255 | class QuestionTextRegex(Question): | 256 | class QuestionTextRegex(Question): |
| 256 | '''An instance of QuestionTextRegex will always have the keys: | 257 | '''An instance of QuestionTextRegex will always have the keys: |
| 257 | type (str) | 258 | type (str) |
| @@ -281,7 +282,7 @@ class QuestionTextRegex(Question): | @@ -281,7 +282,7 @@ class QuestionTextRegex(Question): | ||
| 281 | self['grade'] = 1.0 if ok else 0.0 | 282 | self['grade'] = 1.0 if ok else 0.0 |
| 282 | 283 | ||
| 283 | 284 | ||
| 284 | -# =========================================================================== | 285 | +# ============================================================================ |
| 285 | class QuestionNumericInterval(Question): | 286 | class QuestionNumericInterval(Question): |
| 286 | '''An instance of QuestionTextNumeric will always have the keys: | 287 | '''An instance of QuestionTextNumeric will always have the keys: |
| 287 | type (str) | 288 | type (str) |
| @@ -316,7 +317,7 @@ class QuestionNumericInterval(Question): | @@ -316,7 +317,7 @@ class QuestionNumericInterval(Question): | ||
| 316 | self['grade'] = 1.0 if lower <= answer <= upper else 0.0 | 317 | self['grade'] = 1.0 if lower <= answer <= upper else 0.0 |
| 317 | 318 | ||
| 318 | 319 | ||
| 319 | -# =========================================================================== | 320 | +# ============================================================================ |
| 320 | class QuestionTextArea(Question): | 321 | class QuestionTextArea(Question): |
| 321 | '''An instance of QuestionTextArea will always have the keys: | 322 | '''An instance of QuestionTextArea will always have the keys: |
| 322 | type (str) | 323 | type (str) |
| @@ -397,7 +398,7 @@ class QuestionTextArea(Question): | @@ -397,7 +398,7 @@ class QuestionTextArea(Question): | ||
| 397 | logger.error(f'Invalid grade in "{self["correct"]}".') | 398 | logger.error(f'Invalid grade in "{self["correct"]}".') |
| 398 | 399 | ||
| 399 | 400 | ||
| 400 | -# =========================================================================== | 401 | +# ============================================================================ |
| 401 | class QuestionInformation(Question): | 402 | class QuestionInformation(Question): |
| 402 | # ------------------------------------------------------------------------ | 403 | # ------------------------------------------------------------------------ |
| 403 | def __init__(self, q: QDict) -> None: | 404 | def __init__(self, q: QDict) -> None: |
| @@ -412,30 +413,38 @@ class QuestionInformation(Question): | @@ -412,30 +413,38 @@ class QuestionInformation(Question): | ||
| 412 | self['grade'] = 1.0 # always "correct" but points should be zero! | 413 | self['grade'] = 1.0 # always "correct" but points should be zero! |
| 413 | 414 | ||
| 414 | 415 | ||
| 415 | -# =========================================================================== | 416 | +# ============================================================================ |
| 417 | +# | ||
| 416 | # QFactory is a class that can generate question instances, e.g. by shuffling | 418 | # QFactory is a class that can generate question instances, e.g. by shuffling |
| 417 | # options, running a script to generate the question, etc. | 419 | # options, running a script to generate the question, etc. |
| 418 | # | 420 | # |
| 419 | -# To generate an instance of a question we use the method generate() where | ||
| 420 | -# the argument is the reference of the question we wish to produce. | ||
| 421 | -# The generate() method returns a question instance of the correct class. | 421 | +# To generate an instance of a question we use the method generate(). |
| 422 | +# It returns a question instance of the correct class. | ||
| 423 | +# There is also an asynchronous version called gen_async(). This version is | ||
| 424 | +# synchronous for all question types (radio, checkbox, etc) except for generator | ||
| 425 | +# types which run asynchronously. | ||
| 422 | # | 426 | # |
| 423 | # Example: | 427 | # Example: |
| 424 | # | 428 | # |
| 425 | -# # generate a question instance from a dictionary | ||
| 426 | -# qdict = { | 429 | +# # make a factory for a question |
| 430 | +# qfactory = QFactory({ | ||
| 427 | # 'type': 'radio', | 431 | # 'type': 'radio', |
| 428 | # 'text': 'Choose one', | 432 | # 'text': 'Choose one', |
| 429 | # 'options': ['a', 'b'] | 433 | # 'options': ['a', 'b'] |
| 430 | -# } | ||
| 431 | -# qfactory = QFactory(qdict) | 434 | +# }) |
| 435 | +# | ||
| 436 | +# # generate synchronously | ||
| 432 | # question = qfactory.generate() | 437 | # question = qfactory.generate() |
| 433 | # | 438 | # |
| 439 | +# # generate asynchronously | ||
| 440 | +# question = await qfactory.gen_async() | ||
| 441 | +# | ||
| 434 | # # answer one question and correct it | 442 | # # answer one question and correct it |
| 435 | # question['answer'] = 42 # set answer | 443 | # question['answer'] = 42 # set answer |
| 436 | # question.correct() # correct answer | 444 | # question.correct() # correct answer |
| 437 | # grade = question['grade'] # get grade | 445 | # grade = question['grade'] # get grade |
| 438 | -# =========================================================================== | 446 | +# |
| 447 | +# ============================================================================ | ||
| 439 | class QFactory(object): | 448 | class QFactory(object): |
| 440 | # Depending on the type of question, a different question class will be | 449 | # Depending on the type of question, a different question class will be |
| 441 | # instantiated. All these classes derive from the base class `Question`. | 450 | # instantiated. All these classes derive from the base class `Question`. |
| @@ -456,44 +465,10 @@ class QFactory(object): | @@ -456,44 +465,10 @@ class QFactory(object): | ||
| 456 | def __init__(self, qdict: QDict = QDict({})) -> None: | 465 | def __init__(self, qdict: QDict = QDict({})) -> None: |
| 457 | self.question = qdict | 466 | self.question = qdict |
| 458 | 467 | ||
| 459 | - # ----------------------------------------------------------------------- | ||
| 460 | - # Given a ref returns an instance of a descendent of Question(), | ||
| 461 | - # i.e. a question object (radio, checkbox, ...). | ||
| 462 | - # ----------------------------------------------------------------------- | ||
| 463 | - def generate(self) -> Question: | ||
| 464 | - # FIXME this is almost the same as the one below | ||
| 465 | - # return asyncio.run(self.gen_async()) | ||
| 466 | - | ||
| 467 | - logger.debug(f'generating {self.question["ref"]}...') | ||
| 468 | - # Shallow copy so that script generated questions will not replace | ||
| 469 | - # the original generators | ||
| 470 | - q = self.question.copy() | ||
| 471 | - q['qid'] = str(uuid.uuid4()) # unique for each generated question | ||
| 472 | - | ||
| 473 | - # If question is of generator type, an external program will be run | ||
| 474 | - # which will print a valid question in yaml format to stdout. This | ||
| 475 | - # output is then yaml parsed into a dictionary `q`. | ||
| 476 | - if q['type'] == 'generator': | ||
| 477 | - logger.debug(f' \\_ Running "{q["script"]}".') | ||
| 478 | - q.setdefault('args', []) | ||
| 479 | - q.setdefault('stdin', '') # FIXME is it really necessary? | ||
| 480 | - script = path.join(q['path'], q['script']) | ||
| 481 | - out = run_script(script=script, args=q['args'], stdin=q['stdin']) | ||
| 482 | - q.update(out) | ||
| 483 | - | ||
| 484 | - # Finally we create an instance of Question() | ||
| 485 | - try: | ||
| 486 | - qinstance = self._types[q['type']](QDict(q)) # of matching class | ||
| 487 | - except QuestionException as e: | ||
| 488 | - logger.error(e) | ||
| 489 | - raise e | ||
| 490 | - except KeyError: | ||
| 491 | - logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') | ||
| 492 | - raise | ||
| 493 | - else: | ||
| 494 | - return qinstance | ||
| 495 | - | ||
| 496 | - # ----------------------------------------------------------------------- | 468 | + # ------------------------------------------------------------------------ |
| 469 | + # generates a question instance of QuestionRadio, QuestionCheckbox, ..., | ||
| 470 | + # which is a descendent of base class Question. | ||
| 471 | + # ------------------------------------------------------------------------ | ||
| 497 | async def gen_async(self) -> Question: | 472 | async def gen_async(self) -> Question: |
| 498 | logger.debug(f'generating {self.question["ref"]}...') | 473 | logger.debug(f'generating {self.question["ref"]}...') |
| 499 | # Shallow copy so that script generated questions will not replace | 474 | # Shallow copy so that script generated questions will not replace |
| @@ -524,3 +499,7 @@ class QFactory(object): | @@ -524,3 +499,7 @@ class QFactory(object): | ||
| 524 | raise | 499 | raise |
| 525 | else: | 500 | else: |
| 526 | return qinstance | 501 | return qinstance |
| 502 | + | ||
| 503 | + # ------------------------------------------------------------------------ | ||
| 504 | + def generate(self) -> Question: | ||
| 505 | + return asyncio.get_event_loop().run_until_complete(self.gen_async()) |
aprendizations/serve.py
| @@ -433,6 +433,7 @@ def run_webserver(app, | @@ -433,6 +433,7 @@ def run_webserver(app, | ||
| 433 | webapp = WebApplication(app, debug=debug) | 433 | webapp = WebApplication(app, debug=debug) |
| 434 | except Exception: | 434 | except Exception: |
| 435 | logger.critical('Failed to start web application.') | 435 | logger.critical('Failed to start web application.') |
| 436 | + raise | ||
| 436 | sys.exit(1) | 437 | sys.exit(1) |
| 437 | else: | 438 | else: |
| 438 | logger.info('Web application started (tornado.web.Application)') | 439 | logger.info('Web application started (tornado.web.Application)') |
aprendizations/tools.py
| @@ -110,14 +110,14 @@ class HighlightRenderer(mistune.Renderer): | @@ -110,14 +110,14 @@ class HighlightRenderer(mistune.Renderer): | ||
| 110 | return highlight(code, lexer, formatter) | 110 | return highlight(code, lexer, formatter) |
| 111 | 111 | ||
| 112 | def table(self, header, body): | 112 | def table(self, header, body): |
| 113 | - return '<table class="table table-sm"><thead class="thead-light">' \ | ||
| 114 | - + header + '</thead><tbody>' + body + '</tbody></table>' | 113 | + return ('<table class="table table-sm"><thead class="thead-light">' |
| 114 | + f'{header}</thead><tbody>{body}</tbody></table>') | ||
| 115 | 115 | ||
| 116 | def image(self, src, title, alt): | 116 | def image(self, src, title, alt): |
| 117 | alt = mistune.escape(alt, quote=True) | 117 | alt = mistune.escape(alt, quote=True) |
| 118 | title = mistune.escape(title or '', quote=True) | 118 | title = mistune.escape(title or '', quote=True) |
| 119 | - return f'<img src="/file/{src}" ' \ | ||
| 120 | - f'alt="{alt}" title="{title}">' | 119 | + return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' |
| 120 | + 'class="img-fluid">') | ||
| 121 | # class="img-fluid mx-auto d-block" | 121 | # class="img-fluid mx-auto d-block" |
| 122 | 122 | ||
| 123 | # Pass math through unaltered - mathjax does the rendering in the browser | 123 | # Pass math through unaltered - mathjax does the rendering in the browser |
| @@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
| 1 | +--- | ||
| 2 | +# ---------------------------------------------------------------------------- | ||
| 3 | +# optional values applied to each topic, if undefined there | ||
| 4 | +# ---------------------------------------------------------------------------- | ||
| 5 | + | ||
| 6 | +# defaults: | ||
| 7 | +# file: questions.yaml | ||
| 8 | +# shuffle_questions: true | ||
| 9 | +# choose: 6 | ||
| 10 | +# max_tries: 2 | ||
| 11 | +# forgetting_factor: 0.97 | ||
| 12 | +# min_level: 0.01 | ||
| 13 | +# append_wrong: true | ||
| 14 | + | ||
| 15 | +# ---------------------------------------------------------------------------- | ||
| 16 | +# topics and their dependencies | ||
| 17 | +# ---------------------------------------------------------------------------- | ||
| 18 | + | ||
| 19 | +topics: | ||
| 20 | + astronomy/solar-system: | ||
| 21 | + name: Sistema solar | ||
| 22 | + | ||
| 23 | + # astronomy/milky-way: | ||
| 24 | + # name: Via Láctea | ||
| 25 | + # deps: | ||
| 26 | + # - solar-system |
| @@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | + | ||
| 3 | +import re | ||
| 4 | +import sys | ||
| 5 | +import time | ||
| 6 | + | ||
| 7 | +s = sys.stdin.read() | ||
| 8 | + | ||
| 9 | +ans = set(re.findall(r'[\w]+', s.lower())) # convert answer to lowercase | ||
| 10 | +ans.difference_update({'e', 'a', 'o', 'planeta', 'planetas'}) # ignore words | ||
| 11 | + | ||
| 12 | +# correct set of planets | ||
| 13 | +planets = {'mercúrio', 'vénus', 'terra'} | ||
| 14 | + | ||
| 15 | +correct = set.intersection(ans, planets) # the ones I got right | ||
| 16 | +wrong = set.difference(ans, planets) # the ones I got wrong | ||
| 17 | + | ||
| 18 | +grade = (len(correct) - len(wrong)) / len(planets) | ||
| 19 | + | ||
| 20 | +comments = 'Certo' if grade == 1.0 else 'as iniciais dos planetas são M, V e T' | ||
| 21 | + | ||
| 22 | +out = f'''--- | ||
| 23 | +grade: {grade} | ||
| 24 | +comments: {comments}''' | ||
| 25 | + | ||
| 26 | +time.sleep(2) # simulate computation time (may generate timeout) | ||
| 27 | + | ||
| 28 | +print(out) |
1.02 MB
2.96 MB
419 KB
117 KB
| @@ -0,0 +1,66 @@ | @@ -0,0 +1,66 @@ | ||
| 1 | +--- | ||
| 2 | +# ---------------------------------------------------------------------------- | ||
| 3 | +- type: text | ||
| 4 | + ref: planet-earth | ||
| 5 | + title: Sistema solar | ||
| 6 | + text: O nosso planeta chama-se planeta... | ||
| 7 | + correct: ['Terra', 'terra'] | ||
| 8 | + # opcional | ||
| 9 | + answer: dos macacos? | ||
| 10 | + solution: | | ||
| 11 | + O nosso planeta é o planeta **Terra**. | ||
| 12 | + | ||
| 13 | +  | ||
| 14 | + | ||
| 15 | +# ---------------------------------------------------------------------------- | ||
| 16 | +- type: radio | ||
| 17 | + ref: largest-planet | ||
| 18 | + title: Sistema solar | ||
| 19 | + text: | | ||
| 20 | +  | ||
| 21 | + | ||
| 22 | + Qual é o maior planeta do Sistema Solar? | ||
| 23 | + options: | ||
| 24 | + - Mercúrio | ||
| 25 | + - Marte | ||
| 26 | + - Júpiter | ||
| 27 | + - Saturno | ||
| 28 | + - Têm todos o mesmo tamanho | ||
| 29 | + # opcional | ||
| 30 | + correct: 2 | ||
| 31 | + shuffle: false | ||
| 32 | + solution: | | ||
| 33 | + O maior planeta é Júpiter. Tem uma massa 1000 vezes inferior ao Sol, mas | ||
| 34 | + ainda assim 2.5 vezes maior que a massa de todos os outros planetas juntos. | ||
| 35 | + É um gigante gasoso maioritariamente composto por hidrogénio. | ||
| 36 | + | ||
| 37 | +  | ||
| 38 | + | ||
| 39 | +# ---------------------------------------------------------------------------- | ||
| 40 | +- type: text-regex | ||
| 41 | + ref: saturn | ||
| 42 | + title: Sistema solar | ||
| 43 | + text: O planeta do sistema solar conhecido pelos seus aneis é o planeta... | ||
| 44 | + correct: '[Ss]aturno' | ||
| 45 | + solution: | | ||
| 46 | + O planeta Saturno é famoso pelos seus anéis. | ||
| 47 | + É o segundo maior planeta do Sistema Solar. | ||
| 48 | + Tal como Júpiter, é um gigante gasoso. | ||
| 49 | + Os seus anéis são formados por partículas de gelo. | ||
| 50 | + | ||
| 51 | +  | ||
| 52 | + | ||
| 53 | +# ---------------------------------------------------------------------------- | ||
| 54 | +- type: textarea | ||
| 55 | + ref: first_3_planets | ||
| 56 | + title: Sistema solar | ||
| 57 | + text: | | ||
| 58 | + Qual o nome dos três planetas mais próximos do Sol? | ||
| 59 | + Exemplo `Ceres, Krypton e Vulcano` | ||
| 60 | + correct: correct-first_3_planets.py | ||
| 61 | + # correct: correct-timeout.py | ||
| 62 | + # opcional | ||
| 63 | + answer: Ceres, Krypton e Vulcano | ||
| 64 | + timeout: 3 | ||
| 65 | + solution: | | ||
| 66 | + Os 3 planetas mais perto do Sol são Mercúrio, Vénus e Terra. |
| @@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
| 1 | +--- | ||
| 2 | +# ---------------------------------------------------------------------------- | ||
| 3 | +# import topics and their dependencies | ||
| 4 | +# ---------------------------------------------------------------------------- | ||
| 5 | +topics_from: | ||
| 6 | + - math.yaml | ||
| 7 | + - astronomy.yaml | ||
| 8 | + | ||
| 9 | +# ---------------------------------------------------------------------------- | ||
| 10 | +# course names, their goals and other details | ||
| 11 | +# ---------------------------------------------------------------------------- | ||
| 12 | +courses: | ||
| 13 | + math: | ||
| 14 | + title: Matemática | ||
| 15 | + description: | | ||
| 16 | + Adição, multiplicação e números primos. | ||
| 17 | + goals: | ||
| 18 | + - math/addition | ||
| 19 | + - math/multiplication | ||
| 20 | + - math/prime-numbers | ||
| 21 | + | ||
| 22 | + astronomy: | ||
| 23 | + title: Astronomia | ||
| 24 | + description: | | ||
| 25 | + Sistema Solar e Via Láctea. | ||
| 26 | + goals: | ||
| 27 | + - astronomy/solar-system | ||
| 28 | + # - astronomy/milky-way |
demo/demo.yaml
| @@ -1,29 +0,0 @@ | @@ -1,29 +0,0 @@ | ||
| 1 | ---- | ||
| 2 | - | ||
| 3 | -title: Example | ||
| 4 | -database: students.db | ||
| 5 | - | ||
| 6 | -# values applied to each topic, if undefined there | ||
| 7 | -file: questions.yaml | ||
| 8 | -shuffle_questions: true | ||
| 9 | -choose: 6 | ||
| 10 | -max_tries: 2 | ||
| 11 | -forgetting_factor: 0.97 | ||
| 12 | -min_level: 0.01 | ||
| 13 | -append_wrong: true | ||
| 14 | - | ||
| 15 | -# ---------------------------------------------------------------------------- | ||
| 16 | -topics: | ||
| 17 | - # topic without dependencies | ||
| 18 | - math: | ||
| 19 | - name: Matemática | ||
| 20 | - file: questions.yaml | ||
| 21 | - choose: 6 | ||
| 22 | - shuffle: true | ||
| 23 | - | ||
| 24 | - # topic with one dependency | ||
| 25 | - solar_system: | ||
| 26 | - name: Sistema solar | ||
| 27 | - deps: | ||
| 28 | - - math | ||
| 29 | - forgetting_factor: 0.1 |
| @@ -0,0 +1,31 @@ | @@ -0,0 +1,31 @@ | ||
| 1 | +--- | ||
| 2 | +# ---------------------------------------------------------------------------- | ||
| 3 | +# optional values applied to each topic, if undefined there | ||
| 4 | +# ---------------------------------------------------------------------------- | ||
| 5 | + | ||
| 6 | +# defaults: | ||
| 7 | +# file: questions.yaml | ||
| 8 | +# shuffle_questions: true | ||
| 9 | +# choose: 6 | ||
| 10 | +# max_tries: 2 | ||
| 11 | +# forgetting_factor: 0.97 | ||
| 12 | +# min_level: 0.01 | ||
| 13 | +# append_wrong: true | ||
| 14 | + | ||
| 15 | +# ---------------------------------------------------------------------------- | ||
| 16 | +# topics and their dependencies | ||
| 17 | +# ---------------------------------------------------------------------------- | ||
| 18 | + | ||
| 19 | +topics: | ||
| 20 | + math/addition: | ||
| 21 | + name: Adição | ||
| 22 | + | ||
| 23 | + math/multiplication: | ||
| 24 | + name: Multiplicação | ||
| 25 | + deps: | ||
| 26 | + - math/addition | ||
| 27 | + | ||
| 28 | + math/prime-numbers: | ||
| 29 | + name: Números primos | ||
| 30 | + deps: | ||
| 31 | + - math/multiplication |
| @@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | + | ||
| 3 | +import random | ||
| 4 | +import sys | ||
| 5 | + | ||
| 6 | +a = int(sys.argv[1]) | ||
| 7 | +b = int(sys.argv[2]) | ||
| 8 | + | ||
| 9 | +x = random.randint(a, b) | ||
| 10 | +y = random.randint(a, b) | ||
| 11 | +r = x + y | ||
| 12 | + | ||
| 13 | +print(f'''--- | ||
| 14 | +type: text | ||
| 15 | +title: Adição de números com 2 algarismos | ||
| 16 | +text: | | ||
| 17 | + Qual o resultado da soma ${x}+{y}$? | ||
| 18 | +correct: ['{r}'] | ||
| 19 | +solution: | | ||
| 20 | + O resultado é {r}.''') |
| @@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
| 1 | +--- | ||
| 2 | +# --------------------------------------------------------------------------- | ||
| 3 | +- type: generator | ||
| 4 | + ref: addition-two-digits | ||
| 5 | + script: addition-two-digits.py | ||
| 6 | + args: [10, 20] | ||
| 7 | + | ||
| 8 | +- type: checkbox | ||
| 9 | + ref: addition-properties | ||
| 10 | + title: Propriedades da adição | ||
| 11 | + text: Indique quais as propriedades que a adição satisfaz. | ||
| 12 | + options: | ||
| 13 | + # right | ||
| 14 | + - Existência de elemento neutro, $x+0=x$. | ||
| 15 | + - Existência de inverso aditivo (simétrico), $x+(-x)=0$. | ||
| 16 | + - Propriedade associativa, $(x+y)+z = x+(y+z)$. | ||
| 17 | + - Propriedade comutativa, $x+y=y+x$. | ||
| 18 | + # wrong | ||
| 19 | + - Existência de elemento absorvente, $x+1=1$. | ||
| 20 | + correct: [1, 1, 1, 1, -1] | ||
| 21 | + solution: | | ||
| 22 | + A adição não tem elemento absorvente. |
demo/math/gen-multiples-of-3.py
| @@ -1,29 +0,0 @@ | @@ -1,29 +0,0 @@ | ||
| 1 | -#!/usr/bin/env python3 | ||
| 2 | - | ||
| 3 | -import random | ||
| 4 | -import sys | ||
| 5 | - | ||
| 6 | -a, b = map(int, sys.argv[1:]) # get command line arguments | ||
| 7 | - | ||
| 8 | -numbers = list(range(a, b)) | ||
| 9 | -random.shuffle(numbers) | ||
| 10 | -numbers = numbers[:8] | ||
| 11 | -correct = [1 if n % 3 == 0 else -1 for n in numbers] | ||
| 12 | - | ||
| 13 | -multiples = [str(n) for n in numbers if n % 3 == 0] | ||
| 14 | -if multiples: | ||
| 15 | - solution = f'Os números múltiplos de 3 são: {", ".join(multiples)}.' | ||
| 16 | -else: | ||
| 17 | - solution = f'Nenhum número mostrado é múltiplo de 3.' | ||
| 18 | - | ||
| 19 | - | ||
| 20 | -q = f'''--- | ||
| 21 | -type: checkbox | ||
| 22 | -title: Múltiplos de 3 | ||
| 23 | -text: Indique quais dos seguintes números são múltiplos de 3. | ||
| 24 | -options: {numbers} | ||
| 25 | -correct: {correct} | ||
| 26 | -solution: | | ||
| 27 | - {solution}''' | ||
| 28 | - | ||
| 29 | -print(q) |
| @@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | + | ||
| 3 | +import random | ||
| 4 | +import sys | ||
| 5 | + | ||
| 6 | +# can be repeated | ||
| 7 | +# x = random.randint(2, 9) | ||
| 8 | +# y = random.randint(2, 9) | ||
| 9 | + | ||
| 10 | +# with x != y | ||
| 11 | +x, y = random.sample(range(2,10), k=2) | ||
| 12 | +r = x * y | ||
| 13 | + | ||
| 14 | +yy = '+'.join([str(y)]*x) | ||
| 15 | +xx = '+'.join([str(x)]*y) | ||
| 16 | + | ||
| 17 | +print(f'''--- | ||
| 18 | +type: text | ||
| 19 | +title: Multiplicação (tabuada) | ||
| 20 | +text: | | ||
| 21 | + Qual o resultado da multiplicação ${x}\\times {y}$? | ||
| 22 | +correct: ['{r}'] | ||
| 23 | +solution: | | ||
| 24 | + A multiplicação é a repetição da soma. Podemos fazer de duas maneiras: | ||
| 25 | + $$ {x}\\times {y} = {yy} = {r} $$ | ||
| 26 | + ou | ||
| 27 | + $$ {y}\\times {x} = {xx} = {r}. $$''') |
| @@ -0,0 +1,24 @@ | @@ -0,0 +1,24 @@ | ||
| 1 | +--- | ||
| 2 | +# --------------------------------------------------------------------------- | ||
| 3 | +- type: generator | ||
| 4 | + ref: multiplication-table | ||
| 5 | + script: multiplication-table.py | ||
| 6 | + | ||
| 7 | +- type: checkbox | ||
| 8 | + ref: multiplication-properties | ||
| 9 | + title: Propriedades da multiplicação | ||
| 10 | + text: Indique quais as propriedades que a multiplicação satisfaz. | ||
| 11 | + options: | ||
| 12 | + # right | ||
| 13 | + - Existência de elemento neutro, $1x=x$. | ||
| 14 | + - Propriedade associativa, $(xy)z = x(yz)$. | ||
| 15 | + - Propriedade comutativa, $xy=yx$. | ||
| 16 | + - Existência de elemento absorvente, $0x=0$. | ||
| 17 | + # wrong | ||
| 18 | + - Existência de inverso, todos os números $x$ tem um inverso $1/x$ tal que | ||
| 19 | + $x(1/x)=1$. | ||
| 20 | + correct: [1, 1, 1, 1, -1] | ||
| 21 | + solution: | | ||
| 22 | + Na multiplicação nem todos os números têm inverso. Só têm inverso os números | ||
| 23 | + diferentes de zero. | ||
| 24 | + As outras propriedades são satisfeitas para todos os números. |
| @@ -0,0 +1,59 @@ | @@ -0,0 +1,59 @@ | ||
| 1 | +--- | ||
| 2 | +# --------------------------------------------------------------------------- | ||
| 3 | +- type: radio | ||
| 4 | + ref: prime-number-definition | ||
| 5 | + title: Números primos | ||
| 6 | + text: | | ||
| 7 | + Qual a definição de número primo? | ||
| 8 | + options: | ||
| 9 | + - Um número $n>1$ é primo se só é divisível por si próprio e pela unidade. | ||
| 10 | + - Um número $n\ge 2$ é primo se só é divisível por si próprio e pela unidade. | ||
| 11 | + - Um número $n>1$ é primo se os seus divisores são apenas $1$ e $n$. | ||
| 12 | + - Um número $n\ge 2$ é primo se os seus divisores são apenas $1$ e $n$. | ||
| 13 | + # wrong | ||
| 14 | + - Um número $n$ é primo se é divisível por si próprio e pela unidade. | ||
| 15 | + - Um número $n>1$ é primo se é divisível por si próprio e pela unidade. | ||
| 16 | + - Um número $n\ge 2$ é primo se não tem divisores. | ||
| 17 | + choose: 3 | ||
| 18 | + correct: [1, 1, 1, 1, 0, 0, 0] | ||
| 19 | + solution: | ||
| 20 | + Um número $n$ é primo se $n\ge 2$ e se os únicos divisores forem $1$ e $n$. | ||
| 21 | + | ||
| 22 | +# --------------------------------------------------------------------------- | ||
| 23 | +- type: checkbox | ||
| 24 | + ref: even-odd-prime | ||
| 25 | + title: Números pares, ímpares e primos | ||
| 26 | + text: Indique as afirmações verdadeiras. | ||
| 27 | + options: | ||
| 28 | + - ['3 é primo', '4 é primo'] | ||
| 29 | + - ['2 é par', '3 é par'] | ||
| 30 | + - ['1 é ímpar', '2 é ímpar'] | ||
| 31 | + correct: [1, 1, 1] | ||
| 32 | + solution: | | ||
| 33 | + A tabela seguinte resume as propriedades dos números 1 a 4: | ||
| 34 | + | ||
| 35 | + Número | ímpar | par | primo | ||
| 36 | + :-----:|:-----:|:---:|:------: | ||
| 37 | + 1 | S | N | N | ||
| 38 | + 2 | N | S | S | ||
| 39 | + 3 | S | N | S | ||
| 40 | + 4 | N | S | N | ||
| 41 | + | ||
| 42 | +# --------------------------------------------------------------------------- | ||
| 43 | +- type: radio | ||
| 44 | + ref: prime-numbers | ||
| 45 | + title: Números primos | ||
| 46 | + text: Qual dos seguintes números é um primo múltiplo de 3? | ||
| 47 | + options: | ||
| 48 | + - 3 | ||
| 49 | + # wrong | ||
| 50 | + - 9 | ||
| 51 | + - 13 | ||
| 52 | + - 21 | ||
| 53 | + - 15 | ||
| 54 | + - Não há primos múltiplos de 3. | ||
| 55 | + max_tries: 1 | ||
| 56 | + solution: | | ||
| 57 | + O único número primo múltiplo de 3 é o próprio 3. | ||
| 58 | + Todos os outros múltiplos (6, 9, 12, 15, ...) têm 3 como divisor e portanto | ||
| 59 | + não são primos. |
demo/math/questions.yaml
| @@ -1,87 +0,0 @@ | @@ -1,87 +0,0 @@ | ||
| 1 | ---- | ||
| 2 | -# --------------------------------------------------------------------------- | ||
| 3 | -- type: radio | ||
| 4 | - ref: distributive_property | ||
| 5 | - title: Propriedade distributiva | ||
| 6 | - text: | | ||
| 7 | - Qual das seguintes opções usa a propriedade distributiva para calcular | ||
| 8 | - $9\times 2 - 2\times 3$? | ||
| 9 | - options: | ||
| 10 | - # correct | ||
| 11 | - - $2\times(9 - 3)$ | ||
| 12 | - - $(9-3)\times 2$ | ||
| 13 | - # wrong | ||
| 14 | - - $18 - 6 = 12$ | ||
| 15 | - - $2\times 9 - 2\times 3$ | ||
| 16 | - - $2\times(9-2\times 3)$ | ||
| 17 | - correct: [1, 1, 0, 0, 0] | ||
| 18 | - choose: 3 | ||
| 19 | - max_tries: 1 | ||
| 20 | - solution: | | ||
| 21 | - Colocando o 2 em evidência, obtém-se a resposta correcta $2\times(9 - 3)$ | ||
| 22 | - ou $(9-3)\times 2$. | ||
| 23 | - | ||
| 24 | -# --------------------------------------------------------------------------- | ||
| 25 | -- type: checkbox | ||
| 26 | - ref: numbers | ||
| 27 | - title: Números pares e primos | ||
| 28 | - text: Indique as afirmações verdadeiras. | ||
| 29 | - options: | ||
| 30 | - - ['3 é primo', '4 é primo'] | ||
| 31 | - - ['2 é par', '3 é par'] | ||
| 32 | - - ['1 é ímpar', '2 é ímpar'] | ||
| 33 | - correct: [1, 1, 1] | ||
| 34 | - solution: | | ||
| 35 | - A tabela seguinte resume as propriedades dos números 1 a 4: | ||
| 36 | - | ||
| 37 | - Número | ímpar | par | primo | ||
| 38 | - :-----:|:-----:|:---:|:------: | ||
| 39 | - 1 | S | N | N | ||
| 40 | - 2 | N | S | S | ||
| 41 | - 3 | S | N | S | ||
| 42 | - 4 | N | S | N | ||
| 43 | - | ||
| 44 | -# --------------------------------------------------------------------------- | ||
| 45 | -- type: radio | ||
| 46 | - ref: prime_numbers | ||
| 47 | - title: Números primos | ||
| 48 | - text: Qual dos seguintes números é primo? | ||
| 49 | - options: | ||
| 50 | - - 13 | ||
| 51 | - - 12 | ||
| 52 | - - 14 | ||
| 53 | - - 1, a **unidade** | ||
| 54 | - max_tries: 1 | ||
| 55 | - solution: | | ||
| 56 | - O único número primo é o 13. | ||
| 57 | - | ||
| 58 | - O número 1, embora apenas seja divisível por ele próprio e pela unidade | ||
| 59 | - (que neste caso coincide com ele próprio), não é um número primo. Apenas | ||
| 60 | - são considerados números primos a partir do 2. | ||
| 61 | - | ||
| 62 | -# --------------------------------------------------------------------------- | ||
| 63 | -- type: checkbox | ||
| 64 | - ref: math-expressions | ||
| 65 | - title: Expressões matemáticas | ||
| 66 | - text: Quais das seguintes expressões são verdadeiras? | ||
| 67 | - options: | ||
| 68 | - - $1 > 0$ | ||
| 69 | - - $\sqrt{3} > \sqrt{2}$ | ||
| 70 | - - $e^{i\pi} + 1 = 0$ | ||
| 71 | - - $\frac{\partial f(x,y)}{\partial z} = 1$ | ||
| 72 | - - $-1 > 1$ | ||
| 73 | - # how many points for each checkmark (normalized afterwards): | ||
| 74 | - correct: [1, 1, 1, -1, -1] | ||
| 75 | - solution: | | ||
| 76 | - Das 5 opções existem 2 falsas. A derivada parcial é zero uma vez que a | ||
| 77 | - função não varia com $z$. A desigualdade $-1 > 1$ é também falsa, os | ||
| 78 | - números negativos são menores que os positivos. | ||
| 79 | - | ||
| 80 | -# --------------------------------------------------------------------------- | ||
| 81 | -# the program should print a question in yaml format. The args will be | ||
| 82 | -# sent as command line options when the program is run. | ||
| 83 | -- type: generator | ||
| 84 | - ref: overflow | ||
| 85 | - script: gen-multiples-of-3.py | ||
| 86 | - args: [11, 120] | ||
| 87 | - choose: 3 |
demo/solar_system/correct-first_3_planets.py
| @@ -1,28 +0,0 @@ | @@ -1,28 +0,0 @@ | ||
| 1 | -#!/usr/bin/env python3 | ||
| 2 | - | ||
| 3 | -import re | ||
| 4 | -import sys | ||
| 5 | -import time | ||
| 6 | - | ||
| 7 | -s = sys.stdin.read() | ||
| 8 | - | ||
| 9 | -ans = set(re.findall(r'[\w]+', s.lower())) # convert answer to lowercase | ||
| 10 | -ans.difference_update({'e', 'a', 'o', 'planeta', 'planetas'}) # ignore words | ||
| 11 | - | ||
| 12 | -# correct set of planets | ||
| 13 | -planets = {'mercúrio', 'vénus', 'terra'} | ||
| 14 | - | ||
| 15 | -correct = set.intersection(ans, planets) # the ones I got right | ||
| 16 | -wrong = set.difference(ans, planets) # the ones I got wrong | ||
| 17 | - | ||
| 18 | -grade = (len(correct) - len(wrong)) / len(planets) | ||
| 19 | - | ||
| 20 | -comments = 'Certo' if grade == 1.0 else 'as iniciais dos planetas são M, V e T' | ||
| 21 | - | ||
| 22 | -out = f'''--- | ||
| 23 | -grade: {grade} | ||
| 24 | -comments: {comments}''' | ||
| 25 | - | ||
| 26 | -time.sleep(2) # simulate computation time (may generate timeout) | ||
| 27 | - | ||
| 28 | -print(out) |
demo/solar_system/correct-timeout.py
demo/solar_system/public/earth.jpg
1.02 MB
demo/solar_system/public/jupiter.gif
2.96 MB
demo/solar_system/public/planets.png
419 KB
demo/solar_system/public/saturn.jpg
117 KB
demo/solar_system/questions.yaml
| @@ -1,69 +0,0 @@ | @@ -1,69 +0,0 @@ | ||
| 1 | ---- | ||
| 2 | - | ||
| 3 | -# --------------------------------------------------------------------------- | ||
| 4 | -- type: text | ||
| 5 | - ref: home-planet | ||
| 6 | - title: Sistema solar | ||
| 7 | - text: O nosso planeta chama-se planeta... | ||
| 8 | - correct: ['Terra', 'terra'] | ||
| 9 | - # opcional | ||
| 10 | - answer: Não é Marte... | ||
| 11 | - solution: | | ||
| 12 | - Embora algumas pessoas andem muitas vezes na Lua, o nosso planeta é o | ||
| 13 | - planeta **Terra**! | ||
| 14 | - | ||
| 15 | -  | ||
| 16 | - | ||
| 17 | -# --------------------------------------------------------------------------- | ||
| 18 | -- type: radio | ||
| 19 | - ref: solar-system | ||
| 20 | - title: Sistema solar | ||
| 21 | - text: | | ||
| 22 | -  | ||
| 23 | - | ||
| 24 | - Qual é o maior planeta do Sistema Solar? | ||
| 25 | - | ||
| 26 | - options: | ||
| 27 | - - Mercúrio | ||
| 28 | - - Marte | ||
| 29 | - - Júpiter | ||
| 30 | - - Têm todos o mesmo tamanho | ||
| 31 | - # opcional | ||
| 32 | - correct: 2 | ||
| 33 | - shuffle: false | ||
| 34 | - discount: true | ||
| 35 | - solution: | | ||
| 36 | - O maior planeta é Júpiter. Tem uma massa 1000 vezes inferior ao Sol, mas | ||
| 37 | - ainda assim 2.5 vezes maior que a massa de todos os outros planetas juntos. | ||
| 38 | - É um gigante gasoso maioritariamente composto por hidrogénio. | ||
| 39 | - | ||
| 40 | -  | ||
| 41 | - | ||
| 42 | -# --------------------------------------------------------------------------- | ||
| 43 | -- type: text-regex | ||
| 44 | - ref: saturn | ||
| 45 | - title: Sistema solar | ||
| 46 | - text: O planeta do sistema solar conhecido por ter aneis é o planeta... | ||
| 47 | - correct: '[Ss]aturno' | ||
| 48 | - solution: | | ||
| 49 | - O planeta Saturno é famoso pelos seus anéis. | ||
| 50 | - É o segundo maior planeta do Sistema Solar, a seguir a Júpiter. | ||
| 51 | - Tal como Júpiter, é um gigante gasoso. | ||
| 52 | - Os seus anéis são formados por partículas de gelo. | ||
| 53 | - | ||
| 54 | -  | ||
| 55 | - | ||
| 56 | -# --------------------------------------------------------------------------- | ||
| 57 | -- type: textarea | ||
| 58 | - ref: first_3_planets | ||
| 59 | - title: Sistema solar | ||
| 60 | - text: | | ||
| 61 | - Qual o nome dos três planetas mais próximos do Sol? | ||
| 62 | - Exemplo `Lua, Ceres e Vulcano` | ||
| 63 | - correct: correct-first_3_planets.py | ||
| 64 | - # correct: correct-timeout.py | ||
| 65 | - # opcional | ||
| 66 | - answer: Vulcano, Krypton, Plutão | ||
| 67 | - timeout: 3 | ||
| 68 | - solution: | | ||
| 69 | - Os 3 planetas mais perto do Sol são: Mercúrio, Vénus e Terra. |