diff --git a/game/messages.py b/game/messages.py index 60d4d5cc0..29934aafe 100644 --- a/game/messages.py +++ b/game/messages.py @@ -2357,6 +2357,503 @@ def hint_level109(): ) +# Episode 12, Levels 110 - 122 +( + title_level110, + description_level110, + hint_level110, +) = ( + lambda: "Here's Python", + lambda: build_description( + title_level110(), + "As you create your program using Blockly, see what it looks like in the Python programming language. Can you tell which Python statement matches which block?", + ), + lambda: "This is a deliberately simple level. What you need to focus on is the Python code that is being generated for the blocks you join.", +) + +( + title_level111, + description_level111, + hint_level111, +) = ( + lambda: "Matching Blockly", + lambda: build_description( + title_level111(), + "As you create your program using Blockly, see what it looks like in the Python programming language. Can you tell which Python statement matches which block?", + ), + lambda: "This is a deliberately simple level. What you need to focus on is the Python code that is being generated for the blocks you join.", +) + +( + title_level112, + description_level112, + hint_level112, +) = ( + lambda: "Don't forget to find the shortest route", + lambda: build_description( + title_level112(), + "As you create your program using Blockly, see what it looks like in the Python programming language. Can you tell which Python statement matches which block?" + "

" + "Don't forget to find the shortest route!", + ), + lambda: "Pay close attention to the spelling of the Python commands, the use of the dot and the round brackets.", +) + +( + title_level113, + description_level113, + hint_level113, +) = ( + lambda: "Where did the blocks go?", + lambda: build_description( + title_level113(), + "Can you remember how the Python was formed under the blocks in the previous levels? Take a look in the Py Commands for a quick reminder.", + ), + lambda: "Now it is your turn, you can click on the Py commands button for a reminder of the Python commands that you can use.", +) + +( + title_level114, + description_level114, + hint_level114, +) = ( + lambda: "Tree snake", + lambda: build_description( + title_level114(), + "This route is just a bit longer. Make sure you type the Python code accurately!", + ), + lambda: "The most common errors here are missing the round brackets at the end of the commands or mis-typing the commands, e.g. use my_van.move_fowards() and not my_van.move_foward()", +) + +( + title_level115, + description_level115, + hint_level115, +) = ( + lambda: "Which way to turn? This way or that way?", + lambda: build_description(title_level115(), "Try to find the shortest route using Python code only. You are getting really good at this!"), + lambda: "If you are stuck on this one, check that you are using the Python commands accurately. Click the Py Commands button to help you.", +) + +( + title_level116, + description_level116, + hint_level116, +) = ( + lambda: "In a while, crocodile!", + lambda: build_description( + title_level116(), + "This level may look easy, but this time you need to use a While Loop to deliver to the house." + "
" + "Can you do it? Name your variables wisely.", + ), + lambda: "Make sure that you are using the right blocks here. You must use the pink block for variables and the dark blue number block for values. Watch the video if you are unsure.", +) + +( + title_level117, + description_level117, + hint_level117, +) = ( + lambda: "Wiggle while you work", + lambda: build_description( + title_level117(), + "This time is's a little harder. We've added the directions back in." + "
" + "Can you put them in the right place?" + "

