From f2dd9ed3555219876fa48a324313da0739d6fd41 Mon Sep 17 00:00:00 2001
From: Stefan Kairinos <118008817+SKairinos@users.noreply.github.com>
Date: Mon, 30 Oct 2023 18:47:40 +0000
Subject: [PATCH] feat: loop level links (#1507)
* quick save
* fix: next episode
* initial for each episode
* fix formatting
* episode 12
* episode 13
* episode 14
* episode 15
* episode 12
* setup for other episodes
* disconnect levels 122 and 123
* remove unused comments
* difficulty
* formatting
* undo formatting
* add bg for loops
* iterate level links
* add hints
* level 115 description
* remove extra the
* Merge branch 'master' into loop_level_links
* update migration name
---
game/messages.py | 501 +++++++++++++++++++++++
game/migrations/0086_loop_levels.py | 316 ++++++++++++++
game/models.py | 7 +-
game/static/game/css/backgrounds.css | 4 +
game/static/game/css/level_selection.css | 4 +
game/templates/game/level_selection.html | 119 ++++--
game/views/level_selection.py | 43 +-
7 files changed, 950 insertions(+), 44 deletions(-)
create mode 100644 game/migrations/0086_loop_levels.py
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 @@
- {{episode.name}} - - Levels {{episode.first_level}}-{{episode.last_level}} - - -
-+ {{episode.name}} + + Levels {{episode.first_level}}-{{episode.last_level}} + + +
+- {{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}} - +
+ {{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 %} ++ {{episode.name}} + + Levels {{episode.first_level}}-{{episode.last_level}} + + +
++ {{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 %} +