<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<!-- question: 0  -->
  <question type="category">
    <category>
      <text><![CDATA[$system$/top/PROTOTYPES & DEMOS]]></text>
    </category>
    <info format="html">
      <text><![CDATA[<p>!! DANGER ZONE !!</p>]]></text>
    </info>
    <idnumber></idnumber>
  </question>

<!-- question: 0  -->
  <question type="category">
    <category>
      <text><![CDATA[$system$/top/PROTOTYPES & DEMOS/PRODUCTION PROTOTYPES]]></text>
    </category>
    <info format="html">
      <text></text>
    </info>
    <idnumber></idnumber>
  </question>

<!-- question: 4802  -->
  <question type="coderunner">
    <name>
      <text>A_PROTOTYPE_python3_MAIN</text>
    </name>
    <questiontext format="html">
      <text><![CDATA[<p><em><strong>Last Updated: Oct 12, 2024</strong></em></p>
<h3><span style="text-decoration: underline;">How Results Are Displayed to Students</span></h3>
<ul>
<li><strong>Input - Got - Expected - Points</strong>
<ul>
<li>What inputs were given for each test</li>
<li>What THEIR code printed/outputted (Got)</li>
<li>What was expected by the test auto-grader</li>
<li>The number of points ( 2 / 2) for that specific test.</li>
</ul>
</li>
<li>Scratchpad code always ignored during testing</li>
<li>If a test fails, best efforts are attempted to continue on to other tests
<ul>
<li>Helps prevent a student failing future tests 3,4,5 as a result of failing test 2</li>
</ul>
</li>
<li>You can customize what is displayed by clicking Customize and changing the list of dictionary values in "<em>Result columns</em>"</li>
</ul>
<h3> </h3>
<p></p>
<h3><span style="text-decoration: underline;">Order Of Events For Test Cases</span></h3>
<p>Review "Test Variables and Functions" as well as "Template Parameters" for more info on the <code>embedded variables</code> named below.</p>
<ol>
<li><strong>Global Extra</strong>, followed by <strong>Test Extra</strong> (useful for individual tests) are injected PRIOR to the student code. </li>
<li>Student Code is appended, and also saved in the variable <strong><code>student_code</code></strong>
<ul>
<li>Given the sequence, you can't inspect student code in your global/test extra</li>
</ul>
</li>
<li>Prohibited code snippets are checked for in student code.
<ul>
<li>If any are found and <code><strong>PROHIBIT_SPECIFIC_CODE_PHRASES</strong></code> is set to True, the test fails</li>
</ul>
</li>
<li>Substitutions to be made in student_code is completed
<ul>
<li>If <code><strong>ENABLE_STUDENT_CODE_SUBSTITUTIONS</strong></code> is set to True, all changes are made prior to running any code.</li>
<li>An important one set by default and replaces <em>except:</em> with <em>except Exception as e:</em></li>
</ul>
</li>
<li>Syntax errors are checked for in the student code.
<ul>
<li>The code is linted using ast</li>
<li>If any errors are found, they are printed if <code><strong>OUTPUT_STUDENT_ERRORS_TO_USER</strong></code> is set to True</li>
</ul>
</li>
<li>Runtime errors are checked for by executing the student code, not including any test case code.
<ul>
<li>This runs to check for simple run time errors a student may have typed. Similar to pressing "Run Code", but via the test cases.</li>
</ul>
</li>
<li>Errors: if any errors do occur, best efforts are made to match up the error line numbers to the student code
<ul>
<li><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">Scratchpad code numbers are not an issue as code here is ignored and not tested</span></li>
<li><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">You can access <code><strong>TEST_ERROR_DESCRIPTION</strong></code>, <code><strong>TEST_ERROR_TYPE</strong></code>, and <code><strong>TEST_ERROR_LINE_NUMBER</strong></code> in the event of any errors<strong> in your Test Case</strong> code.</span></li>
</ul>
</li>
<li>Finally, all code is run together
<ul>
<li>Any pre code (Global or Test Extra) + Student Code + Test Case Code</li>
</ul>
</li>
</ol>
<p> </p>
<p></p>
<h3><span style="text-decoration: underline;">Test Case Functions &amp; Variables</span></h3>
<h5>Student Code</h5>
<p>Student's raw code can be accessed in a multi-line string in your test cases.</p>
<ul>
<li><code><strong>student_code</strong></code>
<ul>
<li><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">Use Cases:</span>
<ul>
<li><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">if </span><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">"import" in student_code</span></li>
<li><span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; font-size: 1rem; text-align: initial;">student_code.splitlines() then iterate thru each line looking for something</span></li>
</ul>
</li>
</ul>
</li>
</ul>
<p></p>
<h5>Removing/Capturing Print Statements</h5>
<p>Test code can told to silence print statements using functions built into template:</p>
<ul>
<li><strong><code>print_off()</code></strong>
<ul>
<li>No args needed and returns nothing. Just call it and any output (print/input/etc)</li>
</ul>
</li>
</ul>
<ul>
<li><strong><code>print_on()</code></strong><br>
<ul>
<li>Reenables any stdout back to the output screen
<ul>
<li>You might turn print off before running their code, then back on after/during your tests.</li>
<li>For example, off in test extra then on in your test case code.</li>
</ul>
</li>
<li>Returns any output since print_on was engaged.
<ul>
<li>You could search this for a particular output if you wanted</li>
</ul>
</li>
<li>Returns in a form of a list (each line is a string element)</li>
</ul>
</li>
</ul>
<h5> </h5>
<h5>Check For Beginning Comments</h5>
<ul>
<li><code><strong>check_first_three_lines()</strong></code>
<ul>
<li>Checks for 3 comments in the first 5 lines of student code</li>
<li>Will print one of 2 results:
<ul>
<li>Passed comment test</li>
<li>Failed comment test - add 3# at start of your code</li>
</ul>
</li>
</ul>
</li>
</ul>
<p></p>
<p></p>
<h3><span style="text-decoration: underline;">Template Parameters</span></h3>
<ul>
<li>There are a number of built-in options that are run during each test case that you may want turned on/off on a particular question.</li>
<li>These have default values already preset in the prototype question.</li>
<li>To modify these for a single question, you simply <em><strong>copy and paste</strong></em> the JSON formatted record below into the <strong>Template params</strong> and adjust as needed </li>
</ul>
<p><code>{</code><br><code>    "SCAN_FOR_SYNTAX_ERRORS": true,</code><br><code>    "SCAN_FOR_RUNTIME_ERRORS": true,</code><br><code>    "OUTPUT_STUDENT_ERRORS_TO_USER": true,</code><br><code>    "OUTPUT_TEST_ERRORS_TO_USER": true,</code><br><code>    "ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR": true,</code><br><code>    "PROHIBIT_SPECIFIC_CODE_PHRASES": false,</code><br><code>    "ENABLE_STUDENT_CODE_SUBSTITUTIONS": false</code><br><code>}</code></p>
<h5>What They Do</h5>
<ul>
<li>SCAN_FOR_SYNTAX_ERRORS
<ul>
<li>Check student code for syntax errors prior to running test code</li>
</ul>
</li>
<li>SCAN_FOR_RUNTIME_ERRORS
<ul>
<li>Check student code for runtime errors prior to running test code    </li>
</ul>
</li>
<li>OUTPUT_STUDENT_ERRORS_TO_USER
<ul>
<li>Output any errors in the student code itself (run on its own)</li>
</ul>
</li>
<li>OUTPUT_TEST_ERRORS_TO_USER
<ul>
<li>Output any errors generated during the test case to the student</li>
</ul>
</li>
<li>ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR
<ul>
<li>Display full traceback error (<strong>Note: this exposes your test code to the student</strong>)</li>
</ul>
</li>
<li>ENABLE_STUDENT_CODE_SUBSTITUTIONS
<ul>
<li>Replace specific code phrases in student code as listed</li>
</ul>
</li>
<li>PROHIBIT_SPECIFIC_CODE_PHRASES
<ul>
<li>Restrict any code phrases (import, etc)</li>
</ul>
</li>
</ul>
<p>If you want to further customize <code>PROHIBIT_SPECIFIC_CODE_PHRASES</code> or <code>ENABLE_STUDENT_CODE_SUBSTITUTIONS</code> (they are both dictionary constants in the test template code), then at the top of the question choose "<strong>Customise</strong>", then under the new Customisation section edit "<strong>Template</strong>" and mod those variables.</p>
<p></p>
<p></p>
<h3><span style="text-decoration: underline;">Randomization</span></h3>
<h5>In Test Cases</h5>
<ul>
<li>Anywhere in your test case code you'll need to enter <code>random.seed(some_number)</code> so the test is predictable</li>
<li>Random library is already imported in the test template</li>
</ul>
<p></p>
<h5>Scratchpad "Run Code" Option</h5>
<ul>
<li>When not running tests, if students run "Run Code" from the scratchpad and is trying to use both random and <em><strong>multiple inputs</strong></em>, we have an issue.<br>
<ul>
<li>With each input entered, full code is run again (the previous input is then injected)</li>
<li>This gives the illusion of a full operational program, but really it's just re-run every time there is another input().</li>
<li>This means if we are randoming anything, it's re-randomized with <em>every input</em>!
<ul>
<li>For example "guess a number" games results in the original number being change with every input.</li>
</ul>
</li>
<li>Therefore, if there are any programs with more than 1 input, it needs to be <strong>seeded</strong>.</li>
</ul>
</li>
<li>This is prebuilt for you in the scratchpad template (Prototype extra)
<ul>
<li>Be default, it will not seed the randomizer with there are <code>&lt;= 1 "input("</code> detected in the code</li>
<li>Anything more than 2, t<span style="font-size: 1rem; text-align: initial;">he number of characters in the students code is used as the seed value</span>
<ul>
<li>If a student notices it's always the same, tell them to press space or make a new line. This changes the seed</li>
</ul>
</li>
<li>If you want to edit this, customize the question and change the <code><strong>max_allowed_inputs</strong></code> in <em><strong>Prototype Extra</strong></em></li>
</ul>
</li>
</ul>
<p> </p>]]></text>
    </questiontext>
    <generalfeedback format="html">
      <text></text>
    </generalfeedback>
    <defaultgrade>5.0000000</defaultgrade>
    <penalty>0.0000000</penalty>
    <hidden>0</hidden>
    <idnumber></idnumber>
    <coderunnertype>A_PROTOTYPE_python3_MAIN</coderunnertype>
    <prototypetype>2</prototypetype>
    <allornothing>0</allornothing>
    <penaltyregime>0, 0, 0, 10, 20, ...</penaltyregime>
    <precheck>0</precheck>
    <hidecheck>0</hidecheck>
    <showsource>0</showsource>
    <answerboxlines>18</answerboxlines>
    <answerboxcolumns>100</answerboxcolumns>
    <answerpreload><![CDATA[{"answer_code":[""],"test_code":[""],"show_hide":["1"],"prefix_ans":["1"]}]]></answerpreload>
    <globalextra></globalextra>
    <useace>1</useace>
    <resultcolumns><![CDATA[[["Input", "stdin"], ["Got", "got"], ["Expected", "expected"], ["Points", "awarded", "mark", "%.1f / %.1f"]]]]></resultcolumns>
    <template><![CDATA[import random, os, sys, io, inspect, ast, traceback, subprocess, tempfile

# NOTE: if you want a per test restriction, do it in the test code for that test by inspecting student_code
prohibited_code_phrases = {
    "import" : "Sorry, you cannot import any libraries in this question.",
    "sys.exit(" : "Sorry, you cannot use sys.exit() in your code."
}


# Replace any student code below with a substituation
# NOTE: A bare 'except' can catch a test error and cause erratic test case behavior. It's recommended it's replaced in all student code
code_phrases_to_replace = {
    "except:" : "except Exception as e:"
}




#### TEMPLATE FUNCTIONS

## Functions For Evaluating Student Code  
# Capture print statements into memory
def print_off():
    global old_stdout, new_stdout   # Uses global as called from various areas
    old_stdout = sys.stdout
    new_stdout = io.StringIO()
    sys.stdout = new_stdout

# Restores printing output to console (stdout)
def print_on():
    global old_stdout, new_stdout   # Uses global as called from various areas
    sys.stdout = old_stdout
    output = new_stdout.getvalue()
    
    # Returns any output recorded when turned off
    # NOTE: remove .splitlines() if you want all as sting
    return output.splitlines()      # Returns a list by default


# Gets current code line number. Needed to adjust error line numbers for students
def get_current_line_number():
    # Returns the line number in its caller's context
    return inspect.currentframe().f_back.f_lineno


def check_first_three_lines():
    #string = student_code
    # Split the string into lines
    lines = student_code.splitlines()
    count = sum(1 for line in lines[:5] if line.startswith('#'))
    
    # Check if there are at least 3 lines, and if the first 3 lines start with '#'
    if count > 2:
        print("Passed comment test")
    else:
        print("Failed comment test - add 3# at start of your code")

# Used to save the input from the test cases
__saved_input__ = input
# Grabs input items from tests 
def input(prompt=''):
    s = __saved_input__(prompt)
    print(s)
    return s
    


######################## TEMPLATE BEGINS - USE CAUTION ########################   
    
#### Required Global Template Variables - DO NOT MOD THESE
# Globals for print_on and print_off
old_stdout = new_stdout = ""            

# If there is an error during a test case, return these values
TEST_ERROR_DESCRIPTION = TEST_ERROR_TYPE = TEST_ERROR_LINE_NUMBER = ""



# Read the 'Template params' dict otherwise use the default values (default values set in prototype question)
{% if QUESTION.parameters.SCAN_FOR_SYNTAX_ERRORS %}
SCAN_FOR_SYNTAX_ERRORS = True
{% else %}
SCAN_FOR_SYNTAX_ERRORS = False
{% endif %}

{% if QUESTION.parameters.SCAN_FOR_RUNTIME_ERRORS %}
SCAN_FOR_RUNTIME_ERRORS = True
{% else %}
SCAN_FOR_RUNTIME_ERRORS = False
{% endif %}

{% if QUESTION.parameters.OUTPUT_STUDENT_ERRORS_TO_USER %}
OUTPUT_STUDENT_ERRORS_TO_USER = True
{% else %}
OUTPUT_STUDENT_ERRORS_TO_USER = False
{% endif %}

{% if QUESTION.parameters.OUTPUT_TEST_ERRORS_TO_USER %}
OUTPUT_TEST_ERRORS_TO_USER = True
{% else %}
OUTPUT_TEST_ERRORS_TO_USER = False
{% endif %}

{% if QUESTION.parameters.ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR %}
ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR = True
{% else %}
ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR = False
{% endif %}

{% if QUESTION.parameters.ENABLE_STUDENT_CODE_SUBSTITUTIONS %}
ENABLE_STUDENT_CODE_SUBSTITUTIONS = True
{% else %}
ENABLE_STUDENT_CODE_SUBSTITUTIONS = False
{% endif %}

{% if QUESTION.parameters.PROHIBIT_SPECIFIC_CODE_PHRASES %}
PROHIBIT_SPECIFIC_CODE_PHRASES = True
{% else %}
PROHIBIT_SPECIFIC_CODE_PHRASES = False
{% endif %}







##### TEMPLATE LOOP FOR TEST CASES

# Run all test cases tests in a loop
{% for TEST in TESTCASES %}


# Inject and Execute both Global and Test Extra (BEFORE Student Code Is Run)
{{ QUESTION.globalextra }}

{{ TEST.extra }}


# Capture student code into a variable
student_code = """{{ STUDENT_ANSWER | e('py') }}"""


# Count number of inputs expected in test. Not needed for test cases
test_inputs = """{{ TEST.stdin | e('py') }}"""
test_num_of_inputs = len(test_inputs.splitlines())

# Used to abort test on fail but allow others to continue
continue_testing = True


#### Scan Student Code for Prohibited Code
if PROHIBIT_SPECIFIC_CODE_PHRASES and continue_testing:
    for prohibited_code in prohibited_code_phrases:
        if prohibited_code in student_code:        
            print(prohibited_code_phrases[prohibited_code])
            continue_testing = False      # Quits current test but allows next to proceed


### Substitute student code for alternative if needed
if ENABLE_STUDENT_CODE_SUBSTITUTIONS:
    for student_code_sub in code_phrases_to_replace:
        if student_code_sub in student_code:        
            student_code = student_code.replace(student_code_sub, code_phrases_to_replace[student_code_sub])
    



##### Scan students code for SYNTAX and RUNTIME errors

# For formatting, student code has 1 additional blank line added (see student_code declaration)
#first_newline = student_code.find('\n')
#fixed_student_code = student_code[first_newline+1:]  # Skip the newline character itself

# Scan for SYNTAX errors
if SCAN_FOR_SYNTAX_ERRORS and continue_testing:
    try:    
        ast.parse(student_code)
    except SyntaxError as e:
        TEST_ERROR_DESCRIPTION = e.msg      # Capture the description of the error
        TEST_ERROR_TYPE = type(e).__name__  # Capture the error type
        TEST_ERROR_LINE_NUMBER = e.lineno   # Capture the line number of the error
    
        
        # Format the exception to show traceback as Python would in the console
        tb = traceback.format_exception_only(type(e), e)
        tb_lines = ''.join(tb).strip().splitlines()     # Split traceback into lines
        tb_without_first_line = '\n'.join(tb_lines[0:]) # Join all lines

        print("\nERROR: There is an error in the *formatting* of your code\n")
        if OUTPUT_STUDENT_ERRORS_TO_USER:
            print(tb_without_first_line)
            #print(''.join(tb).strip())     # Complete error but line numbers don't match
        continue_testing = False # Quits current test but allows the next to proceed
    

# Scan for RUNTIME errors
#if SCAN_FOR_RUNTIME_ERRORS and continue_testing:
if continue_testing:
    try:
        # Compile code first to include filename and starting line number
        current_line_num = get_current_line_number()
        compiled_code = compile(student_code, filename="test_student_code.py", mode='exec')
        exec(compiled_code)
    except Exception as e:
        # Access traceback details directly from the exception object
        exc_traceback = e.__traceback__
        while exc_traceback.tb_next:
            exc_traceback = exc_traceback.tb_next
        line_number = exc_traceback.tb_lineno   # Get line number
        error_type = type(e).__name__           # Get the exact error type
        try:
            line_num = int(line_number)
            student_code_list = student_code.splitlines()
        except Exception as e:
            line_num = 0
        TEST_ERROR_DESCRIPTION = error_type                 # Capture the description of the error
        TEST_ERROR_TYPE = e                                 # Capture the error type
        TEST_ERROR_LINE_NUMBER = line_number                # Capture the line number of the error
        TEST_ERROR_FAILED_LINE = ""
        if line_num != 0:
            TEST_ERROR_FAILED_LINE = student_code_list[line_num-1]  # Grab the actual line of code that failed (list so go back 1)
            
        print("\nERROR: There is an error while *executing* your code\n")
        if OUTPUT_STUDENT_ERRORS_TO_USER:
            # Specific EOF error reply
            if error_type == "EOFError":
                
                print(f"\nError Testing Input\n\nThe test expected {test_num_of_inputs} input()'s being asked.")
                print("Your code expects something different than what the test is giving.")
                print("\nManually 'Run Code' and investigate for yourself.")
                
            # All other errors show the actual error code  
            else:
                print(f"An error occurred on line {line_number}:")
                print(TEST_ERROR_FAILED_LINE)
                print(f"{error_type}: {e}\n")
                print("Manually 'Run Code' for further troubleshooting.")
        continue_testing = False # Quits current test but allows the next to proceed




if continue_testing:
    #### Add / inject test code here by first wraping it in a try/except block with proper indentation
    try:
        pass    # This is required so the try statement isn't empty if there is no test case code
{{ ('        ' ~ TEST.testcode)|replace({'\n': '\n        '}) }}
    except Exception as e:
        print(f"\nERROR: An error occurred while *the test code was interacting with* your code.")
        if OUTPUT_TEST_ERRORS_TO_USER:
            print(f"\nError: {e}\n")
            if ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR:
                tb = traceback.format_exc()
                print(tb)



SEPARATOR = "#<ab@17943918#@>#"


{% if not loop.last %}
print(SEPARATOR)
{% endif %}
{% endfor %}]]></template>
    <iscombinatortemplate>1</iscombinatortemplate>
    <allowmultiplestdins>0</allowmultiplestdins>
    <answer><![CDATA[{"answer_code":[""],"test_code":[""],"show_hide":["1"],"prefix_ans":["1"]}]]></answer>
    <validateonsave>0</validateonsave>
    <testsplitterre><![CDATA[|#<ab@17943918#@>#\n|ms]]></testsplitterre>
    <language>python3</language>
    <acelang></acelang>
    <sandbox></sandbox>
    <grader>NearEqualityGrader</grader>
    <cputimelimitsecs>5</cputimelimitsecs>
    <memlimitmb></memlimitmb>
    <sandboxparams></sandboxparams>
    <templateparams><![CDATA[{
    "SCAN_FOR_SYNTAX_ERRORS": true,
    "SCAN_FOR_RUNTIME_ERRORS": true,
    "OUTPUT_STUDENT_ERRORS_TO_USER": true,
    "OUTPUT_TEST_ERRORS_TO_USER": true,
    "ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR": true,
    "PROHIBIT_SPECIFIC_CODE_PHRASES": false,
    "ENABLE_STUDENT_CODE_SUBSTITUTIONS": false
}]]></templateparams>
    <hoisttemplateparams>1</hoisttemplateparams>
    <extractcodefromjson>1</extractcodefromjson>
    <templateparamslang>twig</templateparamslang>
    <templateparamsevalpertry>0</templateparamsevalpertry>
    <templateparamsevald><![CDATA[{
    "SCAN_FOR_SYNTAX_ERRORS": true,
    "SCAN_FOR_RUNTIME_ERRORS": true,
    "OUTPUT_STUDENT_ERRORS_TO_USER": true,
    "OUTPUT_TEST_ERRORS_TO_USER": true,
    "ALLOW_TRACEBACK_DISPLAYED_ON_TEST_ERROR": true,
    "PROHIBIT_SPECIFIC_CODE_PHRASES": false,
    "ENABLE_STUDENT_CODE_SUBSTITUTIONS": false
}]]></templateparamsevald>
    <twigall>1</twigall>
    <uiplugin>scratchpad</uiplugin>
    <uiparameters><![CDATA[{  
"wrapper_src": "prototypeextra",  
"output_display_mode": "json",  
"escape": true,  
"scratchpad_name":"Scratchpad",  
"button_name":"Run Code",  
"prefix_name":"Run Answer Code?",  
"help_text": "<i>Your work is <b>autosaved</b> every 20 seconds.</i><br><br><h4><u><b>Run Code Button</u></b></h4>You can press '<b>Run Code</b>' as often as you'd like for no penality. This will execute your code and show your output.<br><br><b><u>If the 'Run Answer Code?' box is:</b></u><br><b>Checked</b> = Run code in Answer Box<br><b>Unchecked</b> = Run code in Scratchpad<br><br><br><h4><u><b>Check Button</u></b></h4>Pressing '<b>Check</b>' will run prebuilt 'tests' on code in your Answer Box.<br><br>If <b>penalty regime</b> (shown just above the answer box) is <b><i>not zero 0 only</b></i>, then you could be <i>docked marks</i> if you press it. Ask if you're not sure!<br><br><br><h4><u><b>What Is Graded</u></b></h4>Only code in the answer box is graded. The scratchpad is basically 'scrap paper' and not part of your answer.<br><br><br><h4><u><b>Using The Scratchpad</h4></u></b>If you have the 'run answer code?' box checked it will actually run code from <i>both</i> boxes. It will run the answer code 1st, then whatever is in the scratchpad.<br><br>So if the box is checked, you'll want to <b><u>remove/comment out</u> anything in your scratchpad</b> so code in both boxes doesn't run together.<br><br>"  
}]]></uiparameters>
    <attachments>0</attachments>
    <attachmentsrequired>0</attachmentsrequired>
    <maxfilesize>10240</maxfilesize>
    <filenamesregex></filenamesregex>
    <filenamesexplain></filenamesexplain>
    <displayfeedback>1</displayfeedback>
    <giveupallowed>0</giveupallowed>
    <prototypeextra><![CDATA[import sys, io, subprocess, ast, traceback, json, locale, re

# RANDOM NUMBERS: Set the max allowed input() phases allowed in the student code (be careful with more than 0)
max_allowed_inputs = 0

locale.setlocale(locale.LC_ALL, "C.UTF-8")
MAX_OUTPUT_CHARS = 30000

# Any pre-loaded code you want for scratchpad runs, enter it here
PRE_CODE = """

"""

# RANDOM SEED BASED ON CHARACTER LENGTH OF STUDENT CODE
seed_info_start = """import random
random.seed("""
seed_info_end = """)
"""
# Construct seeding code for precode injection
seed_code = seed_info_start + str(len(PRE_CODE)) + seed_info_end
# Count number of input()'s in student code
input_count = PRE_CODE.count("input(")    

# Precode for creating the Input Fields
ini_subproc_code = """
import sys
MAX_OUTPUT_CHARS = 30000
__saved_input__ = input

def input(prompt=''):
    try:
        line = __saved_input__()
    except EOFError:
        print(prompt, end = '')
        sys.stderr.flush()
        sys.stdout.flush()
        sys.exit(42)
    print(prompt, end='')
    print(line)
    return line

__saved_print__ = print
__output_chars__ = 0

def print(*params, **keyparams):
    global __output_chars__
    for param in params:
        try:
            __output_chars__ += len(str(param))
        except:
            pass
    if __output_chars__ > 2 * MAX_OUTPUT_CHARS:
        __saved_print__("\\\\n*** Excessive output. Job aborted ***", file=sys.stderr)
        sys.exit(1)
    else:
        __saved_print__(*params, **keyparams)
"""



# Add input precode and apply Random Seed based on character number if inputs are found
if input_count > max_allowed_inputs:
    subproc_code = seed_code + ini_subproc_code
else:
    subproc_code = ini_subproc_code


#### FULL Precode (does not include student code)
subproc_code += PRE_CODE

##### STUDENT Code Only 
og_student_code = """{| ANSWER_CODE |}
{| SCRATCHPAD_CODE |}
"""

replaced_student_code = og_student_code.replace("except:", "except Exception as e:")

## NOTE: Precode and Student Code Mixes as Needed Below

# Normalizes code lines numbers by removing precode injected line numbers from student code
def adjust_error_message(stderr, line_offset):
    # Adjust the line numbers in the traceback using a more precise regex
    def adjust_line_number(match):
        # Extract the current line number from the traceback
        line_number = int(match.group(1))
        # Adjust the line number by subtracting the offset
        return f'line {line_number - line_offset}'

    # Try to remove precode line numbers
    try:
        adjusted_stderr = re.sub(r'line (\d+)', adjust_line_number, stderr)
        return adjusted_stderr
    except:
        # If precode adjustment failed, return original numbers
        return "NOTE: Line numbers below are not correctly adjusted\n" + stderr


# Cut short any stdout or stderr output
def truncated(s):
    return s if len(s) < MAX_OUTPUT_CHARS else s[:MAX_OUTPUT_CHARS] + " ... (truncated)"

# Checks the syntax BEFORE executing.
def check_syntax(code_to_test):
    try:
        ast.parse(code_to_test)
        return ""
    except SyntaxError:
        catcher = io.StringIO()
        traceback.print_exc(limit=0, file=catcher)
        return catcher.getvalue()

# Prepare variables for analysis
stdout = ""
stderr = check_syntax(replaced_student_code)  # Check that student code has no syntax errors

if stderr == "":  # No syntax errors, run code
    program_code = subproc_code + replaced_student_code
    with open("prog.py", "w", encoding="utf-8") as outfile:
        outfile.write(program_code)

    # Calculate the number of precode lines
    line_offset = subproc_code.count('\n')

    proc = subprocess.Popen(
        [sys.executable, "prog.py"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    try:
        stdout, stderr = proc.communicate(timeout=3)
        returncode = proc.returncode
    except subprocess.TimeoutExpired:
        # Run timed out. Maybe infinate loop?
        proc.kill()
        stdout, stderr = proc.communicate()
        returncode = 13

    # If an error was detected, try to adjust line numbers. If not, return original error
    if stderr:
        stderr = adjust_error_message(stderr, line_offset)


else:
    returncode = 1  # Syntax errors


# CodeRunner requires a JSON dump. Assemble it here.
output = {
    "returncode": returncode,
    "stdout": truncated(stdout),
    "stderr": truncated(stderr),
    "files": [],
}

# Dump for CodeRunner
print(json.dumps(output))
]]></prototypeextra>
    <testcases>
    </testcases>
  </question>

<!-- question: 4765  -->
  <question type="coderunner">
    <name>
      <text>A_PROTOTYPE_python3_SIMPLE</text>
    </name>
    <questiontext format="html">
      <text><![CDATA[<h4>Template Layout</h4>
<ul>
<li><strong>Global Extra</strong> and <strong>Test Extra</strong> (extra for specific tests) is injected PRIOR to the question. </li>
<li>Before running student code, it's evaluated for presence of:
<ul>
<li><code>quit()</code></li>
<li><code>exit()</code></li>
<li>syntax errors</li>
<li>runtime errors</li>
<li>attempts to ensure error line numbers match up for the student</li>
<li><strong>Note:</strong> You can add more to this if statement via customizing so you can edit it</li>
</ul>
</li>
<li>Student code is then run as part of the test.</li>
<li>Test code for each test runs AFTER the student code.</li>
</ul>
<p> </p>
<h4>Test Code</h4>
<ul>
<li>
<h5>Removing/Capturing Print Statements</h5>
<ul>
<li>Test code can told to silence print statements using functions built into template:
<ul>
<li><strong><code>print_on()</code></strong>
<ul>
<li>No args needed and returns nothing. Just call it</li>
</ul>
</li>
<li><strong><code>print_off()</code></strong>
<ul>
<li>Returns any output since print_on was engaged. </li>
<li>Returns in a form of a list (each line is a string element)</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p> </p>
<ul>
<li>
<h5>Seeding Randomization</h5>
<ul>
<li>Anywhere in your test code enter <code>random.seed(some_number)</code></li>
<li>Random library is already imported in the test template</li>
</ul>
</li>
</ul>
<p> </p>
<h4>Scratchpad "Run Code" Option &amp; Randomization</h4>
<ul>
<li>When not running tests, if students run "Run Code" from the scratchpad and is trying to use both random and <em><strong>multiple inputs</strong></em>, we have an issue.<br>
<ul>
<li>With each input entered, full code is run again (the previous input is then injected)</li>
<li>This gives the illusion of a full operational program, but really it's just re-run every time there is another input().</li>
<li>This means if we are randoming anything, it's re-randomized with <em>every input</em>!
<ul>
<li>For example "guess a number" games results in the original number being change with every input.</li>
</ul>
</li>
<li>Therefore, if there are any programs with more than 1 input, it needs to be <strong>seeded</strong>.</li>
</ul>
</li>
<li>This is prebuilt for you in the scratchpad template (Prototype extra)
<ul>
<li>Be default, it will not seed the randomizer with there are <code>&lt;= 1 "input("</code> detected in the code</li>
<li>Anything more than 2, t<span style="font-size: 1rem; text-align: initial;">he number of characters in the students code is used as the seed value</span>
<ul>
<li>If a student notices it's always the same, tell them to press space or make a new line. This changes the seed</li>
</ul>
</li>
<li>If you want to edit this, customize the question and change the <code><strong>max_allowed_inputs</strong></code> in <em><strong>Prototype Extra</strong></em></li>
</ul>
</li>
</ul>
<p> </p>
<h4>How Results Are Outputed to Students</h4>
<ul>
<li>What was Inputed</li>
<li>What THEIR code gave (Got)</li>
<li>What was expected by the auto-grader</li>
</ul>]]></text>
    </questiontext>
    <generalfeedback format="html">
      <text></text>
    </generalfeedback>
    <defaultgrade>5.0000000</defaultgrade>
    <penalty>0.0000000</penalty>
    <hidden>0</hidden>
    <idnumber></idnumber>
    <coderunnertype>A_PROTOTYPE_python3_SIMPLE</coderunnertype>
    <prototypetype>2</prototypetype>
    <allornothing>1</allornothing>
    <penaltyregime>0</penaltyregime>
    <precheck>0</precheck>
    <hidecheck>0</hidecheck>
    <showsource>0</showsource>
    <answerboxlines>18</answerboxlines>
    <answerboxcolumns>100</answerboxcolumns>
    <answerpreload></answerpreload>
    <globalextra></globalextra>
    <useace>1</useace>
    <resultcolumns><![CDATA[[["Input", "stdin"], ["Got", "got"], ["Expected", "expected"]]]]></resultcolumns>
    <template><![CDATA[import random, os, sys, io, inspect, ast, traceback
old_stdout = new_stdout = ""    # Globals for print_on and print_off

#### Functions needed for evaluating student code  
# Capture print statements into memory
def print_off():
    global old_stdout, new_stdout   # Uses global as called from various areas
    old_stdout = sys.stdout
    new_stdout = io.StringIO()
    sys.stdout = new_stdout

# Restores printing output to console (stdout)
def print_on():
    global old_stdout, new_stdout   # Uses global as called from various areas
    sys.stdout = old_stdout
    output = new_stdout.getvalue()
    
    # Returns any output recorded when turned off
    # NOTE: remove .splitlines() if you want all as sting
    return output.splitlines()      # Returns a list by default


# Gets current code line number. Needed to adjust error line numbers for students
def get_current_line_number():
    # Returns the line number in its caller's context
    return inspect.currentframe().f_back.f_lineno


# Grabs input items from tests    
__saved_input__ = input
def input(prompt=''):
    s = __saved_input__(prompt)
    print(s)
    return s



##### Run all test cases tests in a loop
{% for TEST in TESTCASES %}


# Inject and Execute both Global and Test Extra (BEFORE Student Code Is Run)
{{ TEST.extra }}
{{ QUESTION.globalextra }}


# Capture student code into a variable
student_code = """
{{ STUDENT_ANSWER | e('py') }}"""



# Put an IF prior to this if you want to check for anything

# Check for existance of quit or exit (both break the testing). Add additional elif as needed.
#     For example, you could eval for the existance of a function name,
#     or use regex to find a function and code within and determine the number of lines,
#     really add here whatever restrictions you want for all tests
#     NOTE: if you want a per test restriction, do it in the test code for that test!


if "quit(" in student_code:                                     # Check for quit() in student code
    print("You cannot use quit() in your code or it will fail the tests")
    exit()      # Quits current test but allows next to proceed
elif "exit(" in student_code:                                   # Check for exit() in student code
    print("You cannot use exit() in your code or it will fail the tests")
    exit()      # Quits current test but allows next to proceed

#### Test execute code
# For formatting, student code has 1 additional blank line added (see student_code declaration)
first_newline = student_code.find('\n')
fixed_student_code = student_code[first_newline+1:]  # Skip the newline character itself

# ##### Check for syntax errors
# try:    
#     ast.parse(fixed_student_code)
# except SyntaxError as e:
#     print(f"Syntax error in the code: {e}")
#     exit()      # Quits current test but allows next to proceed


try:
    # Compile code first to include filename and starting line number
    current_line_num = get_current_line_number()
    compiled_code = compile(fixed_student_code, filename="test_student_code.py", mode='exec')
    exec(compiled_code)
except Exception as e:
    # Access traceback details directly from the exception object
    exc_traceback = e.__traceback__
    while exc_traceback.tb_next:
        exc_traceback = exc_traceback.tb_next
    # Get line number
    line_number = exc_traceback.tb_lineno
    print(f"An error occurred on line {line_number}:\n\n{e}")
    exit()


# Add/inject test code here
{{ TEST.testcode }}

# Recommendation: make students add all code inside functions, including main()
# this allows for you to call the code from the test on demand. If students add
# straight code, then it's executed before any of your test code.



SEPARATOR = "#<ab@17943918#@>#"


{% if not loop.last %}
print(SEPARATOR)
{% endif %}
{% endfor %}]]></template>
    <iscombinatortemplate>1</iscombinatortemplate>
    <allowmultiplestdins>0</allowmultiplestdins>
    <answer></answer>
    <validateonsave>1</validateonsave>
    <testsplitterre><![CDATA[|#<ab@17943918#@>#\n|ms]]></testsplitterre>
    <language>python3</language>
    <acelang></acelang>
    <sandbox></sandbox>
    <grader>NearEqualityGrader</grader>
    <cputimelimitsecs></cputimelimitsecs>
    <memlimitmb></memlimitmb>
    <sandboxparams></sandboxparams>
    <templateparams></templateparams>
    <hoisttemplateparams>1</hoisttemplateparams>
    <extractcodefromjson>1</extractcodefromjson>
    <templateparamslang>None</templateparamslang>
    <templateparamsevalpertry>0</templateparamsevalpertry>
    <templateparamsevald>{}</templateparamsevald>
    <twigall>0</twigall>
    <uiplugin>scratchpad</uiplugin>
    <uiparameters><![CDATA[{  
"wrapper_src": "prototypeextra",  
"output_display_mode": "json",  
"escape": true,  
"scratchpad_name":"Scratchpad",  
"button_name":"Run Code",  
"prefix_name":"Run Answer Code?",  
"help_text": "<i>Your work is <b>autosaved</b> every 20 seconds.</i><br><br><h4><u><b>Run Code Button</u></b></h4>You can press '<b>Run Code</b>' as often as you'd like for no penality. This will execute your code and show your output.<br><br><b><u>If the 'Run Answer Code?' box is:</b></u><br><b>Checked</b> = Run code in Answer Box<br><b>Unchecked</b> = Run code in Scratchpad<br><br><br><h4><u><b>Check Button</u></b></h4>Pressing '<b>Check</b>' will run prebuilt 'tests' on code in your Answer Box.<br><br>If <b>penalty regime</b> (shown just above the answer box) is <b><i>not zero 0 only</b></i>, then you could be <i>docked marks</i> if you press it. Ask if you're not sure!<br><br><br><h4><u><b>What Is Graded</u></b></h4>Only code in the answer box is graded. The scratchpad is basically 'scrap paper' and not part of your answer.<br><br><br><h4><u><b>Using The Scratchpad</h4></u></b>If you have the 'run answer code?' box checked it will actually run code from <i>both</i> boxes. It will run the answer code 1st, then whatever is in the scratchpad.<br><br>So if the box is checked, you'll want to <b><u>remove/comment out</u> anything in your scratchpad</b> so code in both boxes doesn't run together.<br><br>"  
}]]></uiparameters>
    <attachments>0</attachments>
    <attachmentsrequired>0</attachmentsrequired>
    <maxfilesize>10240</maxfilesize>
    <filenamesregex></filenamesregex>
    <filenamesexplain></filenamesexplain>
    <displayfeedback>1</displayfeedback>
    <giveupallowed>0</giveupallowed>
    <prototypeextra><![CDATA[import sys, io, subprocess, ast, traceback, json, locale, re

# Set the max allow input() phases allowed in the student code (be careful with more than 1)
max_allowed_inputs = 1

locale.setlocale(locale.LC_ALL, "C.UTF-8")
MAX_OUTPUT_CHARS = 30000

# Any pre-loaded code you want for scratchpad runs, enter it here
PRE_CODE = """

"""

# RANDOM SEED BASED ON CHARACTER LENGTH OF STUDENT CODE
seed_info_start = """import random
random.seed("""
seed_info_end = """)
"""
# Construct seeding code for precode injection
seed_code = seed_info_start + str(len(PRE_CODE)) + seed_info_end
# Count number of input()'s in student code
input_count = PRE_CODE.count("input(")    

# Precode for creating the Input Fields
ini_subproc_code = """
import sys
MAX_OUTPUT_CHARS = 30000
__saved_input__ = input

def input(prompt=''):
    try:
        line = __saved_input__()
    except EOFError:
        print(prompt, end = '')
        sys.stderr.flush()
        sys.stdout.flush()
        sys.exit(42)
    print(prompt, end='')
    print(line)
    return line

__saved_print__ = print
__output_chars__ = 0

def print(*params, **keyparams):
    global __output_chars__
    for param in params:
        try:
            __output_chars__ += len(str(param))
        except:
            pass
    if __output_chars__ > 2 * MAX_OUTPUT_CHARS:
        __saved_print__("\\\\n*** Excessive output. Job aborted ***", file=sys.stderr)
        sys.exit(1)
    else:
        __saved_print__(*params, **keyparams)
"""



# Add input precode and apply Random Seed based on character number if inputs are found
if input_count > max_allowed_inputs:
    subproc_code = seed_code + ini_subproc_code
else:
    subproc_code = ini_subproc_code


#### FULL Precode (does not include student code)
subproc_code += PRE_CODE

##### STUDENT Code Only 
og_student_code = """{| ANSWER_CODE |}
{| SCRATCHPAD_CODE |}
"""


## NOTE: Precode and Student Code Mixes as Needed Below

# Normalizes code lines numbers by removing precode injected line numbers from student code
def adjust_error_message(stderr, line_offset):
    # Adjust the line numbers in the traceback using a more precise regex
    def adjust_line_number(match):
        # Extract the current line number from the traceback
        line_number = int(match.group(1))
        # Adjust the line number by subtracting the offset
        return f'line {line_number - line_offset}'

    # Try to remove precode line numbers
    try:
        adjusted_stderr = re.sub(r'line (\d+)', adjust_line_number, stderr)
        return adjusted_stderr
    except:
        # If precode adjustment failed, return original numbers
        return "NOTE: Line numbers below are not correctly adjusted\n" + stderr


# Cut short any stdout or stderr output
def truncated(s):
    return s if len(s) < MAX_OUTPUT_CHARS else s[:MAX_OUTPUT_CHARS] + " ... (truncated)"

# Checks the syntax BEFORE executing.
def check_syntax(code_to_test):
    try:
        ast.parse(code_to_test)
        return ""
    except SyntaxError:
        catcher = io.StringIO()
        traceback.print_exc(limit=0, file=catcher)
        return catcher.getvalue()

# Prepare variables for analysis
stdout = ""
stderr = check_syntax(og_student_code)  # Check that og student code has no syntax errors

if stderr == "":  # No syntax errors, run code
    program_code = subproc_code + og_student_code
    with open("prog.py", "w", encoding="utf-8") as outfile:
        outfile.write(program_code)

    # Calculate the number of precode lines
    line_offset = subproc_code.count('\n')

    proc = subprocess.Popen(
        [sys.executable, "prog.py"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    try:
        stdout, stderr = proc.communicate(timeout=3)
        returncode = proc.returncode
    except subprocess.TimeoutExpired:
        # Run timed out. Maybe infinate loop?
        proc.kill()
        stdout, stderr = proc.communicate()
        returncode = 13

    # If an error was detected, try to adjust line numbers. If not, return original error
    if stderr:
        stderr = adjust_error_message(stderr, line_offset)


else:
    returncode = 1  # Syntax errors


# CodeRunner requires a JSON dump. Assemble it here.
output = {
    "returncode": returncode,
    "stdout": truncated(stdout),
    "stderr": truncated(stderr),
    "files": [],
}

# Dump for CodeRunner
print(json.dumps(output))
]]></prototypeextra>
    <testcases>
    </testcases>
  </question>

</quiz>