" + "Notice how the Python looks in the console.", + ), + lambda: "If you cannot solve this one, try creating a solution without a while loop and then looking for what is repeated...", +) + +( + title_level118, + description_level118, + hint_level118, +) = ( + lambda: "This way, that way, forwards...", + lambda: build_description( + title_level118(), + "Now all three directions are back!" + "
" + "Use what you have learned so far to get your delivery completed.", + ), + lambda: "You can solve this in stages. Try moving to the start of the repeated section, then adding the loop and then finishing it off.", +) + +( + title_level119, + description_level119, + hint_level119, +) = ( + lambda: "Four leaf clover", + lambda: build_description( + title_level119(), + "Now let's try what you've learned without blocks." + "
" + "Click on the Py Commands button to remind yourself of the syntax you need.", + ), + lambda: "If you cannot solve this one, try creating a solution without a while loop and then looking for what is repeated...", +) + +( + title_level120, + description_level120, + hint_level120, +) = ( + lambda: "Pond life", + lambda: build_description( + title_level120(), + "So many routes but only one that is efficient. Can you work out which one it is?", + ), + lambda: "What if you go down the middle section?", +) + +( + title_level121, + description_level121, + hint_level121, +) = ( + lambda: "Farmyard overdrive", + lambda: build_description( + title_level121(), + "Maybe including a loop is the answer here?", + ), + lambda: "Try that middle route...", +) + +( + title_level122, + description_level122, + hint_level122, +) = ( + lambda: "Snnnaaaake reflection", + lambda: build_description( + title_level122(), + "Can you see some patterns? Looks like those loops will come in handy again for this tricky route.", + ), + lambda: "Don't try to solve this in one go. Work out the pattern for the top route and check that works. Then move on to the next part...", +) + + +# Episode 13, Levels 123 - 140 +( + title_level123, + description_level123, + hint_level123, +) = ( + lambda: "TODO", + lambda: build_description(title_level123(), "TODO"), + lambda: "TODO", +) + +( + title_level124, + description_level124, + hint_level124, +) = ( + lambda: "TODO", + lambda: build_description(title_level124(), "TODO"), + lambda: "TODO", +) + +( + title_level125, + description_level125, + hint_level125, +) = ( + lambda: "TODO", + lambda: build_description(title_level125(), "TODO"), + lambda: "TODO", +) + +( + title_level126, + description_level126, + hint_level126, +) = ( + lambda: "TODO", + lambda: build_description(title_level126(), "TODO"), + lambda: "TODO", +) + +( + title_level127, + description_level127, + hint_level127, +) = ( + lambda: "TODO", + lambda: build_description(title_level127(), "TODO"), + lambda: "TODO", +) + +( + title_level128, + description_level128, + hint_level128, +) = ( + lambda: "TODO", + lambda: build_description(title_level128(), "TODO"), + lambda: "TODO", +) + +( + title_level129, + description_level129, + hint_level129, +) = ( + lambda: "TODO", + lambda: build_description(title_level129(), "TODO"), + lambda: "TODO", +) + +( + title_level130, + description_level130, + hint_level130, +) = ( + lambda: "TODO", + lambda: build_description(title_level130(), "TODO"), + lambda: "TODO", +) + +( + title_level131, + description_level131, + hint_level131, +) = ( + lambda: "TODO", + lambda: build_description(title_level131(), "TODO"), + lambda: "TODO", +) + +( + title_level132, + description_level132, + hint_level132, +) = ( + lambda: "TODO", + lambda: build_description(title_level132(), "TODO"), + lambda: "TODO", +) + +( + title_level133, + description_level133, + hint_level133, +) = ( + lambda: "TODO", + lambda: build_description(title_level133(), "TODO"), + lambda: "TODO", +) + +( + title_level134, + description_level134, + hint_level134, +) = ( + lambda: "TODO", + lambda: build_description(title_level134(), "TODO"), + lambda: "TODO", +) + +( + title_level135, + description_level135, + hint_level135, +) = ( + lambda: "TODO", + lambda: build_description(title_level135(), "TODO"), + lambda: "TODO", +) + +( + title_level136, + description_level136, + hint_level136, +) = ( + lambda: "TODO", + lambda: build_description(title_level136(), "TODO"), + lambda: "TODO", +) + +( + title_level137, + description_level137, + hint_level137, +) = ( + lambda: "TODO", + lambda: build_description(title_level137(), "TODO"), + lambda: "TODO", +) + +( + title_level138, + description_level138, + hint_level138, +) = ( + lambda: "TODO", + lambda: build_description(title_level138(), "TODO"), + lambda: "TODO", +) + +( + title_level139, + description_level139, + hint_level139, +) = ( + lambda: "TODO", + lambda: build_description(title_level139(), "TODO"), + lambda: "TODO", +) + +( + title_level140, + description_level140, + hint_level140, +) = ( + lambda: "TODO", + lambda: build_description(title_level140(), "TODO"), + lambda: "TODO", +) + + +# Episode 14, Levels 141 - 148 +( + title_level141, + description_level141, + hint_level141, +) = ( + lambda: "TODO", + lambda: build_description(title_level141(), "TODO"), + lambda: "TODO", +) + +( + title_level142, + description_level142, + hint_level142, +) = ( + lambda: "TODO", + lambda: build_description(title_level142(), "TODO"), + lambda: "TODO", +) + +( + title_level143, + description_level143, + hint_level143, +) = ( + lambda: "TODO", + lambda: build_description(title_level143(), "TODO"), + lambda: "TODO", +) + +( + title_level144, + description_level144, + hint_level144, +) = ( + lambda: "TODO", + lambda: build_description(title_level144(), "TODO"), + lambda: "TODO", +) + +( + title_level145, + description_level145, + hint_level145, +) = ( + lambda: "TODO", + lambda: build_description(title_level145(), "TODO"), + lambda: "TODO", +) + +( + title_level146, + description_level146, + hint_level146, +) = ( + lambda: "TODO", + lambda: build_description(title_level146(), "TODO"), + lambda: "TODO", +) + +( + title_level147, + description_level147, + hint_level147, +) = ( + lambda: "TODO", + lambda: build_description(title_level147(), "TODO"), + lambda: "TODO", +) + +( + title_level148, + description_level148, + hint_level148, +) = ( + lambda: "TODO", + lambda: build_description(title_level148(), "TODO"), + lambda: "TODO", +) + + +# Episode 15, Levels 149 - 153 +( + title_level149, + description_level149, + hint_level149, +) = ( + lambda: "TODO", + lambda: build_description(title_level149(), "TODO"), + lambda: "TODO", +) + +( + title_level150, + description_level150, + hint_level150, +) = ( + lambda: "TODO", + lambda: build_description(title_level150(), "TODO"), + lambda: "TODO", +) + +( + title_level151, + description_level151, + hint_level151, +) = ( + lambda: "TODO", + lambda: build_description(title_level151(), "TODO"), + lambda: "TODO", +) + +( + title_level152, + description_level152, + hint_level152, +) = ( + lambda: "TODO", + lambda: build_description(title_level152(), "TODO"), + lambda: "TODO", +) + +( + title_level153, + description_level153, + hint_level153, +) = ( + lambda: "TODO", + lambda: build_description(title_level153(), "TODO"), + lambda: "TODO", +) + + +# Episode titles def get_episode_title(episode_id): episode_titles = { 1: "Getting Started", @@ -2370,6 +2867,10 @@ def get_episode_title(episode_id): 9: "Blockly Brain Teasers", 10: "Introduction to Python", 11: "Python", + 12: "Sequencing and Counted Loops", + 13: "Indeterminate WHILE Loops - coming soon", + 14: "Selection in a Loop - coming soon", + 15: "For Loops - coming soon", } return episode_titles[episode_id] diff --git a/game/migrations/0086_loop_levels.py b/game/migrations/0086_loop_levels.py new file mode 100644 index 000000000..300c86c54 --- /dev/null +++ b/game/migrations/0086_loop_levels.py @@ -0,0 +1,316 @@ +from django.apps.registry import Apps +from django.db import migrations + + +def add_loop_levels(apps: Apps, *args): + Level = apps.get_model("game", "Level") + Episode = apps.get_model("game", "Episode") + + episode_15 = Episode.objects.create( + pk=15, + name="For Loops - coming soon", + ) + episode_14 = Episode.objects.create( + pk=14, + next_episode=episode_15, + name="Selection in a Loop - coming soon", + ) + episode_13 = Episode.objects.create( + pk=13, + next_episode=episode_14, + name="Indeterminate WHILE Loops - coming soon", + ) + episode_12 = Episode.objects.create( + pk=12, + next_episode=episode_13, + name="Sequencing and Counted Loops", + ) + + episode_11 = Episode.objects.get(pk=11) + episode_11.next_episode = episode_12 + episode_11.save() + + # Episode 15, Levels 149 - 153 + level_153 = Level.objects.create( + name="153", + episode=episode_15, + path="", + ) + level_152 = Level.objects.create( + name="152", + episode=episode_15, + path="", + next_level=level_153, + ) + level_151 = Level.objects.create( + name="151", + episode=episode_15, + path="", + next_level=level_152, + ) + level_150 = Level.objects.create( + name="150", + episode=episode_15, + path="", + next_level=level_151, + ) + level_149 = Level.objects.create( + name="149", + episode=episode_15, + path="", + next_level=level_150, + ) + + # Episode 14, Levels 141 - 148 + level_148 = Level.objects.create( + name="148", + episode=episode_14, + path="", + next_level=level_149, + ) + level_147 = Level.objects.create( + name="147", + episode=episode_14, + path="", + next_level=level_148, + ) + level_146 = Level.objects.create( + name="146", + episode=episode_14, + path="", + next_level=level_147, + ) + level_145 = Level.objects.create( + name="145", + episode=episode_14, + path="", + next_level=level_146, + ) + level_144 = Level.objects.create( + name="144", + episode=episode_14, + path="", + next_level=level_145, + ) + level_143 = Level.objects.create( + name="143", + episode=episode_14, + path="", + next_level=level_144, + ) + level_142 = Level.objects.create( + name="142", + episode=episode_14, + path="", + next_level=level_143, + ) + level_141 = Level.objects.create( + name="141", + episode=episode_14, + path="", + next_level=level_142, + ) + + # Episode 13, Levels 123 - 140 + level_140 = Level.objects.create( + name="140", + episode=episode_13, + path="", + next_level=level_141, + ) + level_139 = Level.objects.create( + name="139", + episode=episode_13, + path="", + next_level=level_140, + ) + level_138 = Level.objects.create( + name="138", + episode=episode_13, + path="", + next_level=level_139, + ) + level_137 = Level.objects.create( + name="137", + episode=episode_13, + path="", + next_level=level_138, + ) + level_136 = Level.objects.create( + name="136", + episode=episode_13, + path="", + next_level=level_137, + ) + level_135 = Level.objects.create( + name="135", + episode=episode_13, + path="", + next_level=level_136, + ) + level_134 = Level.objects.create( + name="134", + episode=episode_13, + path="", + next_level=level_135, + ) + level_133 = Level.objects.create( + name="133", + episode=episode_13, + path="", + next_level=level_134, + ) + level_132 = Level.objects.create( + name="132", + episode=episode_13, + path="", + next_level=level_133, + ) + level_131 = Level.objects.create( + name="131", + episode=episode_13, + path="", + next_level=level_132, + ) + level_130 = Level.objects.create( + name="130", + episode=episode_13, + path="", + next_level=level_131, + ) + level_129 = Level.objects.create( + name="129", + episode=episode_13, + path="", + next_level=level_130, + ) + level_128 = Level.objects.create( + name="128", + episode=episode_13, + path="", + next_level=level_129, + ) + level_127 = Level.objects.create( + name="127", + episode=episode_13, + path="", + next_level=level_128, + ) + level_126 = Level.objects.create( + name="126", + episode=episode_13, + path="", + next_level=level_127, + ) + level_125 = Level.objects.create( + name="125", + episode=episode_13, + path="", + next_level=level_126, + ) + level_124 = Level.objects.create( + name="124", + episode=episode_13, + path="", + next_level=level_125, + ) + level_123 = Level.objects.create( + name="123", + episode=episode_13, + path="", + next_level=level_124, + ) + + # Episode 12, Levels 110 - 122 + level_122 = Level.objects.create( + name="122", + episode=episode_12, + path="", + # next_level=level_123, TODO: connect them when the next levels are enabled. + ) + level_121 = Level.objects.create( + name="121", + episode=episode_12, + path="", + next_level=level_122, + ) + level_120 = Level.objects.create( + name="120", + episode=episode_12, + path="", + next_level=level_121, + ) + level_119 = Level.objects.create( + name="119", + episode=episode_12, + path="", + next_level=level_120, + ) + level_118 = Level.objects.create( + name="118", + episode=episode_12, + path="", + next_level=level_119, + ) + level_117 = Level.objects.create( + name="117", + episode=episode_12, + path="", + next_level=level_118, + ) + level_116 = Level.objects.create( + name="116", + episode=episode_12, + path="", + next_level=level_117, + ) + level_115 = Level.objects.create( + name="115", + episode=episode_12, + path="", + next_level=level_116, + ) + level_114 = Level.objects.create( + name="114", + episode=episode_12, + path="", + next_level=level_115, + ) + level_113 = Level.objects.create( + name="113", + episode=episode_12, + path="", + next_level=level_114, + ) + level_112 = Level.objects.create( + name="112", + episode=episode_12, + path="", + next_level=level_113, + ) + level_111 = Level.objects.create( + name="111", + episode=episode_12, + path="", + next_level=level_112, + ) + level_110 = Level.objects.create( + name="110", + episode=episode_12, + path="", + next_level=level_111, + ) + + level_109 = Level.objects.get(name="109") + level_109.next_level = level_110 + level_109.save() + + +class Migration(migrations.Migration): + dependencies = [("game", "0085_add_new_blocks")] + operations = [ + migrations.RunPython( + add_loop_levels, + reverse_code=migrations.RunPython.noop, + ) + ] diff --git a/game/models.py b/game/models.py index e88a88018..65c1edae7 100644 --- a/game/models.py +++ b/game/models.py @@ -1,6 +1,6 @@ from builtins import str -from common.models import UserProfile, Student, Class +from common.models import Class, Student, UserProfile from django.contrib.auth.models import User from django.db import models @@ -37,6 +37,7 @@ def __str__(self): class Meta: ordering = ["block_type", "pk"] + class Episode(models.Model): """Variables prefixed with r_ signify they are parameters for random level generation""" @@ -85,6 +86,10 @@ def difficulty(self): 9: "brainteasers", 10: "hard", 11: "advanced", + 12: "loops", + 13: "loops", + 14: "loops", + 15: "loops", } return difficulty_map.get(self.id, "easy") diff --git a/game/static/game/css/backgrounds.css b/game/static/game/css/backgrounds.css index 4d6b004e4..05c3c315b 100644 --- a/game/static/game/css/backgrounds.css +++ b/game/static/game/css/backgrounds.css @@ -27,3 +27,7 @@ .bg--shared-levels { background: #f6be00; } + +.bg--loops { + background: #3F3F3F; +} diff --git a/game/static/game/css/level_selection.css b/game/static/game/css/level_selection.css index 1762dfdae..62e11de1f 100644 --- a/game/static/game/css/level_selection.css +++ b/game/static/game/css/level_selection.css @@ -2,6 +2,10 @@ margin: 30px 0 20px 0; } +#episodes .panel-subintro { + margin: 20px 0 20px 0; +} + #episodes .panel-header { color: #fff; margin-bottom: 0; diff --git a/game/templates/game/level_selection.html b/game/templates/game/level_selection.html index f3ca0888f..a449fafad 100644 --- a/game/templates/game/level_selection.html +++ b/game/templates/game/level_selection.html @@ -128,43 +128,94 @@

Python levels

here. {% for episode in pythonEpisodes %} -
-
- -
+ {% if episode.difficulty != "loops" %} +
+
+ +
-
-
- {% for level in episode.levels %} - {% if user|is_logged_in_as_student and user.new_student.class_field in level.locked_for_class.all %} - -

- {{level.name}}: {{level.title.strip | safe}} - {% if level.score != None %} - {{level.score|floatformat}}/{{level.maxScore|floatformat}} - - {% endif %} -

- {% else %} - -

- {{level.name}}: {{level.title.strip | safe}} - {% if level.score != None %} - {{level.score|floatformat}}/{{level.maxScore|floatformat}} - +

+
+ {% for level in episode.levels %} + {% if user|is_logged_in_as_student and user.new_student.class_field in level.locked_for_class.all %} + +

+ {{level.name}}: {{level.title.strip | safe}} + {% if level.score != None %} + {{level.score|floatformat}}/{{level.maxScore|floatformat}} + + {% endif %} +

+ {% else %} + +

+ {{level.name}}: {{level.title.strip | safe}} + {% if level.score != None %} + {{level.score|floatformat}}/{{level.maxScore|floatformat}} + + {% endif %} +

{% endif %} -

- {% endif %} - {% endfor %} + {% endfor %} +
+
-
+ {% endif %} + {% endfor %} +
+ The following levels focus on Loops using Blockly and Python.
+ {% for episode in pythonEpisodes %} + {% if episode.difficulty == "loops" %} +
+
+ +
+ +
+
+ {% for level in episode.levels %} + {% if user|is_logged_in_as_student and user.new_student.class_field in level.locked_for_class.all %} + +

+ {{level.name}}: {{level.title.strip | safe}} + {% if level.score != None %} + {{level.score|floatformat}}/{{level.maxScore|floatformat}} + + {% endif %} +

+ {% else %} + +

+ {{level.name}}: {{level.title.strip | safe}} + {% if level.score != None %} + {{level.score|floatformat}}/{{level.maxScore|floatformat}} + + {% endif %} +

+ {% endif %} + {% endfor %} +
+
+
+ {% endif %} {% endfor %} {% if user|is_logged_in %} @@ -256,4 +307,4 @@
Owned by {{ teacher|make_into_username }}
{% endif %}
-{% endblock content %} +{% endblock content %} \ No newline at end of file diff --git a/game/views/level_selection.py b/game/views/level_selection.py index d2c9c2e59..4d487cc8a 100644 --- a/game/views/level_selection.py +++ b/game/views/level_selection.py @@ -12,6 +12,7 @@ from game import app_settings, random_road from game.cache import cached_episode from game.models import Attempt, Episode, Level + from .level_editor import play_anonymous_level @@ -43,7 +44,12 @@ def fetch_episode_data_from_database(early_access, start, end): minName = level_name levels.append( - {"id": level.id, "name": level_name, "maxScore": max_score(level), "title": get_level_title(level_name)} + { + "id": level.id, + "name": level_name, + "maxScore": max_score(level), + "title": get_level_title(level_name), + } ) e = { @@ -78,7 +84,10 @@ def fetch_episode_data(early_access, start=1, end=12): dict( episode, name=messages.get_episode_title(episode["id"]), - levels=[dict(level, title=get_level_title(level["name"])) for level in episode["levels"]], + levels=[ + dict(level, title=get_level_title(level["name"])) + for level in episode["levels"] + ], ) for episode in data ] @@ -114,7 +123,8 @@ def get_blockly_episodes(request): def get_python_episodes(request): - return fetch_episode_data(app_settings.EARLY_ACCESS_FUNCTION(request), 10, 11) + return fetch_episode_data(app_settings.EARLY_ACCESS_FUNCTION(request), 10, 15) + def levels(request): """Loads a page with all levels listed. @@ -145,13 +155,17 @@ def levels(request): for episode in blockly_episodes: for level in episode["levels"]: attach_attempts_to_level(attempts, level) - level["locked_for_class"] = Level.objects.get(id=level["id"]).locked_for_class + level["locked_for_class"] = Level.objects.get( + id=level["id"] + ).locked_for_class python_episodes = get_python_episodes(request) for episode in python_episodes: for level in episode["levels"]: attach_attempts_to_level(attempts, level) - level["locked_for_class"] = Level.objects.get(id=level["id"]).locked_for_class + level["locked_for_class"] = Level.objects.get( + id=level["id"] + ).locked_for_class owned_level_data = [] directly_shared_levels = [] @@ -161,7 +175,12 @@ def levels(request): for level in owned_levels: owned_level_data.append( - {"id": level.id, "title": level.name, "score": attempts.get(level.id), "maxScore": 10} + { + "id": level.id, + "title": level.name, + "score": attempts.get(level.id), + "maxScore": 10, + } ) for level in shared_levels: @@ -176,20 +195,26 @@ def levels(request): if teacher not in indirectly_shared_levels: indirectly_shared_levels[teacher] = [] - indirectly_shared_levels[teacher].append(get_shared_level(level, attempts)) + indirectly_shared_levels[teacher].append( + get_shared_level(level, attempts) + ) else: student_class = level.owner.student.class_field class_teacher = student_class.teacher.new_user # get levels shared by students in the current user's classes if class_teacher == user: - directly_shared_levels.append(get_shared_level(level, attempts, student_class)) + directly_shared_levels.append( + get_shared_level(level, attempts, student_class) + ) # get levels shared by students in the other teachers' classes else: if class_teacher not in indirectly_shared_levels: indirectly_shared_levels[class_teacher] = [] - indirectly_shared_levels[class_teacher].append(get_shared_level(level, attempts, student_class)) + indirectly_shared_levels[class_teacher].append( + get_shared_level(level, attempts, student_class) + ) # if user is a student or a standard teacher, just get levels shared with them directly. else: directly_shared_levels.append(get_shared_level(level, attempts))