rapid-router 5.4.1__py2.py3-none-any.whl → 7.6.8__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. example_project/rapid_router_test_settings.py +164 -0
  2. example_project/settings.py +152 -0
  3. example_project/urls.py +15 -0
  4. example_project/{example_project/wsgi.py → wsgi.py} +2 -1
  5. game/__init__.py +1 -1
  6. game/admin.py +43 -4
  7. game/app_settings.py +3 -7
  8. game/character.py +26 -18
  9. game/decor.py +172 -97
  10. game/end_to_end_tests/base_game_test.py +44 -33
  11. game/end_to_end_tests/editor_page.py +17 -2
  12. game/end_to_end_tests/game_page.py +127 -45
  13. game/end_to_end_tests/selenium_test_case.py +1 -20
  14. game/end_to_end_tests/test_cow_crashes.py +3 -5
  15. game/end_to_end_tests/test_language_dropdown.py +14 -0
  16. game/end_to_end_tests/test_level_editor.py +290 -0
  17. game/end_to_end_tests/test_level_failures.py +1 -1
  18. game/end_to_end_tests/test_level_selection.py +79 -0
  19. game/end_to_end_tests/test_play_through.py +240 -102
  20. game/end_to_end_tests/test_python_levels.py +44 -13
  21. game/end_to_end_tests/test_saving_workspace.py +22 -0
  22. game/forms.py +9 -2
  23. game/level_management.py +38 -29
  24. game/messages.py +1218 -203
  25. game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
  26. game/migrations/0026_levels_pt2.py +13 -2
  27. game/migrations/0032_cannot_turn_left_level.py +13 -2
  28. game/migrations/0033_recursion_level.py +13 -2
  29. game/migrations/0034_joes_level.py +13 -2
  30. game/migrations/0035_disable_route_score_level_70.py +0 -2
  31. game/migrations/0036_level_score_73.py +0 -2
  32. game/migrations/0037_level_score_79.py +0 -2
  33. game/migrations/0038_level_score_40.py +0 -1
  34. game/migrations/0042_level_score_73.py +0 -2
  35. game/migrations/0048_add_cow_field_and_blocks.py +0 -2
  36. game/migrations/0049_level_score_34.py +0 -2
  37. game/migrations/0050_level_score_40.py +0 -2
  38. game/migrations/0051_level_score_49.py +0 -1
  39. game/migrations/0076_level_locked_for_class.py +19 -0
  40. game/migrations/0077_alter_level_next_level.py +52 -0
  41. game/migrations/0078_add_block_types.py +23 -0
  42. game/migrations/0079_populate_block_type_add_cow_blocks.py +60 -0
  43. game/migrations/0080_level_disable_algorithm_score.py +18 -0
  44. game/migrations/0081_first_12_levels_no_algo_score.py +29 -0
  45. game/migrations/0082_level_43_solution.py +16 -0
  46. game/migrations/0083_add_cows_to_existing_levels.py +195 -0
  47. game/migrations/0084_alter_block_block_type.py +18 -0
  48. game/migrations/0085_add_new_blocks.py +53 -0
  49. game/migrations/0086_loop_levels.py +482 -0
  50. game/migrations/0087_workspace_python_view_enabled.py +18 -0
  51. game/migrations/0088_rename_episodes.py +35 -0
  52. game/migrations/0089_episodes_in_development.py +30 -0
  53. game/migrations/0090_add_missing_model_solutions.py +144 -0
  54. game/migrations/0091_disable_algo_score_if_no_model_solution.py +46 -0
  55. game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
  56. game/migrations/0093_alter_level_character_name.py +18 -0
  57. game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
  58. game/migrations/0095_level_commands.py +18 -0
  59. game/migrations/0096_alter_level_commands.py +18 -0
  60. game/migrations/0097_add_python_den_levels.py +1515 -0
  61. game/migrations/0098_add_episode_link_fields.py +44 -0
  62. game/migrations/0099_python_episodes_links.py +103 -0
  63. game/migrations/0100_reorder_python_levels.py +179 -0
  64. game/migrations/0101_rename_episodes.py +45 -0
  65. game/migrations/0102_reoder_episodes_13_14.py +136 -0
  66. game/migrations/0103_level_1015_solution.py +26 -0
  67. game/migrations/0104_remove_level_direct_drive.py +17 -0
  68. game/migrations/0105_delete_invalid_attempts.py +18 -0
  69. game/migrations/0106_fields_to_snake_case.py +48 -0
  70. game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
  71. game/migrations/0108_episode_indy_worksheet_link.py +18 -0
  72. game/migrations/0109_create_episodes_23_and_24.py +99 -0
  73. game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
  74. game/migrations/0111_create_worksheets.py +149 -0
  75. game/migrations/0112_worksheet_locked_classes.py +21 -0
  76. game/migrations/0113_level_needs_approval.py +18 -0
  77. game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
  78. game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
  79. game/migrations/0116_update_worksheet_video_links.py +68 -0
  80. game/migrations/0117_update_solutions_to_if_else.py +61 -0
  81. game/models.py +157 -16
  82. game/permissions.py +34 -19
  83. game/python_den_urls.py +26 -0
  84. game/random_road.py +43 -127
  85. game/serializers.py +12 -17
  86. game/static/django_reverse_js/js/reverse.js +171 -0
  87. game/static/game/css/LilitaOne-Regular.ttf +0 -0
  88. game/static/game/css/backgrounds.css +14 -10
  89. game/static/game/css/dataTables.custom.css +4 -2
  90. game/static/game/css/dataTables.jqueryui.css +561 -320
  91. game/static/game/css/editor.css +47 -0
  92. game/static/game/css/game.css +43 -49
  93. game/static/game/css/game_screen.css +116 -53
  94. game/static/game/css/jquery.dataTables.css +455 -251
  95. game/static/game/css/level_editor.css +10 -1
  96. game/static/game/css/level_selection.css +32 -3
  97. game/static/game/css/level_share.css +6 -5
  98. game/static/game/css/skulpt/codemirror.css +1 -0
  99. game/static/game/image/Python_Den_hero_student.png +0 -0
  100. game/static/game/image/Python_levels_page.svg +1954 -0
  101. game/static/game/image/characters/front_view/Electric_van.svg +448 -0
  102. game/static/game/image/characters/top_view/Electric_van.svg +448 -0
  103. game/static/game/image/characters/top_view/Sleigh.svg +436 -0
  104. game/static/game/image/decor/city/solar_panel.svg +1200 -0
  105. game/static/game/image/decor/farm/solar_panel.svg +86 -0
  106. game/static/game/image/decor/grass/solar_panel.svg +86 -0
  107. game/static/game/image/decor/snow/barn.svg +1788 -0
  108. game/static/game/image/decor/snow/cfc.svg +1050 -147
  109. game/static/game/image/decor/snow/crops.svg +7370 -0
  110. game/static/game/image/decor/snow/hospital.svg +1220 -0
  111. game/static/game/image/decor/snow/house1.svg +971 -0
  112. game/static/game/image/decor/snow/house2.svg +1574 -0
  113. game/static/game/image/decor/snow/school.svg +1071 -0
  114. game/static/game/image/decor/snow/shop.svg +3211 -0
  115. game/static/game/image/decor/snow/solar_panel.svg +173 -0
  116. game/static/game/image/electric_van.svg +448 -0
  117. game/static/game/image/icons/add_house.svg +26 -0
  118. game/static/game/image/icons/delete_house.svg +26 -0
  119. game/static/game/image/icons/description.svg +1 -0
  120. game/static/game/image/icons/hint.svg +1 -0
  121. game/static/game/image/icons/if_else.svg +3 -0
  122. game/static/game/image/icons/python.svg +1 -1
  123. game/static/game/image/if_else_example.png +0 -0
  124. game/static/game/image/pigeon.svg +684 -0
  125. game/static/game/image/python_den_header.svg +19 -0
  126. game/static/game/js/animation.js +84 -28
  127. game/static/game/js/blockly/msg/js/bg.js +52 -1
  128. game/static/game/js/blockly/msg/js/ca.js +52 -1
  129. game/static/game/js/blockly/msg/js/en-gb.js +52 -1
  130. game/static/game/js/blockly/msg/js/en.js +52 -1
  131. game/static/game/js/blockly/msg/js/es.js +52 -1
  132. game/static/game/js/blockly/msg/js/fr.js +52 -1
  133. game/static/game/js/blockly/msg/js/hi.js +52 -1
  134. game/static/game/js/blockly/msg/js/it.js +52 -1
  135. game/static/game/js/blockly/msg/js/pl.js +52 -1
  136. game/static/game/js/blockly/msg/js/pt-br.js +52 -1
  137. game/static/game/js/blockly/msg/js/ru.js +52 -1
  138. game/static/game/js/blockly/msg/js/ur.js +52 -1
  139. game/static/game/js/blocklyCompiler.js +550 -392
  140. game/static/game/js/blocklyControl.js +335 -302
  141. game/static/game/js/blocklyCustomBlocks.js +691 -458
  142. game/static/game/js/blocklyCustomisations.js +3 -1
  143. game/static/game/js/button.js +12 -0
  144. game/static/game/js/cow.js +15 -130
  145. game/static/game/js/drawing.js +313 -201
  146. game/static/game/js/editor.js +23 -0
  147. game/static/game/js/game.js +148 -139
  148. game/static/game/js/jquery.dataTables.min.js +3 -159
  149. game/static/game/js/level_editor.js +823 -448
  150. game/static/game/js/level_moderation.js +33 -2
  151. game/static/game/js/level_selection.js +62 -25
  152. game/static/game/js/loadLanguages.js +21 -0
  153. game/static/game/js/map.js +106 -36
  154. game/static/game/js/model.js +55 -107
  155. game/static/game/js/pathFinder.js +73 -72
  156. game/static/game/js/program.js +184 -193
  157. game/static/game/js/pythonControl.js +14 -1
  158. game/static/game/js/scoreboard.js +0 -37
  159. game/static/game/js/scoreboardSharedLevels.js +48 -0
  160. game/static/game/js/sharing.js +22 -10
  161. game/static/game/js/skulpt/codemirror.js +5 -4
  162. game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
  163. game/static/game/js/sound.js +52 -5
  164. game/static/game/js/van.js +0 -7
  165. game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
  166. game/static/game/raphael_image/characters/top_view/Sleigh.svg +436 -0
  167. game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
  168. game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
  169. game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
  170. game/static/game/raphael_image/decor/snow/barn.svg +1788 -0
  171. game/static/game/raphael_image/decor/snow/cfc.svg +1050 -147
  172. game/static/game/raphael_image/decor/snow/crops.svg +7370 -0
  173. game/static/game/raphael_image/decor/snow/hospital.svg +1220 -0
  174. game/static/game/raphael_image/decor/snow/house1.svg +971 -0
  175. game/static/game/raphael_image/decor/snow/house2.svg +1574 -0
  176. game/static/game/raphael_image/decor/snow/school.svg +1071 -0
  177. game/static/game/raphael_image/decor/snow/shop.svg +3211 -0
  178. game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
  179. game/static/game/raphael_image/pigeon.svg +685 -0
  180. game/static/game/raphael_image/sleigh_wreckage.svg +430 -0
  181. game/static/game/sass/game.scss +22 -6
  182. game/static/game/sound/clown_horn.mp3 +0 -0
  183. game/static/game/sound/clown_horn.ogg +0 -0
  184. game/static/game/sound/electric_van_starting.mp3 +0 -0
  185. game/static/game/sound/electric_van_starting.ogg +0 -0
  186. game/static/game/sound/pigeon.mp3 +0 -0
  187. game/static/game/sound/pigeon.ogg +0 -0
  188. game/static/game/sound/sleigh_bells.mp3 +0 -0
  189. game/static/game/sound/sleigh_bells.ogg +0 -0
  190. game/static/game/sound/sleigh_crash.mp3 +0 -0
  191. game/static/game/sound/sleigh_crash.ogg +0 -0
  192. game/templates/game/base.html +35 -15
  193. game/templates/game/basenonav.html +23 -17
  194. game/templates/game/game.html +236 -111
  195. game/templates/game/level_editor.html +353 -275
  196. game/templates/game/level_moderation.html +19 -6
  197. game/templates/game/level_selection.html +75 -62
  198. game/templates/game/python_den_level_selection.html +291 -0
  199. game/templates/game/python_den_worksheet.html +101 -0
  200. game/templates/game/scoreboard.html +88 -65
  201. game/tests/test_level_editor.py +210 -35
  202. game/tests/test_level_moderation.py +6 -20
  203. game/tests/test_level_selection.py +332 -11
  204. game/tests/test_python_den_worksheet.py +85 -0
  205. game/tests/test_scoreboard.py +258 -66
  206. game/tests/utils/level.py +43 -3
  207. game/tests/utils/teacher.py +2 -2
  208. game/theme.py +21 -21
  209. game/urls.py +125 -78
  210. game/views/language_code_conversions.py +90 -0
  211. game/views/level.py +201 -63
  212. game/views/level_editor.py +109 -48
  213. game/views/level_moderation.py +29 -6
  214. game/views/level_selection.py +179 -56
  215. game/views/level_solutions.py +600 -106
  216. game/views/scoreboard.py +181 -66
  217. game/views/worksheet.py +25 -0
  218. rapid_router-7.6.8.dist-info/METADATA +174 -0
  219. {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/RECORD +222 -242
  220. {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
  221. rapid_router-7.6.8.dist-info/licenses/LICENSE.md +3 -0
  222. example_project/example_project/__init__.py +0 -1
  223. example_project/example_project/settings.py +0 -54
  224. example_project/example_project/urls.py +0 -16
  225. example_project/manage.py +0 -10
  226. game/autoconfig.py +0 -59
  227. game/csp_config.py +0 -23
  228. game/locale/ar_SA/LC_MESSAGES/django.mo +0 -0
  229. game/locale/ar_SA/LC_MESSAGES/django.po +0 -405
  230. game/locale/ar_SA/LC_MESSAGES/djangojs.mo +0 -0
  231. game/locale/ar_SA/LC_MESSAGES/djangojs.po +0 -743
  232. game/locale/bg_BG/LC_MESSAGES/django.mo +0 -0
  233. game/locale/bg_BG/LC_MESSAGES/django.po +0 -405
  234. game/locale/bg_BG/LC_MESSAGES/djangojs.mo +0 -0
  235. game/locale/bg_BG/LC_MESSAGES/djangojs.po +0 -739
  236. game/locale/ca_ES/LC_MESSAGES/django.mo +0 -0
  237. game/locale/ca_ES/LC_MESSAGES/django.po +0 -405
  238. game/locale/ca_ES/LC_MESSAGES/djangojs.mo +0 -0
  239. game/locale/ca_ES/LC_MESSAGES/djangojs.po +0 -740
  240. game/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  241. game/locale/cs_CZ/LC_MESSAGES/django.po +0 -405
  242. game/locale/cs_CZ/LC_MESSAGES/djangojs.mo +0 -0
  243. game/locale/cs_CZ/LC_MESSAGES/djangojs.po +0 -741
  244. game/locale/cy_GB/LC_MESSAGES/django.mo +0 -0
  245. game/locale/cy_GB/LC_MESSAGES/django.po +0 -405
  246. game/locale/cy_GB/LC_MESSAGES/djangojs.mo +0 -0
  247. game/locale/cy_GB/LC_MESSAGES/djangojs.po +0 -743
  248. game/locale/de_DE/LC_MESSAGES/django.mo +0 -0
  249. game/locale/de_DE/LC_MESSAGES/django.po +0 -405
  250. game/locale/de_DE/LC_MESSAGES/djangojs.mo +0 -0
  251. game/locale/de_DE/LC_MESSAGES/djangojs.po +0 -739
  252. game/locale/el_GR/LC_MESSAGES/django.mo +0 -0
  253. game/locale/el_GR/LC_MESSAGES/django.po +0 -405
  254. game/locale/el_GR/LC_MESSAGES/djangojs.mo +0 -0
  255. game/locale/el_GR/LC_MESSAGES/djangojs.po +0 -739
  256. game/locale/en_GB/LC_MESSAGES/django.mo +0 -0
  257. game/locale/en_GB/LC_MESSAGES/django.po +0 -405
  258. game/locale/en_GB/LC_MESSAGES/djangojs.mo +0 -0
  259. game/locale/en_GB/LC_MESSAGES/djangojs.po +0 -739
  260. game/locale/es_ES/LC_MESSAGES/django.mo +0 -0
  261. game/locale/es_ES/LC_MESSAGES/django.po +0 -405
  262. game/locale/es_ES/LC_MESSAGES/djangojs.mo +0 -0
  263. game/locale/es_ES/LC_MESSAGES/djangojs.po +0 -739
  264. game/locale/fi_FI/LC_MESSAGES/django.mo +0 -0
  265. game/locale/fi_FI/LC_MESSAGES/django.po +0 -405
  266. game/locale/fi_FI/LC_MESSAGES/djangojs.mo +0 -0
  267. game/locale/fi_FI/LC_MESSAGES/djangojs.po +0 -739
  268. game/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  269. game/locale/fr_FR/LC_MESSAGES/django.po +0 -405
  270. game/locale/fr_FR/LC_MESSAGES/djangojs.mo +0 -0
  271. game/locale/fr_FR/LC_MESSAGES/djangojs.po +0 -739
  272. game/locale/gu_IN/LC_MESSAGES/django.mo +0 -0
  273. game/locale/gu_IN/LC_MESSAGES/django.po +0 -405
  274. game/locale/gu_IN/LC_MESSAGES/djangojs.mo +0 -0
  275. game/locale/gu_IN/LC_MESSAGES/djangojs.po +0 -739
  276. game/locale/hi_IN/LC_MESSAGES/django.mo +0 -0
  277. game/locale/hi_IN/LC_MESSAGES/django.po +0 -405
  278. game/locale/hi_IN/LC_MESSAGES/djangojs.mo +0 -0
  279. game/locale/hi_IN/LC_MESSAGES/djangojs.po +0 -739
  280. game/locale/id_ID/LC_MESSAGES/django.mo +0 -0
  281. game/locale/id_ID/LC_MESSAGES/django.po +0 -405
  282. game/locale/id_ID/LC_MESSAGES/djangojs.mo +0 -0
  283. game/locale/id_ID/LC_MESSAGES/djangojs.po +0 -738
  284. game/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  285. game/locale/it_IT/LC_MESSAGES/django.po +0 -405
  286. game/locale/it_IT/LC_MESSAGES/djangojs.mo +0 -0
  287. game/locale/it_IT/LC_MESSAGES/djangojs.po +0 -739
  288. game/locale/ja_JP/LC_MESSAGES/django.mo +0 -0
  289. game/locale/ja_JP/LC_MESSAGES/django.po +0 -405
  290. game/locale/ja_JP/LC_MESSAGES/djangojs.mo +0 -0
  291. game/locale/ja_JP/LC_MESSAGES/djangojs.po +0 -738
  292. game/locale/lol_US/LC_MESSAGES/django.mo +0 -0
  293. game/locale/lol_US/LC_MESSAGES/django.po +0 -405
  294. game/locale/lol_US/LC_MESSAGES/djangojs.mo +0 -0
  295. game/locale/lol_US/LC_MESSAGES/djangojs.po +0 -739
  296. game/locale/nb_NO/LC_MESSAGES/django.mo +0 -0
  297. game/locale/nb_NO/LC_MESSAGES/django.po +0 -405
  298. game/locale/nb_NO/LC_MESSAGES/djangojs.mo +0 -0
  299. game/locale/nb_NO/LC_MESSAGES/djangojs.po +0 -739
  300. game/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  301. game/locale/nl_NL/LC_MESSAGES/django.po +0 -405
  302. game/locale/nl_NL/LC_MESSAGES/djangojs.mo +0 -0
  303. game/locale/nl_NL/LC_MESSAGES/djangojs.po +0 -739
  304. game/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  305. game/locale/pl_PL/LC_MESSAGES/django.po +0 -405
  306. game/locale/pl_PL/LC_MESSAGES/djangojs.mo +0 -0
  307. game/locale/pl_PL/LC_MESSAGES/djangojs.po +0 -741
  308. game/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  309. game/locale/pt_BR/LC_MESSAGES/django.po +0 -405
  310. game/locale/pt_BR/LC_MESSAGES/djangojs.mo +0 -0
  311. game/locale/pt_BR/LC_MESSAGES/djangojs.po +0 -739
  312. game/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  313. game/locale/pt_PT/LC_MESSAGES/django.po +0 -405
  314. game/locale/pt_PT/LC_MESSAGES/djangojs.mo +0 -0
  315. game/locale/pt_PT/LC_MESSAGES/djangojs.po +0 -739
  316. game/locale/ro_RO/LC_MESSAGES/django.mo +0 -0
  317. game/locale/ro_RO/LC_MESSAGES/django.po +0 -405
  318. game/locale/ro_RO/LC_MESSAGES/djangojs.mo +0 -0
  319. game/locale/ro_RO/LC_MESSAGES/djangojs.po +0 -740
  320. game/locale/ru_RU/LC_MESSAGES/django.mo +0 -0
  321. game/locale/ru_RU/LC_MESSAGES/django.po +0 -405
  322. game/locale/ru_RU/LC_MESSAGES/djangojs.mo +0 -0
  323. game/locale/ru_RU/LC_MESSAGES/djangojs.po +0 -741
  324. game/locale/sv_SE/LC_MESSAGES/django.mo +0 -0
  325. game/locale/sv_SE/LC_MESSAGES/django.po +0 -405
  326. game/locale/sv_SE/LC_MESSAGES/djangojs.mo +0 -0
  327. game/locale/sv_SE/LC_MESSAGES/djangojs.po +0 -739
  328. game/locale/tl_PH/LC_MESSAGES/django.mo +0 -0
  329. game/locale/tl_PH/LC_MESSAGES/django.po +0 -405
  330. game/locale/tl_PH/LC_MESSAGES/djangojs.mo +0 -0
  331. game/locale/tl_PH/LC_MESSAGES/djangojs.po +0 -739
  332. game/locale/tlh_AA/LC_MESSAGES/django.mo +0 -0
  333. game/locale/tlh_AA/LC_MESSAGES/django.po +0 -405
  334. game/locale/tlh_AA/LC_MESSAGES/djangojs.mo +0 -0
  335. game/locale/tlh_AA/LC_MESSAGES/djangojs.po +0 -739
  336. game/locale/tr_TR/LC_MESSAGES/django.mo +0 -0
  337. game/locale/tr_TR/LC_MESSAGES/django.po +0 -405
  338. game/locale/tr_TR/LC_MESSAGES/djangojs.mo +0 -0
  339. game/locale/tr_TR/LC_MESSAGES/djangojs.po +0 -740
  340. game/locale/ur_IN/LC_MESSAGES/django.mo +0 -0
  341. game/locale/ur_IN/LC_MESSAGES/django.po +0 -405
  342. game/locale/ur_IN/LC_MESSAGES/djangojs.mo +0 -0
  343. game/locale/ur_IN/LC_MESSAGES/djangojs.po +0 -739
  344. game/locale/ur_PK/LC_MESSAGES/django.mo +0 -0
  345. game/locale/ur_PK/LC_MESSAGES/django.po +0 -405
  346. game/locale/ur_PK/LC_MESSAGES/djangojs.mo +0 -0
  347. game/locale/ur_PK/LC_MESSAGES/djangojs.po +0 -739
  348. game/static/game/image/actions/go.svg +0 -18
  349. game/static/game/image/icons/destination.svg +0 -9
  350. game/static/game/js/pqselect.min.js +0 -9
  351. game/static/game/js/widget-scroller.js +0 -906
  352. rapid_router-5.4.1.dist-info/LICENSE.md +0 -577
  353. rapid_router-5.4.1.dist-info/METADATA +0 -24
  354. {rapid_router-5.4.1.dist-info → rapid_router-7.6.8.dist-info}/top_level.txt +0 -0
@@ -8,6 +8,8 @@ ocargo.LevelEditor = function(levelId) {
8
8
  /* Constants */
9
9
  /*************/
10
10
 
11
+ const TAB_PANE_WIDTH = 500;
12
+
11
13
  var LIGHT_RED_URL = ocargo.Drawing.raphaelImageDir + 'trafficLight_red.svg';
12
14
  var LIGHT_GREEN_URL = ocargo.Drawing.raphaelImageDir + 'trafficLight_green.svg';
13
15
 
@@ -36,21 +38,37 @@ ocargo.LevelEditor = function(levelId) {
36
38
  '#8F7C00' // Khaki
37
39
  ];
38
40
 
39
- var ADD_ROAD_IMG_URL = ocargo.Drawing.imageDir + "icons/add_road.svg";
40
- var DELETE_ROAD_IMG_URL = ocargo.Drawing.imageDir + "icons/delete_road.svg";
41
- var MARK_START_IMG_URL = ocargo.Drawing.imageDir + "icons/origin.svg";
42
- var MARK_END_IMG_URL = ocargo.Drawing.imageDir + "icons/destination.svg";
43
-
44
41
  var VALID_LIGHT_COLOUR = '#87E34D';
45
42
  var INVALID_LIGHT_COLOUR = '#E35F4D';
46
43
 
47
44
  var paper = $('#paper'); // May as well cache this
48
45
 
49
46
  var modes = {
50
- ADD_ROAD_MODE: {name: gettext('Add road'), url: ocargo.Drawing.imageDir + "icons/add_road.svg"},
51
- DELETE_ROAD_MODE: {name: gettext('Delete road'), url: ocargo.Drawing.imageDir + "icons/delete_road.svg"},
52
- MARK_DESTINATION_MODE: {name: gettext('Mark end'), url: ocargo.Drawing.imageDir + "icons/destination.svg"},
53
- MARK_ORIGIN_MODE: {name: gettext('Mark start'), url: ocargo.Drawing.imageDir + "icons/origin.svg"}
47
+ ADD_ROAD_MODE: {
48
+ name: gettext('Add road'),
49
+ url: ocargo.Drawing.imageDir + 'icons/add_road.svg',
50
+ id: 'add_road',
51
+ },
52
+ DELETE_ROAD_MODE: {
53
+ name: gettext('Delete road'),
54
+ url: ocargo.Drawing.imageDir + 'icons/delete_road.svg',
55
+ id: 'delete_road',
56
+ },
57
+ MARK_ORIGIN_MODE: {
58
+ name: gettext('Mark start'),
59
+ url: ocargo.Drawing.imageDir + 'icons/origin.svg',
60
+ id: 'start',
61
+ },
62
+ ADD_HOUSE_MODE: {
63
+ name: gettext('Add house'),
64
+ url: ocargo.Drawing.imageDir + 'icons/add_house.svg',
65
+ id: 'add_house',
66
+ },
67
+ DELETE_HOUSE_MODE: {
68
+ name: gettext('Delete house'),
69
+ url: ocargo.Drawing.imageDir + 'icons/delete_house.svg',
70
+ id: 'delete_house',
71
+ }
54
72
  };
55
73
 
56
74
  /*********/
@@ -67,8 +85,9 @@ ocargo.LevelEditor = function(levelId) {
67
85
  var trafficLights = [];
68
86
  var cows = [];
69
87
  var originNode = null;
70
- var destinationNode = null;
88
+ var houseNodes = [];
71
89
  var currentTheme = THEMES.grass;
90
+ var needsApproval = false;
72
91
 
73
92
  // Reference to the Raphael elements for each square
74
93
  var grid;
@@ -173,6 +192,8 @@ ocargo.LevelEditor = function(levelId) {
173
192
  tabs.character = new ocargo.Tab($('#character_radio'), $('#character_radio + label'), $('#character_pane'));
174
193
  tabs.blocks = new ocargo.Tab($('#blocks_radio'), $('#blocks_radio + label'), $('#blocks_pane'));
175
194
  tabs.random = new ocargo.Tab($('#random_radio'), $('#random_radio + label'), $('#random_pane'));
195
+ tabs.description = new ocargo.Tab($('#description_radio'), $('#description_radio + label'), $('#description_pane'));
196
+ tabs.hint = new ocargo.Tab($('#hint_radio'), $('#hint_radio + label'), $('#hint_pane'));
176
197
  tabs.load = new ocargo.Tab($('#load_radio'), $('#load_radio + label'), $('#load_pane'));
177
198
  tabs.save = new ocargo.Tab($('#save_radio'), $('#save_radio + label'), $('#save_pane'));
178
199
  tabs.share = new ocargo.Tab($('#share_radio'), $('#share_radio + label'), $('#share_pane'));
@@ -186,6 +207,8 @@ ocargo.LevelEditor = function(levelId) {
186
207
  setupCharacterTab();
187
208
  setupBlocksTab();
188
209
  setupRandomTab();
210
+ setupDescriptionTab();
211
+ setupHintTab();
189
212
  setupLoadTab();
190
213
  setupSaveTab();
191
214
  setupShareTab();
@@ -235,6 +258,10 @@ ocargo.LevelEditor = function(levelId) {
235
258
  function changeCurrentToolDisplay(mode){
236
259
  $('#currentToolText').text(mode.name);
237
260
  $('#currentToolIcon').attr("src", mode.url);
261
+ Object.values(modes).forEach((element) => {
262
+ $(`#${element.id}`).addClass('unselected');
263
+ });
264
+ $(`#${mode.id}`).removeClass('unselected');
238
265
  }
239
266
 
240
267
  function setupMapTab() {
@@ -254,11 +281,6 @@ ocargo.LevelEditor = function(levelId) {
254
281
  changeCurrentToolDisplay(modes.MARK_ORIGIN_MODE);
255
282
  });
256
283
 
257
- $('#end').click(function() {
258
- mode = modes.MARK_DESTINATION_MODE;
259
- changeCurrentToolDisplay(modes.MARK_DESTINATION_MODE);
260
- });
261
-
262
284
  $('#add_road').click(function() {
263
285
  mode = modes.ADD_ROAD_MODE;
264
286
  changeCurrentToolDisplay(modes.ADD_ROAD_MODE);
@@ -269,6 +291,16 @@ ocargo.LevelEditor = function(levelId) {
269
291
  changeCurrentToolDisplay(modes.DELETE_ROAD_MODE);
270
292
  });
271
293
 
294
+ $('#add_house').click(function() {
295
+ mode = modes.ADD_HOUSE_MODE;
296
+ changeCurrentToolDisplay(modes.ADD_HOUSE_MODE);
297
+ });
298
+
299
+ $('#delete_house').click(function() {
300
+ mode = modes.DELETE_HOUSE_MODE;
301
+ changeCurrentToolDisplay(modes.DELETE_HOUSE_MODE);
302
+ });
303
+
272
304
  if(DEVELOPER) {
273
305
  $('#djangoText').click(function() {
274
306
  ocargo.Drawing.startPopup('Django level migration',
@@ -293,122 +325,27 @@ ocargo.LevelEditor = function(levelId) {
293
325
  }
294
326
  });
295
327
 
296
- $('.decor_button').click(function(e){
297
- new InternalDecor(e.target.id);
298
- });
328
+ $('.decor_button').mousedown(handleDraggableDecorMouseDown);
299
329
 
300
- $('#trafficLightRed').click(function() {
301
- new InternalTrafficLight({"redDuration": 3, "greenDuration": 3, "startTime": 0,
302
- "startingState": ocargo.TrafficLight.RED,
303
- "sourceCoordinate": null, "direction": null});
330
+ $('#trafficLightRed').mousedown(function(e) {
331
+ handleDraggableTrafficLightsMouseDown(e, ocargo.TrafficLight.RED);
304
332
  });
305
333
 
306
- $('#trafficLightGreen').click(function() {
307
- new InternalTrafficLight({"redDuration": 3, "greenDuration": 3, "startTime": 0,
308
- "startingState": ocargo.TrafficLight.GREEN,
309
- "sourceCoordinate": null, "direction": null});
334
+ $('#trafficLightGreen').mousedown(function(e) {
335
+ handleDraggableTrafficLightsMouseDown(e, ocargo.TrafficLight.GREEN);
310
336
  });
311
337
 
312
338
  if(COW_LEVELS_ENABLED) {
313
- $('#cow').click(function () {
314
- new InternalCow({group: cowGroups[$('#cow_group_select').val()]});
315
- });
316
-
317
- //"Advanced" button
318
- $('#advanced_cow_options_button').on('click', function () {
319
- $(this).toggleClass('cow_navigation_button_pressed');
320
-
321
- var showIconSrc = "/static/game/image/icons/show.svg";
322
- var hideIconSrc = "/static/game/image/icons/hide_button.svg";
323
- var advancedButtonIcon = $('#advanced_cow_options_button_icon');
324
- var iconSrc = (advancedButtonIcon.attr("src") === showIconSrc) ? hideIconSrc : showIconSrc;
325
- advancedButtonIcon.attr("src", iconSrc);
326
-
327
- var advancedDivHidden = $('#cow_advanced_div').css('display') === 'none';
328
-
329
- $('#cow_advanced_div').slideToggle(1000);
330
-
331
- //Only scroll down if advanced options were previously hidden
332
- if (advancedDivHidden) {
333
- $('#tab_panes_wrapper').animate({scrollTop: $('#tab_panes_wrapper')[0].scrollHeight},
334
- 1500,
335
- "swing");
336
- }
337
- });
338
-
339
- // Set up cow type selector and listener
340
- $('#cow_type_select').append($('<option>', {value: ocargo.Cow.WHITE})
341
- .text(gettext('White')));
342
- $('#cow_type_select').append($('<option>', {value: ocargo.Cow.BROWN})
343
- .text(gettext('Brown')));
344
- $('#cow_type_select').on('change', function () {
345
- var selectedGroupId = $('#cow_group_select').val();
346
- var selectedType = $('#cow_type_select').val();
347
- cowGroups[selectedGroupId].type = selectedType;
348
- var newUrl = ocargo.Drawing.raphaelImageDir + ocargo.Drawing.cowUrl(selectedType);
349
- $('#cow').attr('src', newUrl);
350
-
351
- for(var i = cows.length - 1; i >= 0; i--) {
352
- if(cows[i].data.group.id === selectedGroupId) {
353
- cows[i].image.attr({src: newUrl})
354
- }
355
- }
356
-
357
- });
358
-
359
- //Min & max spinners
360
- var minSpinner = $('#min_cows_spinner').spinner({
361
- min: 1,
362
- max: 1
363
- }).val(1);
364
- minSpinner.on('spinstop', function () {
365
- $('#max_cows_spinner').spinner('option', 'min', $('#min_cows_spinner').val());
366
- cowGroups[$('#cow_group_select').val()].minCows = $('#min_cows_spinner').val();
367
- });
368
-
369
- var maxSpinner = $('#max_cows_spinner').spinner({
370
- min: 1,
371
- max: 1
372
- }).val(1);
373
- maxSpinner.on('spinstop', function () {
374
- $('#min_cows_spinner').spinner('option', 'max', $('#max_cows_spinner').val());
375
- cowGroups[$('#cow_group_select').val()].maxCows = $('#max_cows_spinner').val();
376
- });
377
-
378
- //Group select element (has to be initialised after the min and max spinners are created)
379
- $('#cow_group_select').on('change', function () {
380
- $('#cow_group_select').css('background-color', cowGroups[this.value].color);
381
- //Set max values of min & max spinners
382
- var groupId = this.value;
383
- var noOfValidCowsInGroup = 0;
384
- for (var i = 0; i < cows.length; i++) {
385
- if (cows[i].valid && cows[i].data.group.id === groupId) {
386
- noOfValidCowsInGroup++;
387
- }
388
- }
389
-
390
- // Set cow type
391
- $('#cow_type_select').val(cowGroups[this.value].type).change();
392
-
393
- var minMaxValue = Math.max(1, cowGroups[this.value].maxCows);
394
- $('#min_cows_spinner').spinner('option', 'max', minMaxValue);
395
- var maxMinValue = Math.max(1, cowGroups[this.value].minCows);
396
- $('#max_cows_spinner').spinner('option', 'min', maxMinValue);
397
- $('#max_cows_spinner').spinner('option', 'max', Math.max(1, noOfValidCowsInGroup));
398
-
399
- //Set min & max values
400
- $('#min_cows_spinner').val(cowGroups[this.value].minCows);
401
- $('#max_cows_spinner').val(cowGroups[this.value].maxCows);
402
- });
403
-
404
339
  if (Object.keys(cowGroups).length == 0) {
405
340
  addCowGroup();
406
341
  }
407
-
408
- $('#add_cow_group_button').click(addCowGroup);
409
- $('#remove_cow_group_button').click(removeCowGroup);
342
+ $('#cow').mouseover(function(e) {
343
+ e.target.style.cursor = "pointer";
344
+ })
345
+ $('#cow').mousedown(function(e) {
346
+ handleDraggableCowMouseDown(e, "group1")
347
+ });
410
348
  }
411
-
412
349
  }
413
350
 
414
351
  function setupCharacterTab() {
@@ -479,7 +416,8 @@ ocargo.LevelEditor = function(levelId) {
479
416
 
480
417
  for (var i = 0; i < BLOCKS.length; i++) {
481
418
  var type = BLOCKS[i];
482
- var block = Blockly.mainWorkspace.newBlock(type);
419
+ let usePigeons = type === "cow_crossing" && currentTheme == THEMES.city
420
+ var block = usePigeons ? Blockly.mainWorkspace.newBlock("pigeon_crossing_IMAGE_ONLY") : Blockly.mainWorkspace.newBlock(type);
483
421
  block.initSvg();
484
422
  block.render();
485
423
 
@@ -545,6 +483,18 @@ ocargo.LevelEditor = function(levelId) {
545
483
  });
546
484
  }
547
485
 
486
+ function setupDescriptionTab() {
487
+ tabs.description.setOnChange(function() {
488
+ transitionTab(tabs.description);
489
+ });
490
+ }
491
+
492
+ function setupHintTab() {
493
+ tabs.hint.setOnChange(function() {
494
+ transitionTab(tabs.hint);
495
+ });
496
+ }
497
+
548
498
  function goToMapTab() {
549
499
  tabs.map.select();
550
500
  }
@@ -682,17 +632,27 @@ ocargo.LevelEditor = function(levelId) {
682
632
  return;
683
633
  }
684
634
 
685
- var newName = $('#levelNameInput').val();
635
+ const nameInput = $('#levelNameInput')
636
+ const newName = nameInput.val();
686
637
  if (!newName || newName === "") {
687
- // TODO error message?
638
+ ocargo.Drawing.startPopup(
639
+ "Oh no!",
640
+ "No level title!",
641
+ "Sorry, you need to specify a title for your" +
642
+ " level to be saved.",
643
+ );
688
644
  return;
689
645
  }
690
646
 
691
- var regex = /^(\w?[ ]?)*$/;
692
- var validString = regex.exec($('#levelNameInput').val());
647
+ const regex = /^[\w ]*$/;
648
+ const validString = regex.exec(nameInput.val());
693
649
  if (!validString) {
694
- ocargo.Drawing.startPopup(gettext('Oh no!'), gettext('You used some invalid characters.'),
695
- gettext('Try saving your level again using only letters and numbers.'));
650
+ ocargo.Drawing.startPopup(
651
+ "Oh no!",
652
+ "You used some invalid characters.",
653
+ "Try saving your level again using only" +
654
+ " letters and numbers."
655
+ );
696
656
  return;
697
657
  }
698
658
 
@@ -701,31 +661,37 @@ ocargo.LevelEditor = function(levelId) {
701
661
  }
702
662
 
703
663
  // Test to see if we already have the level saved
704
- var table = $("#saveLevelTable");
705
- var existingId = -1;
664
+ const table = $("#saveLevelTable");
665
+ let existingId = -1;
706
666
 
707
- for (var i = 0; i < table[0].rows.length; i++) {
708
- var row = table[0].rows[i];
709
- var existingName = row.cells[0].innerHTML;
667
+ for (let i = 0; i < table[0].rows.length; i++) {
668
+ const row = table[0].rows[i];
669
+ const existingName = row.cells[0].innerHTML;
710
670
  if (existingName === newName) {
711
671
  existingId = row.getAttribute('value');
712
672
  break;
713
673
  }
714
674
  }
715
675
 
716
- if (existingId != -1) {
676
+ if (existingId !== -1) {
717
677
  if (!saveState.isCurrentLevel(existingId)) {
718
- var onYes = function(){
678
+ const onYes = function(){
719
679
  saveLevelLocal(existingId);
720
- $("#close-modal").click();
680
+ $("#myModal").hide()
681
+ $("#ocargo-modal").hide()
721
682
  };
722
- var onNo = function(){
723
- $("#close-modal").click();
683
+ const onNo = function(){
684
+ $("#myModal").hide()
685
+ $("#ocargo-modal").hide()
724
686
  };
725
- ocargo.Drawing.startYesNoPopup(gettext('Overwriting'), gettext('Warning'),
726
- interpolate(gettext('Level %(level_name)s already exists. Are you sure you want to overwrite it?'), {
727
- level_name: newName
728
- }, true), onYes, onNo);
687
+ ocargo.Drawing.startYesNoPopup(
688
+ "Overwriting",
689
+ "Warning",
690
+ `Level ${newName} already exists. Are
691
+ you sure you want to overwrite it?`,
692
+ onYes,
693
+ onNo
694
+ );
729
695
  } else {
730
696
  saveLevelLocal(existingId);
731
697
  }
@@ -766,7 +732,7 @@ ocargo.LevelEditor = function(levelId) {
766
732
  }
767
733
 
768
734
  function setupShareTab() {
769
- // Setup the behaviour for when the tab is selected
735
+ // Set up the behaviour for when the tab is selected
770
736
  tabs.share.setOnChange(function() {
771
737
  if (!isIndependentStudent() || !isLoggedIn("share") || !canShare() || !isLevelOwned()) {
772
738
  restorePreviousTab();
@@ -797,25 +763,28 @@ ocargo.LevelEditor = function(levelId) {
797
763
  interpolate(
798
764
  gettext('In %(map_icon)s%(map_label)s menu, click %(mark_start_icon)s%(mark_start_label)s and select a ' +
799
765
  'square for your road to start from. The starting point can only be placed on dead ends. You need a ' +
800
- 'road first before adding a starting point. Make sure you use %(mark_end_icon)s%(mark_end_label)s to ' +
801
- 'select a final destination. Setting a fuel level means the route will need to be short enough for the ' +
766
+ 'road first before adding a starting point. Make sure you use %(add_house_icon)s%(add_house_label)s to ' +
767
+ 'select houses for delivery. Setting a fuel level means the route will need to be short enough for the ' +
802
768
  'fuel not to run out.'
803
769
  ), {
804
770
  map_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/map.svg', 'popupIcon'),
805
771
  map_label: '<b>' + gettext('Map') + '</b>',
806
772
  mark_start_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/origin.svg', 'popupIcon'),
807
773
  mark_start_label: '<b>' + gettext('Mark start') + '</b>',
808
- mark_end_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/destination.svg', 'popupIcon'),
809
- mark_end_label: '<b>' + gettext('Mark end') + '</b>'
774
+ add_house_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/add_house.svg', 'popupIcon'),
775
+ add_house_label: '<b>' + gettext('Add house') + '</b>'
810
776
  },
811
777
  true
812
778
  ),
813
779
  interpolate(
814
780
  gettext('To remove road, click the %(delete_road_icon)s%(delete_road_label)s button and select a section ' +
815
- 'to get rid of.'
781
+ 'to get rid of. To remove a house for delivery, click the %(delete_house_icon)s%(delete_house_label)s button' +
782
+ 'and select a house to get rid of.'
816
783
  ), {
817
784
  delete_road_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/delete_road.svg', 'popupIcon'),
818
- delete_road_label: '<b>' + gettext('Delete road') + '</b>'
785
+ delete_road_label: '<b>' + gettext('Delete road') + '</b>',
786
+ delete_house_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/delete_house.svg', 'popupIcon'),
787
+ delete_house_label: '<b>' + gettext('Delete house') + '</b>'
819
788
  },
820
789
  true
821
790
  ),
@@ -942,7 +911,7 @@ ocargo.LevelEditor = function(levelId) {
942
911
  var color = COW_GROUP_COLOR_PALETTE[(currentCowGroupId - 1) % COW_GROUP_COLOR_PALETTE.length];
943
912
  var style = 'background-color: ' + color;
944
913
  var value = 'group' + currentCowGroupId++;
945
- var type = ocargo.Cow.WHITE;
914
+ var type = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
946
915
 
947
916
  cowGroups[value] = {
948
917
  id: value,
@@ -1045,8 +1014,8 @@ ocargo.LevelEditor = function(levelId) {
1045
1014
  var windowWidth = $(window).width();
1046
1015
  var windowHeight = $(window).height();
1047
1016
 
1048
- var paperRightEdge = PAPER_WIDTH + $('#tools').width();
1049
- var paperBottomEdge = PAPER_HEIGHT;
1017
+ var paperRightEdge = EXTENDED_PAPER_WIDTH + $('#tools').width();
1018
+ var paperBottomEdge = EXTENDED_PAPER_HEIGHT;
1050
1019
 
1051
1020
  var bottom = 50;
1052
1021
  if(windowHeight > paperBottomEdge) {
@@ -1093,7 +1062,7 @@ ocargo.LevelEditor = function(levelId) {
1093
1062
  } else {
1094
1063
  closeTrashcan();
1095
1064
  }
1096
- }
1065
+ }
1097
1066
 
1098
1067
  function openTrashcan() {
1099
1068
  $('#trashcanLidOpen').css('display', 'block');
@@ -1116,8 +1085,8 @@ ocargo.LevelEditor = function(levelId) {
1116
1085
  return originNode && originNode.coordinate.equals(coordinate);
1117
1086
  }
1118
1087
 
1119
- function isDestinationCoordinate(coordinate) {
1120
- return destinationNode && destinationNode.coordinate.equals(coordinate);
1088
+ function isHouseCoordinate(coordinate) {
1089
+ return houseNodes.includes(ocargo.Node.findNodeByCoordinate(coordinate, nodes));
1121
1090
  }
1122
1091
 
1123
1092
  function isCoordinateOnGrid(coordinate) {
@@ -1182,7 +1151,7 @@ ocargo.LevelEditor = function(levelId) {
1182
1151
  nodes = [];
1183
1152
  strikeStart = null;
1184
1153
  originNode = null;
1185
- destinationNode = null;
1154
+ houseNodes = [];
1186
1155
 
1187
1156
  cowGroups = {};
1188
1157
  currentCowGroupId = 1;
@@ -1238,7 +1207,7 @@ ocargo.LevelEditor = function(levelId) {
1238
1207
  mark(coordinate, 'red', 0.7, true);
1239
1208
  }
1240
1209
 
1241
- function markAsDestination(coordinate) {
1210
+ function markAsHouse(coordinate) {
1242
1211
  mark(coordinate, 'blue', 0.7, true);
1243
1212
  }
1244
1213
 
@@ -1258,8 +1227,8 @@ ocargo.LevelEditor = function(levelId) {
1258
1227
  if (cows) {
1259
1228
  for (var i = 0; i < cows.length; i++) {
1260
1229
  var internalCow = cows[i];
1261
- if (internalCow.controlledNode) {
1262
- mark(internalCow.controlledNode.coordinate, internalCow.data.group.color, 0.3, true);
1230
+ if (internalCow.coordinate) {
1231
+ mark(internalCow.coordinate, internalCow.data.group.color, 0.3, true);
1263
1232
  }
1264
1233
  }
1265
1234
  }
@@ -1278,8 +1247,10 @@ ocargo.LevelEditor = function(levelId) {
1278
1247
  if (originNode) {
1279
1248
  markAsOrigin(originNode.coordinate);
1280
1249
  }
1281
- if (destinationNode) {
1282
- markAsDestination(destinationNode.coordinate);
1250
+ if (houseNodes.length > 0) {
1251
+ for (let i = 0; i < houseNodes.length; i++) {
1252
+ markAsHouse(houseNodes[i].coordinate);
1253
+ }
1283
1254
  }
1284
1255
 
1285
1256
  bringTrafficLightsToFront();
@@ -1358,10 +1329,11 @@ ocargo.LevelEditor = function(levelId) {
1358
1329
  var prevStart = originNode.coordinate;
1359
1330
  markAsBackground(prevStart);
1360
1331
  }
1361
- // Check if same as destination node
1362
- if (isDestinationCoordinate(coordMap)) {
1363
- destinationNode = null;
1332
+ // Check if same as a house node
1333
+ if (isHouseCoordinate(coordMap)) {
1334
+ houseNodes.splice(houseNodes.indexOf(ocargo.Node.findNodeByCoordinate(coordMap, nodes)), 1)
1364
1335
  }
1336
+
1365
1337
  markAsOrigin(coordMap);
1366
1338
  var newStartIndex = ocargo.Node.findNodeIndexByCoordinate(coordMap, nodes);
1367
1339
 
@@ -1370,20 +1342,22 @@ ocargo.LevelEditor = function(levelId) {
1370
1342
  nodes[newStartIndex] = nodes[0];
1371
1343
  nodes[0] = temp;
1372
1344
  originNode = nodes[0];
1373
- } else if (mode === modes.MARK_DESTINATION_MODE && existingNode) {
1374
- if (destinationNode) {
1375
- var prevEnd = destinationNode.coordinate;
1376
- markAsBackground(prevEnd);
1377
- }
1345
+ } else if (mode === modes.ADD_HOUSE_MODE && existingNode) {
1378
1346
  // Check if same as starting node
1379
1347
  if (isOriginCoordinate(coordMap)) {
1380
1348
  originNode = null;
1381
1349
  }
1382
- markAsDestination(coordMap);
1350
+
1351
+ markAsHouse(coordMap);
1383
1352
  var newEnd = ocargo.Node.findNodeIndexByCoordinate(coordMap, nodes);
1384
- destinationNode = nodes[newEnd];
1353
+ houseNodes.push(nodes[newEnd]);
1385
1354
 
1386
- } else if (mode === modes.ADD_ROAD_MODE || mode === modes.DELETE_ROAD_MODE) {
1355
+ } else if (mode === modes.DELETE_HOUSE_MODE && existingNode) {
1356
+ if (isHouseCoordinate(coordMap)) {
1357
+ houseNodes.splice(houseNodes.indexOf(ocargo.Node.findNodeByCoordinate(coordMap, nodes)), 1);
1358
+ markAsBackground(coordMap);
1359
+ }
1360
+ } else if (mode === modes.ADD_ROAD_MODE || mode === modes.DELETE_ROAD_MODE) {
1387
1361
  strikeStart = coordMap;
1388
1362
  markAsSelected(coordMap);
1389
1363
  }
@@ -1405,17 +1379,19 @@ ocargo.LevelEditor = function(levelId) {
1405
1379
  if (mode === modes.ADD_ROAD_MODE || mode === modes.DELETE_ROAD_MODE) {
1406
1380
  if (strikeStart !== null) {
1407
1381
  markTentativeRoad(coordMap);
1408
- } else if (!isOriginCoordinate(coordMap) && !isDestinationCoordinate(coordMap)) {
1382
+ } else if (!isOriginCoordinate(coordMap) && !isHouseCoordinate(coordMap)) {
1409
1383
  markAsHighlighted(coordMap);
1410
1384
  }
1411
- } else if (mode === modes.MARK_ORIGIN_MODE || mode === modes.MARK_DESTINATION_MODE) {
1385
+ } else if (mode === modes.MARK_ORIGIN_MODE || mode === modes.ADD_HOUSE_MODE || mode === modes.DELETE_HOUSE_MODE) {
1412
1386
  var node = ocargo.Node.findNodeByCoordinate(coordMap, nodes);
1413
- if (node && destinationNode !== node && originNode !== node) {
1414
- if (mode === modes.MARK_DESTINATION_MODE) {
1387
+ if (node && originNode !== node && !houseNodes.includes(node)) {
1388
+ if (mode === modes.ADD_HOUSE_MODE) {
1415
1389
  mark(coordMap, 'blue', 0.3, true);
1416
- } else if (canPlaceCFC(node)) {
1390
+ } else if (mode === modes.MARK_ORIGIN_MODE && canPlaceCFC(node)) {
1417
1391
  mark(coordMap, 'red', 0.5, true);
1418
1392
  }
1393
+ } else if (node && houseNodes.includes(node) && mode === modes.DELETE_HOUSE_MODE) {
1394
+ mark(coordMap, 'blue', 0.3, true);
1419
1395
  }
1420
1396
  }
1421
1397
  };
@@ -1429,14 +1405,17 @@ ocargo.LevelEditor = function(levelId) {
1429
1405
  var coordPaper = getCoordinateFromBBox(getBBox);
1430
1406
  var coordMap = ocargo.Drawing.translate(coordPaper);
1431
1407
 
1432
- if (mode === modes.MARK_ORIGIN_MODE || mode === modes.MARK_DESTINATION_MODE) {
1408
+ if (mode === modes.MARK_ORIGIN_MODE || mode === modes.ADD_HOUSE_MODE || mode === modes.DELETE_HOUSE_MODE) {
1433
1409
  var node = ocargo.Node.findNodeByCoordinate(coordMap, nodes);
1434
- if (node && destinationNode !== node && originNode !== node) {
1410
+ if (node && originNode !== node && !houseNodes.includes(node)) {
1435
1411
  markAsBackground(coordMap);
1436
1412
  markCowNodes();
1413
+ } else if (node && houseNodes.includes(node)) {
1414
+ markAsHouse(coordMap);
1415
+ markCowNodes();
1437
1416
  }
1438
1417
  } else if (mode === modes.ADD_ROAD_MODE || mode === modes.DELETE_ROAD_MODE) {
1439
- if (!isOriginCoordinate(coordMap) && !isDestinationCoordinate(coordMap)) {
1418
+ if (!isOriginCoordinate(coordMap) && !isHouseCoordinate(coordMap)) {
1440
1419
  markAsBackground(coordMap);
1441
1420
  markCowNodes();
1442
1421
  }
@@ -1492,6 +1471,23 @@ ocargo.LevelEditor = function(levelId) {
1492
1471
  };
1493
1472
  }
1494
1473
 
1474
+ function draggedObjectOnGrid(e, dragged_object) {
1475
+ // object location is relative to the whole page, so need to factor in paper and padding size, grid canvas scroll amount, width of toolbar, etc.
1476
+ return e.pageX >= (TAB_PANE_WIDTH + PAPER_PADDING)
1477
+ && (e.pageY + paper.scrollTop() + dragged_object.height / 2) <= (PAPER_HEIGHT + PAPER_PADDING)
1478
+ && (e.pageX + paper.scrollLeft() + dragged_object.width / 2) <= (TAB_PANE_WIDTH + PAPER_WIDTH + PAPER_PADDING)
1479
+ }
1480
+
1481
+ function getAbsCoordinates(e) {
1482
+ const absX = (e.pageX + paper.scrollLeft() - TAB_PANE_WIDTH) / GRID_SPACE_SIZE;
1483
+ const absY = (e.pageY + paper.scrollTop()) / GRID_SPACE_SIZE;
1484
+ return [absX, absY];
1485
+ }
1486
+
1487
+ function draggedCursorOverGrid(absX, absY) {
1488
+ return absY <= SEMI_EXTENDED_PAPER_HEIGHT / 100 && absX <= EXTENDED_PAPER_WIDTH / 100 && absX >= 0
1489
+ }
1490
+
1495
1491
  function setupDecorListeners(decor) {
1496
1492
  var image = decor.image;
1497
1493
 
@@ -1517,14 +1513,14 @@ ocargo.LevelEditor = function(levelId) {
1517
1513
  // Stop it being dragged off the edge of the page
1518
1514
  if (paperX < 0) {
1519
1515
  paperX = 0;
1520
- } else if (paperX + imageWidth > paperWidth) {
1521
- paperX = paperWidth - imageWidth;
1516
+ } else if (paperX + imageWidth > EXTENDED_PAPER_WIDTH) {
1517
+ paperX = EXTENDED_PAPER_WIDTH - imageWidth;
1522
1518
  }
1523
1519
 
1524
1520
  if (paperY < 0) {
1525
1521
  paperY = 0;
1526
- } else if (paperY + imageHeight > paperHeight) {
1527
- paperY = paperHeight - imageHeight;
1522
+ } else if (paperY + imageHeight > EXTENDED_PAPER_HEIGHT) {
1523
+ paperY = EXTENDED_PAPER_HEIGHT - imageHeight;
1528
1524
  }
1529
1525
 
1530
1526
  image.transform('t' + paperX + ',' + paperY);
@@ -1547,8 +1543,17 @@ ocargo.LevelEditor = function(levelId) {
1547
1543
  originX = paperX;
1548
1544
  originY = paperY;
1549
1545
 
1550
- if(trashcanOpen) {
1546
+ if (trashcanOpen) {
1551
1547
  decor.destroy();
1548
+ } else {
1549
+ if (paperWidth < paperX + imageWidth) {
1550
+ originX = paperWidth - imageWidth;
1551
+ }
1552
+ if (paperHeight < paperY + imageHeight) {
1553
+ originY = paperHeight - imageHeight;
1554
+ }
1555
+
1556
+ image.transform('t' + originX + ',' + originY);
1552
1557
  }
1553
1558
 
1554
1559
  closeTrashcan();
@@ -1558,6 +1563,47 @@ ocargo.LevelEditor = function(levelId) {
1558
1563
  addReleaseListeners(image.node);
1559
1564
  }
1560
1565
 
1566
+ function handleDraggableDecorMouseDown(e){
1567
+ e.preventDefault();
1568
+
1569
+ window.dragged_decor = {};
1570
+ dragged_decor.pageX0 = e.pageX;
1571
+ dragged_decor.pageY0 = e.pageY;
1572
+ dragged_decor.elem = this;
1573
+ dragged_decor.offset0 = $(this).offset();
1574
+ dragged_decor.width = parseInt(currentTheme.decor[this.id].width);
1575
+ dragged_decor.height = parseInt(currentTheme.decor[this.id].height);
1576
+ dragged_decor.parent = this.parentElement;
1577
+
1578
+ const clone = $(this).clone(true);
1579
+
1580
+ function handleDraggableDecorDragging(e){
1581
+ const left = dragged_decor.offset0.left + (e.pageX - dragged_decor.pageX0);
1582
+ const top = dragged_decor.offset0.top + (e.pageY - dragged_decor.pageY0);
1583
+ $(dragged_decor.elem).offset({top: top, left: left});
1584
+ }
1585
+
1586
+ function handleDraggableDecorMouseUp(e){
1587
+ if (dragged_decor.elem.id !== null) {
1588
+ if (draggedObjectOnGrid(e, dragged_decor)) {
1589
+ let decorObject = new InternalDecor(dragged_decor.elem.id);
1590
+ decorObject.setPosition(e.pageX + paper.scrollLeft() - TAB_PANE_WIDTH - dragged_decor.width / 2, e.pageY + paper.scrollTop() - dragged_decor.height / 2);
1591
+ }
1592
+ }
1593
+
1594
+ $(document)
1595
+ .off('mousemove', handleDraggableDecorDragging)
1596
+ .off('mouseup mouseleave', handleDraggableDecorMouseUp);
1597
+
1598
+ $(dragged_decor.elem).remove();
1599
+ $(clone).appendTo(dragged_decor.parent);
1600
+ }
1601
+
1602
+ $(document)
1603
+ .on('mouseup mouseleave', handleDraggableDecorMouseUp)
1604
+ .on('mousemove', handleDraggableDecorDragging);
1605
+ }
1606
+
1561
1607
  function setupCowListeners(cow) {
1562
1608
  var image = cow.image;
1563
1609
 
@@ -1598,61 +1644,27 @@ ocargo.LevelEditor = function(levelId) {
1598
1644
  // Stop it being dragged off the edge of the page
1599
1645
  if (paperX < 0) {
1600
1646
  paperX = 0;
1601
- } else if (paperX + imageWidth > paperWidth) {
1602
- paperX = paperWidth - imageWidth;
1647
+ } else if (paperX + imageWidth > EXTENDED_PAPER_WIDTH) {
1648
+ paperX = EXTENDED_PAPER_WIDTH - imageWidth;
1603
1649
  }
1604
1650
  if (paperY < 0) {
1605
1651
  paperY = 0;
1606
- } else if (paperY + imageHeight > paperHeight) {
1607
- paperY = paperHeight - imageHeight;
1652
+ } else if (paperY + imageHeight > EXTENDED_PAPER_HEIGHT) {
1653
+ paperY = EXTENDED_PAPER_HEIGHT - imageHeight;
1608
1654
  }
1609
1655
 
1610
1656
  // And perform the updatee
1611
1657
  image.transform('t' + paperX + ',' + paperY );
1612
1658
 
1613
1659
  //Unmark the squares the cow previously occupied
1614
- if (controlledCoord) {
1615
- markAsBackground(controlledCoord);
1616
- }
1617
- if(cows) {
1618
- for( var i = 0; i < cows.length; i++){
1619
- var internalCow = cows[i];
1620
- if(internalCow !== cow && internalCow.controlledNode) {
1621
- mark(internalCow.controlledNode.coordinate, internalCow.data.group.color, 0.3, true);
1622
- }
1623
- }
1624
- }
1625
- if (originNode) {
1626
- markAsOrigin(originNode.coordinate);
1627
- }
1628
- if (destinationNode) {
1629
- markAsDestination(destinationNode.coordinate);
1630
- }
1660
+ unmarkOldCowSquare(controlledCoord, cow);
1631
1661
 
1632
1662
  // Now calculate the source coordinate
1633
1663
  var box = image.getBBox();
1634
1664
  var absX = (box.x + box.width/2) / GRID_SPACE_SIZE;
1635
1665
  var absY = (box.y + box.height/2) / GRID_SPACE_SIZE;
1636
1666
 
1637
- var x = Math.min(Math.max(0, Math.floor(absX)), GRID_WIDTH - 1);
1638
- var y = GRID_HEIGHT - Math.min(Math.max(0, Math.floor(absY)), GRID_HEIGHT - 1) - 1;
1639
- controlledCoord = new ocargo.Coordinate(x,y);
1640
-
1641
- // If source node is not on grid remove it
1642
- if (!isCoordinateOnGrid(controlledCoord)) {
1643
- controlledCoord = null;
1644
- }
1645
-
1646
- if (controlledCoord) {
1647
- var colour;
1648
- if(isValidPlacement(controlledCoord)) {
1649
- colour = VALID_LIGHT_COLOUR;
1650
- } else {
1651
- colour = INVALID_LIGHT_COLOUR;
1652
- }
1653
-
1654
- mark(controlledCoord, colour, 0.7, false);
1655
- }
1667
+ controlledCoord = markNewCowSquare(absX, absY, controlledCoord, cow);
1656
1668
 
1657
1669
  // Deal with trashcan
1658
1670
  var paperAbsX = paperX - paper.scrollLeft() + imageWidth/2;
@@ -1669,6 +1681,8 @@ ocargo.LevelEditor = function(levelId) {
1669
1681
  }
1670
1682
 
1671
1683
  function onDragStart(x, y) {
1684
+ // cow shouldn't be in the cow group during dragging
1685
+ removeCowFromCowList(cow);
1672
1686
  var bBox = image.getBBox();
1673
1687
  imageWidth = bBox.width;
1674
1688
  imageHeight = bBox.height;
@@ -1684,56 +1698,62 @@ ocargo.LevelEditor = function(levelId) {
1684
1698
  }
1685
1699
 
1686
1700
  function onDragEnd() {
1687
- //Unmark previously occupied square
1688
- if(cow.controlledNode) {
1689
- markAsBackground(cow.controlledNode.coordinate);
1690
- }
1691
-
1692
- // Mark squares currently occupied
1693
- if (controlledCoord) {
1694
- mark(controlledCoord, cow.data.group.color, 0.3, true);
1695
- }
1696
- if (originNode) {
1697
- markAsOrigin(originNode.coordinate);
1698
- }
1699
- if (destinationNode) {
1700
- markAsDestination(destinationNode.coordinate);
1701
- }
1702
1701
 
1703
- if(trashcanOpen) {
1702
+ if (trashcanOpen) {
1704
1703
  cow.destroy();
1705
- } else if(isValidPlacement(controlledCoord)) {
1706
- // Add back to the list of cows if on valid nodes
1707
- var controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1708
- cow.controlledNode = controlledNode;
1709
- cow.valid = true;
1710
- drawing.setCowImagePosition(controlledCoord, image, controlledNode);
1704
+ unmarkOldCowSquare(controlledCoord, cow);
1705
+ closeTrashcan();
1711
1706
  } else {
1712
- cow.controlledNode = null;
1713
- cow.valid = false;
1714
- }
1715
- adjustCowGroupMinMaxFields(cow);
1707
+ setCowMarkingsOnMouseUp(controlledCoord, cow);
1708
+ cows.push(cow);
1709
+ cow.coordinate = controlledCoord;
1710
+ cow.valid = isValidDraggedCowPlacement(controlledCoord, cow);
1711
+ if (cow.isOnRoad()) {
1712
+ const controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1713
+ drawing.setCowImagePosition(controlledCoord, image, controlledNode);
1714
+ }
1715
+ else {
1716
+ var cowX = paperX;
1717
+ var cowY = paperY;
1716
1718
 
1717
- image.attr({'cursor':'pointer'});
1718
- closeTrashcan();
1719
- }
1719
+ if (paperWidth < paperX + imageWidth) {
1720
+ cowX = paperWidth - imageWidth
1721
+ }
1720
1722
 
1721
- function isValidPlacement(controlledCoord){
1722
- var controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1723
- if (!controlledNode)
1724
- return false;
1725
- for (var i=0; i < cows.length; i++) {
1726
- var otherCow = cows[i];
1727
- if (otherCow.controlledNode == controlledNode && cow != otherCow)
1728
- return false;
1723
+ if (paperHeight < paperY + imageHeight) {
1724
+ cowY = paperHeight - imageHeight
1725
+ }
1726
+
1727
+ image.transform('t' + cowX + ',' + cowY);
1728
+ }
1729
1729
  }
1730
- return true;
1730
+
1731
+ adjustCowGroupMinMaxFields(cow);
1732
+ image.attr({'cursor':'pointer'});
1731
1733
  }
1732
1734
 
1733
1735
  image.drag(onDragMove, onDragStart, onDragEnd);
1734
1736
  addReleaseListeners(image.node);
1735
1737
  }
1736
1738
 
1739
+ function removeCowFromCowList(cow) {
1740
+ var index = cows.indexOf(cow);
1741
+ if (index > -1) {
1742
+ cows.splice(index, 1);
1743
+ }
1744
+ }
1745
+
1746
+ function isValidDraggedCowPlacement(controlledCoord, cow){
1747
+ if (isOriginCoordinate(controlledCoord) || isHouseCoordinate(controlledCoord))
1748
+ return false;
1749
+ for (var i=0; i < cows.length; i++) {
1750
+ var otherCow = cows[i];
1751
+ if (cow != otherCow && otherCow.coordinate && otherCow.coordinate.equals(controlledCoord))
1752
+ return false;
1753
+ }
1754
+ return true;
1755
+ }
1756
+
1737
1757
  function adjustCowGroupMinMaxFields(draggedCow) {
1738
1758
  var draggedCowGroupId = draggedCow.data.group.id;
1739
1759
 
@@ -1750,6 +1770,131 @@ ocargo.LevelEditor = function(levelId) {
1750
1770
  $('#cow_group_select').val(draggedCowGroupId).change();
1751
1771
  }
1752
1772
 
1773
+ function unmarkOldCowSquare(controlledCoord, cow = "undefined") {
1774
+ if (controlledCoord) {
1775
+ markAsBackground(controlledCoord);
1776
+ }
1777
+ if (originNode) {
1778
+ markAsOrigin(originNode.coordinate);
1779
+ }
1780
+ if (houseNodes.length > 0) {
1781
+ for (let i = 0; i < houseNodes.length; i++){
1782
+ markAsHouse(houseNodes[i].coordinate);
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ function setCowMarkingsOnMouseUp(controlledCoord, cow) {
1788
+ if (cow.isOnRoad()) {
1789
+ markAsBackground(cow.coordinate);
1790
+ }
1791
+ if (controlledCoord) {
1792
+ mark(controlledCoord, cow.data.group.color, 0.3, true);
1793
+ }
1794
+ if (originNode) {
1795
+ markAsOrigin(originNode.coordinate);
1796
+ }
1797
+ if (houseNodes.length > 0) {
1798
+ for (let i = 0; i < houseNodes.length; i++) {
1799
+ markAsHouse(houseNodes[i].coordinate);
1800
+ }
1801
+ }
1802
+ }
1803
+
1804
+ function markNewCowSquare(absX, absY, controlledCoord, cow = "undefined") {
1805
+ const x = Math.min(Math.max(0, Math.floor(absX)), GRID_WIDTH - 1);
1806
+ const y = GRID_HEIGHT - Math.min(Math.max(0, Math.floor(absY)), GRID_HEIGHT - 1) - 1;
1807
+ controlledCoord = new ocargo.Coordinate(x,y);
1808
+
1809
+ // If source node is not on grid remove it
1810
+ if (!isCoordinateOnGrid(controlledCoord)) {
1811
+ controlledCoord = null;
1812
+ }
1813
+
1814
+ // mark square valid or invalid
1815
+ if (controlledCoord) {
1816
+ let colour;
1817
+ if(isValidDraggedCowPlacement(controlledCoord, cow)) {
1818
+ colour = VALID_LIGHT_COLOUR;
1819
+ } else {
1820
+ colour = INVALID_LIGHT_COLOUR;
1821
+ }
1822
+
1823
+ mark(controlledCoord, colour, 0.7, false);
1824
+ }
1825
+
1826
+ return controlledCoord;
1827
+ }
1828
+
1829
+ function handleDraggableCowMouseDown(e, cowGroup){
1830
+ e.preventDefault();
1831
+
1832
+ window.dragged_cow = {};
1833
+ dragged_cow.pageX0 = e.pageX;
1834
+ dragged_cow.pageY0 = e.pageY;
1835
+ dragged_cow.elem = e.target;
1836
+ dragged_cow.offset0 = $(e.target).offset();
1837
+ dragged_cow.parent = e.target.parentElement;
1838
+ dragged_cow.group = cowGroups[cowGroup];
1839
+ dragged_cow.width = COW_WIDTH;
1840
+ dragged_cow.height = COW_HEIGHT;
1841
+
1842
+ const clone = $(e.target).clone(true);
1843
+ let controlledCoord;
1844
+
1845
+ function handleDraggableCowDragging(e){
1846
+ e.target.style.cursor = "pointer";
1847
+
1848
+ const left = dragged_cow.offset0.left + (e.pageX - dragged_cow.pageX0);
1849
+ const top = dragged_cow.offset0.top + (e.pageY - dragged_cow.pageY0);
1850
+ $(dragged_cow.elem).offset({top: top, left: left});
1851
+
1852
+ unmarkOldCowSquare(controlledCoord);
1853
+
1854
+ const [absX, absY] = getAbsCoordinates(e);
1855
+ if (draggedCursorOverGrid(absX, absY)) {
1856
+ controlledCoord = markNewCowSquare(absX, absY, controlledCoord);
1857
+ }
1858
+ }
1859
+
1860
+ function handleDraggableCowMouseUp(e){
1861
+ let internalCow = new InternalCow({group: cowGroups["group1"]});
1862
+ let image = internalCow.image;
1863
+ internalCow.coordinate = controlledCoord;
1864
+ internalCow.valid = isValidDraggedCowPlacement(controlledCoord, internalCow);
1865
+
1866
+ if (internalCow.isOnRoad()) {
1867
+ const controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1868
+ drawing.setCowImagePosition(controlledCoord, image, controlledNode);
1869
+ } else {
1870
+ const cowX = e.pageX + paper.scrollLeft() - TAB_PANE_WIDTH - dragged_cow.width / 2;
1871
+ const cowY = e.pageY + paper.scrollTop() - dragged_cow.height / 2;
1872
+
1873
+ if (draggedObjectOnGrid(e, dragged_cow)) {
1874
+ image.transform('t' + cowX + ',' + cowY);
1875
+ } else {
1876
+ internalCow.destroy();
1877
+ }
1878
+ }
1879
+
1880
+ if (!trashcanOpen) {
1881
+ setCowMarkingsOnMouseUp(controlledCoord, internalCow);
1882
+ adjustCowGroupMinMaxFields(internalCow);
1883
+ }
1884
+
1885
+ $(document)
1886
+ .off('mousemove', handleDraggableCowDragging)
1887
+ .off('mouseup mouseleave', handleDraggableCowMouseUp);
1888
+
1889
+ $(dragged_cow.elem).remove();
1890
+ $(clone).appendTo(dragged_cow.parent);
1891
+ }
1892
+
1893
+ $(document)
1894
+ .on('mouseup mouseleave', handleDraggableCowMouseUp)
1895
+ .on('mousemove', handleDraggableCowDragging);
1896
+ }
1897
+
1753
1898
 
1754
1899
  function setupTrafficLightListeners(trafficLight) {
1755
1900
  var image = trafficLight.image;
@@ -1801,34 +1946,20 @@ ocargo.LevelEditor = function(levelId) {
1801
1946
  // Stop it being dragged off the edge of the page
1802
1947
  if (paperX < 0) {
1803
1948
  paperX = 0;
1804
- } else if (paperX + imageWidth > paperWidth) {
1805
- paperX = paperWidth - imageWidth;
1949
+ } else if (paperX + imageWidth > EXTENDED_PAPER_WIDTH) {
1950
+ paperX = EXTENDED_PAPER_WIDTH - imageWidth;
1806
1951
  }
1807
1952
  if (paperY < 0) {
1808
1953
  paperY = 0;
1809
- } else if (paperY + imageHeight > paperHeight) {
1810
- paperY = paperHeight - imageHeight;
1954
+ } else if (paperY + imageHeight > EXTENDED_PAPER_HEIGHT) {
1955
+ paperY = EXTENDED_PAPER_HEIGHT - imageHeight;
1811
1956
  }
1812
1957
 
1813
- // And perform the updatee
1958
+ // And perform the update
1814
1959
  image.transform('t' + paperX + ',' + paperY + 'r' + rotation + 's' + scaling);
1815
1960
 
1816
1961
  // Unmark the squares the light previously occupied
1817
- if (sourceCoord) {
1818
- markAsBackground(sourceCoord);
1819
- }
1820
- if (controlledCoord) {
1821
- markAsBackground(controlledCoord);
1822
- }
1823
-
1824
- markCowNodes();
1825
-
1826
- if (originNode) {
1827
- markAsOrigin(originNode.coordinate);
1828
- }
1829
- if (destinationNode) {
1830
- markAsDestination(destinationNode.coordinate);
1831
- }
1962
+ unmarkOldTrafficLightSquare(sourceCoord, controlledCoord);
1832
1963
 
1833
1964
  // Now calculate the source coordinate
1834
1965
  var box = image.getBBox();
@@ -1850,48 +1981,7 @@ ocargo.LevelEditor = function(levelId) {
1850
1981
  break;
1851
1982
  }
1852
1983
 
1853
- var x = Math.min(Math.max(0, Math.floor(absX)), GRID_WIDTH - 1);
1854
- var y = GRID_HEIGHT - Math.min(Math.max(0, Math.floor(absY)), GRID_HEIGHT - 1) - 1;
1855
- sourceCoord = new ocargo.Coordinate(x,y);
1856
-
1857
- // Find controlled position in map coordinates
1858
- switch(rotation) {
1859
- case 0:
1860
- controlledCoord = new ocargo.Coordinate(sourceCoord.x, sourceCoord.y + 1);
1861
- break;
1862
- case 90:
1863
- controlledCoord = new ocargo.Coordinate(sourceCoord.x + 1, sourceCoord.y);
1864
- break;
1865
- case 180:
1866
- controlledCoord = new ocargo.Coordinate(sourceCoord.x, sourceCoord.y - 1);
1867
- break;
1868
- case 270:
1869
- controlledCoord = new ocargo.Coordinate(sourceCoord.x - 1, sourceCoord.y);
1870
- break;
1871
- }
1872
-
1873
- // If controlled node is not on grid, remove it
1874
- if (!isCoordinateOnGrid(controlledCoord)) {
1875
- controlledCoord = null;
1876
- }
1877
-
1878
- // If source node is not on grid remove it
1879
- if (!isCoordinateOnGrid(sourceCoord)) {
1880
- sourceCoord = null;
1881
- }
1882
-
1883
- if (sourceCoord && controlledCoord) {
1884
- var colour;
1885
- if(isValidPlacement(sourceCoord, controlledCoord)) {
1886
- colour = VALID_LIGHT_COLOUR;
1887
- drawing.setTrafficLightImagePosition(sourceCoord, controlledCoord, image);
1888
- } else {
1889
- colour = INVALID_LIGHT_COLOUR;
1890
- }
1891
-
1892
- mark(controlledCoord, colour, 0.7, false);
1893
- mark(sourceCoord, colour, 0.7, false);
1894
- }
1984
+ [sourceCoord, controlledCoord] = markNewTrafficLightSquare(absX, absY, isValidTrafficLightPlacement, sourceCoord, controlledCoord, rotation, image);
1895
1985
 
1896
1986
  // Deal with trashcan
1897
1987
  var paperAbsX = paperX - paper.scrollLeft() + imageWidth/2;
@@ -1931,31 +2021,29 @@ ocargo.LevelEditor = function(levelId) {
1931
2021
 
1932
2022
  function onDragEnd() {
1933
2023
  // Unmark squares currently occupied
1934
- if (sourceCoord) {
1935
- markAsBackground(sourceCoord);
1936
- }
1937
- if (controlledCoord) {
1938
- markAsBackground(controlledCoord);
1939
- }
1940
-
1941
- markCowNodes();
1942
-
1943
- if (originNode) {
1944
- markAsOrigin(originNode.coordinate);
1945
- }
1946
- if (destinationNode) {
1947
- markAsDestination(destinationNode.coordinate);
1948
- }
2024
+ unmarkOldTrafficLightSquare(sourceCoord, controlledCoord);
1949
2025
 
1950
2026
  if(trashcanOpen) {
1951
2027
  trafficLight.destroy();
1952
- } else if(isValidPlacement(sourceCoord, controlledCoord)) {
2028
+ } else if(isValidTrafficLightPlacement(sourceCoord, controlledCoord)) {
1953
2029
  // Add back to the list of traffic lights if on valid nodes
1954
2030
  trafficLight.sourceNode = ocargo.Node.findNodeByCoordinate(sourceCoord, nodes);
1955
2031
  trafficLight.controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1956
2032
  trafficLight.valid = true;
1957
2033
 
1958
2034
  drawing.setTrafficLightImagePosition(sourceCoord, controlledCoord, image);
2035
+ } else {
2036
+ var trafficLightX = paperX;
2037
+ var trafficLightY = paperY;
2038
+
2039
+ if (paperWidth < paperX + imageWidth) {
2040
+ trafficLightX = paperWidth - imageWidth
2041
+ image.transform('t' + trafficLightX + ',' + trafficLightY + 'r' + rotation + 's' + scaling);
2042
+ }
2043
+ if (paperHeight < paperY + imageHeight) {
2044
+ trafficLightY = paperHeight - imageHeight
2045
+ image.transform('t' + trafficLightX + ',' + trafficLightY + 'r' + rotation + 's' + scaling);
2046
+ }
1959
2047
  }
1960
2048
 
1961
2049
  image.attr({'cursor':'pointer'});
@@ -1986,41 +2074,177 @@ ocargo.LevelEditor = function(levelId) {
1986
2074
  }
1987
2075
  return "0,0";
1988
2076
  }
2077
+ }
1989
2078
 
1990
- function isValidPlacement(sourceCoord, controlledCoord) {
1991
- var sourceNode = ocargo.Node.findNodeByCoordinate(sourceCoord, nodes);
1992
- var controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
2079
+ function isValidTrafficLightPlacement(sourceCoord, controlledCoord) {
2080
+ var sourceNode = ocargo.Node.findNodeByCoordinate(sourceCoord, nodes);
2081
+ var controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
1993
2082
 
1994
- // Test if two connected nodes exist
1995
- var connected = false;
1996
- if (sourceNode && controlledNode) {
1997
- for (var i = 0; i < sourceNode.connectedNodes.length; i++) {
1998
- if (sourceNode.connectedNodes[i] === controlledNode) {
1999
- connected = true;
2000
- break;
2001
- }
2083
+ // Test if two connected nodes exist
2084
+ var connected = false;
2085
+ if (sourceNode && controlledNode) {
2086
+ for (var i = 0; i < sourceNode.connectedNodes.length; i++) {
2087
+ if (sourceNode.connectedNodes[i] === controlledNode) {
2088
+ connected = true;
2089
+ break;
2002
2090
  }
2003
2091
  }
2092
+ }
2004
2093
 
2005
- if(!connected) {
2094
+ if(!connected) {
2095
+ return false;
2096
+ }
2097
+
2098
+ // Test it's not already occupied
2099
+ for(var i = 0; i < trafficLights.length; i++) {
2100
+ var tl = trafficLights[i];
2101
+ if(tl.valid &&
2102
+ ((tl.sourceNode === sourceNode && tl.controlledNode === controlledNode) ||
2103
+ (tl.sourceNode === controlledNode && tl.controlledNode === sourceNode))) {
2006
2104
  return false;
2007
2105
  }
2106
+ }
2107
+ return true;
2108
+ }
2109
+
2110
+ function unmarkOldTrafficLightSquare(sourceCoord, controlledCoord) {
2111
+ // Unmark the squares the light previously occupied
2112
+ if (sourceCoord) {
2113
+ markAsBackground(sourceCoord);
2114
+ }
2115
+ if (controlledCoord) {
2116
+ markAsBackground(controlledCoord);
2117
+ }
2118
+
2119
+ markCowNodes();
2120
+
2121
+ if (originNode) {
2122
+ markAsOrigin(originNode.coordinate);
2123
+ }
2124
+ if (houseNodes.length > 0) {
2125
+ for (let i = 0; i < houseNodes.length; i++) {
2126
+ markAsHouse(houseNodes[i].coordinate);
2127
+ }
2128
+ }
2129
+ }
2130
+
2131
+ function markNewTrafficLightSquare(absX, absY, validityCheckFunction, sourceCoord, controlledCoord, rotation, image = "undefined") {
2132
+ var x = Math.min(Math.max(0, Math.floor(absX)), GRID_WIDTH - 1);
2133
+ var y = GRID_HEIGHT - Math.min(Math.max(0, Math.floor(absY)), GRID_HEIGHT - 1) - 1;
2134
+ sourceCoord = new ocargo.Coordinate(x,y);
2135
+
2136
+ // Find controlled position in map coordinates
2137
+ switch(rotation) {
2138
+ case 0:
2139
+ controlledCoord = new ocargo.Coordinate(sourceCoord.x, sourceCoord.y + 1);
2140
+ break;
2141
+ case 90:
2142
+ controlledCoord = new ocargo.Coordinate(sourceCoord.x + 1, sourceCoord.y);
2143
+ break;
2144
+ case 180:
2145
+ controlledCoord = new ocargo.Coordinate(sourceCoord.x, sourceCoord.y - 1);
2146
+ break;
2147
+ case 270:
2148
+ controlledCoord = new ocargo.Coordinate(sourceCoord.x - 1, sourceCoord.y);
2149
+ break;
2150
+ }
2008
2151
 
2009
- // Test it's not already occupied
2010
- for(var i = 0; i < trafficLights.length; i++) {
2011
- var tl = trafficLights[i];
2012
- if(tl.valid &&
2013
- ((tl.sourceNode === sourceNode && tl.controlledNode === controlledNode) ||
2014
- (tl.sourceNode === controlledNode && tl.controlledNode === sourceNode))) {
2015
- return false;
2152
+ // If controlled node is not on grid, remove it
2153
+ if (!isCoordinateOnGrid(controlledCoord)) {
2154
+ controlledCoord = null;
2155
+ }
2156
+
2157
+ // If source node is not on grid remove it
2158
+ if (!isCoordinateOnGrid(sourceCoord)) {
2159
+ sourceCoord = null;
2160
+ }
2161
+
2162
+ if (sourceCoord && controlledCoord) {
2163
+ var colour;
2164
+ if(validityCheckFunction(sourceCoord, controlledCoord)) {
2165
+ colour = VALID_LIGHT_COLOUR;
2166
+ if (image !== "undefined") {
2167
+ drawing.setTrafficLightImagePosition(sourceCoord, controlledCoord, image);
2016
2168
  }
2169
+ } else {
2170
+ colour = INVALID_LIGHT_COLOUR;
2017
2171
  }
2018
- return true;
2172
+
2173
+ mark(controlledCoord, colour, 0.7, false);
2174
+ mark(sourceCoord, colour, 0.7, false);
2019
2175
  }
2020
2176
 
2021
- function occupied(sourceCoord, controlledCoord) {
2177
+ return [sourceCoord, controlledCoord];
2178
+ }
2179
+
2180
+ function handleDraggableTrafficLightsMouseDown(e, startingState){
2181
+ e.preventDefault();
2182
+
2183
+ window.dragged_light = {};
2184
+ dragged_light.pageX0 = e.pageX;
2185
+ dragged_light.pageY0 = e.pageY;
2186
+ dragged_light.elem = e.target;
2187
+ dragged_light.offset0 = $(e.target).offset();
2188
+ dragged_light.width = TRAFFIC_LIGHT_WIDTH;
2189
+ dragged_light.height = TRAFFIC_LIGHT_HEIGHT;
2190
+ dragged_light.parent = e.target.parentElement;
2191
+
2192
+ const clone = $(e.target).clone(true);
2193
+
2194
+ let sourceCoord;
2195
+ let controlledCoord;
2196
+
2197
+ function handleDraggableTrafficLightsDragging(e){
2198
+ const left = dragged_light.offset0.left + (e.pageX - dragged_light.pageX0);
2199
+ const top = dragged_light.offset0.top + (e.pageY - dragged_light.pageY0);
2200
+ $(dragged_light.elem).offset({top: top, left: left});
2022
2201
 
2202
+ unmarkOldTrafficLightSquare(sourceCoord, controlledCoord);
2203
+
2204
+ const [absX, absY] = getAbsCoordinates(e);
2205
+ if (draggedCursorOverGrid(absX, absY)) {
2206
+ [sourceCoord, controlledCoord] = markNewTrafficLightSquare(absX, absY, isValidTrafficLightPlacement, sourceCoord, controlledCoord, 0);
2207
+ }
2023
2208
  }
2209
+
2210
+ function handleDraggableTrafficLightsMouseUp(e){
2211
+ let internalTrafficLight = new InternalTrafficLight({"redDuration": 3, "greenDuration": 3, "startTime": 0, "startingState": startingState, "sourceCoordinate": null, "direction": null});
2212
+ let image = internalTrafficLight.image;
2213
+
2214
+ const lightX = e.pageX + paper.scrollLeft() - TAB_PANE_WIDTH - dragged_light.width;
2215
+ const lightY = e.pageY + paper.scrollTop() - dragged_light.width / 2;
2216
+
2217
+ unmarkOldTrafficLightSquare(sourceCoord, controlledCoord);
2218
+
2219
+ if (isValidTrafficLightPlacement(sourceCoord, controlledCoord)) {
2220
+ internalTrafficLight.sourceNode = ocargo.Node.findNodeByCoordinate(sourceCoord, nodes);
2221
+ internalTrafficLight.controlledNode = ocargo.Node.findNodeByCoordinate(controlledCoord, nodes);
2222
+ internalTrafficLight.valid = true;
2223
+
2224
+ drawing.setTrafficLightImagePosition(sourceCoord, controlledCoord, image);
2225
+ } else {
2226
+ internalTrafficLight.sourceCoord = null;
2227
+ internalTrafficLight.controlledCoord = null;
2228
+ internalTrafficLight.valid = false;
2229
+
2230
+ if (draggedObjectOnGrid(e, dragged_light)) {
2231
+ image.transform('t' + lightX + ',' + lightY + ' s-1,1');
2232
+ } else {
2233
+ internalTrafficLight.destroy();
2234
+ }
2235
+ }
2236
+
2237
+ $(document)
2238
+ .off('mousemove', handleDraggableTrafficLightsDragging)
2239
+ .off('mouseup mouseleave', handleDraggableTrafficLightsMouseUp);
2240
+
2241
+ $(dragged_light.elem).remove();
2242
+ $(clone).appendTo(dragged_light.parent);
2243
+ }
2244
+
2245
+ $(document)
2246
+ .on('mouseup mouseleave', handleDraggableTrafficLightsMouseUp)
2247
+ .on('mousemove', handleDraggableTrafficLightsDragging);
2024
2248
  }
2025
2249
 
2026
2250
  /********************************/
@@ -2039,14 +2263,14 @@ ocargo.LevelEditor = function(levelId) {
2039
2263
  }
2040
2264
  nodes.splice(nodes.indexOf(node), 1);
2041
2265
 
2042
- // Check if start or destination node
2266
+ // Check if start or house node
2043
2267
  if (isOriginCoordinate(coord)) {
2044
2268
  markAsBackground(originNode.coordinate);
2045
2269
  originNode = null;
2046
2270
  }
2047
- if (isDestinationCoordinate(coord)) {
2048
- markAsBackground(destinationNode.coordinate);
2049
- destinationNode = null;
2271
+ if (isHouseCoordinate(coord)) {
2272
+ markAsBackground(houseNodes[houseNodes.indexOf(coord)]);
2273
+ houseNodes.splice(houseNodes.indexOf(ocargo.Node.findNodeByCoordinate(coordMap, nodes)), 1);
2050
2274
  }
2051
2275
 
2052
2276
  // Check if any traffic lights present
@@ -2153,6 +2377,7 @@ ocargo.LevelEditor = function(levelId) {
2153
2377
 
2154
2378
  function setTheme(theme) {
2155
2379
  currentTheme = theme;
2380
+ let newType = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
2156
2381
 
2157
2382
  for (var x = 0; x < GRID_WIDTH; x++) {
2158
2383
  for (var y = 0; y < GRID_HEIGHT; y++) {
@@ -2169,6 +2394,36 @@ ocargo.LevelEditor = function(levelId) {
2169
2394
  });
2170
2395
 
2171
2396
  $('#paper').css({'background-color': theme.background});
2397
+
2398
+ const animalSource = theme == THEMES.city ? "/static/game/image/pigeon.svg" : "/static/game/image/Clarice.svg";
2399
+
2400
+ $('#cow').each(function(index, element) {
2401
+ element.src = animalSource;
2402
+ })
2403
+
2404
+ $('#animals_label').each(function(index, element) {
2405
+ element.innerHTML = theme == THEMES.city ? "Pigeons" : "Cows";
2406
+ })
2407
+
2408
+ for (let [key, value] of Object.entries(cowGroups)) {
2409
+ value["type"] = theme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
2410
+ }
2411
+
2412
+ for (let i = 0; i < cows.length; i++) {
2413
+ cows[i].updateTheme();
2414
+ }
2415
+
2416
+ const pigeonHTML = `<svg class="block_image"><g transform="translate(10,0)" <path="" class="blocklyPathDark" fill="#496684" d="m 0,0 H 111.34375 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
2417
+ "><path class="blocklyPath" stroke="none" fill="#5b80a5" d="m 0,0 H 111.34375 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
2418
+ "></path><path class="blocklyPathLight" stroke="#8ca6c0" d="m 0.5,0.5 H 110.84375 M 110.84375,0.5 M 0.5,29.5 V 18.5 m -7.36,-0.5 q -1.52,-5.5 0,-11 m 7.36,1 V 0.5 H 1
2419
+ "></path><text class="blocklyText" y="12.5" transform="translate(10,5)">pigeons</text><g transform="translate(71.34375,5)"><image height="20px" width="30px" xlink:href="/static/game/image/pigeon.svg" alt=""></image></g></g></svg>`;
2420
+
2421
+ const cowHTML = `<svg class="block_image"><g transform="translate(10,0)" <path="" class="blocklyPathDark" fill="#496684" d="m 0,0 H 93.40625 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
2422
+ "><path class="blocklyPath" stroke="none" fill="#5b80a5" d="m 0,0 H 93.40625 v 30 H 0 V 20 c 0,-10 -8,8 -8,-7.5 s 8,2.5 8,-7.5 z
2423
+ "></path><path class="blocklyPathLight" stroke="#8ca6c0" d="m 0.5,0.5 H 92.90625 M 92.90625,0.5 M 0.5,29.5 V 18.5 m -7.36,-0.5 q -1.52,-5.5 0,-11 m 7.36,1 V 0.5 H 1
2424
+ "></path><text class="blocklyText" y="12.5" transform="translate(10,5)">cows</text><g transform="translate(53.40625,5)"><image height="20px" width="30px" xlink:href="/static/game/image/Clarice.svg" alt=""></image></g></g></svg>`;
2425
+
2426
+ $("#cow_crossing_image").html(newType == ocargo.Cow.PIGEON ? pigeonHTML : cowHTML);
2172
2427
  }
2173
2428
 
2174
2429
  function sortNodes(nodes) {
@@ -2235,7 +2490,7 @@ ocargo.LevelEditor = function(levelId) {
2235
2490
  type: cowGroups[groupId].type}; //editor can only add white cow for now
2236
2491
  }
2237
2492
 
2238
- var coordinates = cows[i].controlledNode.coordinate;
2493
+ var coordinates = cows[i].coordinate;
2239
2494
  var strCoordinates = {'x':coordinates.x, 'y':coordinates.y};
2240
2495
  cowGroupData[groupId].potentialCoordinates.push(strCoordinates);
2241
2496
  }
@@ -2270,9 +2525,10 @@ ocargo.LevelEditor = function(levelId) {
2270
2525
  state.decor = ocargo.utils.sortObjects(state.decor, "z");
2271
2526
 
2272
2527
  // Destination and origin data
2273
- if (destinationNode) {
2274
- var destinationCoord = destinationNode.coordinate;
2275
- state.destinations = JSON.stringify([[destinationCoord.x, destinationCoord.y]]);
2528
+ if (houseNodes.length > 0) {
2529
+ state.destinations = JSON.stringify(houseNodes.map(function (houseNode) {
2530
+ return [houseNode.coordinate.x, houseNode.coordinate.y]
2531
+ }));
2276
2532
  }
2277
2533
  if (originNode) {
2278
2534
  var originCoord = originNode.coordinate;
@@ -2286,19 +2542,83 @@ ocargo.LevelEditor = function(levelId) {
2286
2542
 
2287
2543
  // Language data
2288
2544
  var language = $('#language_select').val();
2289
- state.blocklyEnabled = language === 'blockly' || language === 'both' || language === 'blocklyWithPythonView';
2290
- state.pythonViewEnabled = language === 'blocklyWithPythonView';
2291
- state.pythonEnabled = language === 'python' || language === 'both';
2545
+ state.blockly_enabled = language === 'blockly' || language === 'both' || language === 'blocklyWithPythonView';
2546
+ state.python_view_enabled = language === 'blocklyWithPythonView';
2547
+ state.python_enabled = language === 'python' || language === 'both';
2548
+
2549
+ const regex = /^[\w.?!', ]*$/;
2550
+ const subtitleValue = $('#subtitle').val();
2551
+ const descriptionValue = $('#description').val();
2552
+ const hintValue = $('#hint').val();
2553
+
2554
+ // Description and hint data
2555
+ if (subtitleValue.length > 0) {
2556
+ if (regex.exec(subtitleValue)) {
2557
+ state.subtitle = subtitleValue;
2558
+ }
2559
+ else {
2560
+ ocargo.Drawing.startPopup(
2561
+ "Oh no!",
2562
+ "You used some invalid characters for your level subtitle.",
2563
+ "Try saving your level again using only" +
2564
+ " letters, numbers and standard punctuation."
2565
+ );
2566
+ return
2567
+ }
2568
+ }
2569
+
2570
+ if (descriptionValue.length > 0) {
2571
+ if (regex.exec(descriptionValue)) {
2572
+ state.lesson = descriptionValue;
2573
+ }
2574
+ else {
2575
+ ocargo.Drawing.startPopup(
2576
+ "Oh no!",
2577
+ "You used some invalid characters for your level description.",
2578
+ "Try saving your level again using only" +
2579
+ " letters and numbers and standard punctuation."
2580
+ );
2581
+ return
2582
+ }
2583
+ }
2584
+
2585
+ if (hintValue.length > 0) {
2586
+ if (regex.exec(hintValue)) {
2587
+ state.hint = hintValue;
2588
+ }
2589
+ else {
2590
+ ocargo.Drawing.startPopup(
2591
+ "Oh no!",
2592
+ "You used some invalid characters for your level hint.",
2593
+ "Try saving your level again using only" +
2594
+ " letters and numbers and standard punctuation."
2595
+ );
2596
+ return
2597
+ }
2598
+ }
2292
2599
 
2293
2600
  // Other data
2294
2601
  state.theme = currentTheme.id;
2295
2602
  state.character = $('#character_select').val();
2603
+ state.disable_algorithm_score = true;
2296
2604
 
2297
2605
  return state;
2298
2606
  }
2299
2607
 
2300
2608
  function restoreState(state) {
2301
-
2609
+ console.log("restoring state");
2610
+
2611
+ // Get character id from saved character name
2612
+ var characterName = state.character_name;
2613
+ if (characterName) {
2614
+ var characterId = null;
2615
+ for (var id in CHARACTERS) {
2616
+ if (characterName == CHARACTERS[id].name) {
2617
+ characterId = id;
2618
+ break;
2619
+ }
2620
+ }
2621
+ }
2302
2622
  clear();
2303
2623
 
2304
2624
  // Load node data
@@ -2310,41 +2630,15 @@ ocargo.LevelEditor = function(levelId) {
2310
2630
  new InternalTrafficLight(trafficLightData[i]);
2311
2631
  }
2312
2632
 
2313
- if(COW_LEVELS_ENABLED) {
2314
- var cowGroupData = JSON.parse(state.cows);
2315
- for (var i = 0; i < cowGroupData.length; i++) {
2316
- // Add new group to group select element
2317
- if (i >= Object.keys(cowGroups).length) {
2318
- addCowGroup();
2319
- }
2320
- var cowGroupId = Object.keys(cowGroups)[i];
2321
- cowGroups[cowGroupId].minCows = cowGroupData[i].minCows;
2322
- cowGroups[cowGroupId].maxCows = cowGroupData[i].maxCows;
2323
- cowGroups[cowGroupId].type = cowGroupData[i].type;
2324
-
2325
- if (cowGroupData[i].potentialCoordinates != null) {
2326
- for (var j = 0; j < cowGroupData[i].potentialCoordinates.length; j++) {
2327
- var cowData = {
2328
- coordinates: [cowGroupData[i].potentialCoordinates[j]],
2329
- group: cowGroups[cowGroupId]
2330
- };
2331
- new InternalCow(cowData);
2332
- }
2333
- }
2334
- }
2335
-
2336
- // Trigger change listener on cow group select box to set initial min/max values
2337
- $('#cow_group_select').change();
2338
-
2339
- markCowNodes();
2340
- }
2341
-
2342
2633
  // Load in destination and origin nodes
2343
- // TODO needs to be fixed in the long term with multiple destinations
2344
2634
  if (state.destinations) {
2345
- var destination = JSON.parse(state.destinations)[0];
2346
- var destinationCoordinate = new ocargo.Coordinate(destination[0], destination[1]);
2347
- destinationNode = ocargo.Node.findNodeByCoordinate(destinationCoordinate, nodes);
2635
+ var houses = JSON.parse(state.destinations);
2636
+ var houseCoordinates = houses.map(function (house) {
2637
+ return new ocargo.Coordinate(house[0], house[1]);
2638
+ })
2639
+ houseNodes = houseCoordinates.map(function (houseCoord) {
2640
+ return ocargo.Node.findNodeByCoordinate(houseCoord, nodes);
2641
+ })
2348
2642
  }
2349
2643
 
2350
2644
  if (state.origin) {
@@ -2354,7 +2648,7 @@ ocargo.LevelEditor = function(levelId) {
2354
2648
  }
2355
2649
 
2356
2650
  // Load in character
2357
- $('#character_select').val(state.character);
2651
+ $('#character_select').val(characterId);
2358
2652
  $('#character_select').change();
2359
2653
 
2360
2654
  drawAll();
@@ -2383,6 +2677,36 @@ ocargo.LevelEditor = function(levelId) {
2383
2677
  PAPER_HEIGHT - currentTheme.decor[decor[i].decorName].height - decor[i].y + PAPER_PADDING);
2384
2678
  }
2385
2679
 
2680
+ //Load in cow data
2681
+ if(COW_LEVELS_ENABLED) {
2682
+ var cowGroupData = JSON.parse(state.cows);
2683
+ for (var i = 0; i < cowGroupData.length; i++) {
2684
+ // Add new group to group select element
2685
+ if (i >= Object.keys(cowGroups).length) {
2686
+ addCowGroup();
2687
+ }
2688
+ var cowGroupId = Object.keys(cowGroups)[i];
2689
+ cowGroups[cowGroupId].minCows = cowGroupData[i].minCows;
2690
+ cowGroups[cowGroupId].maxCows = cowGroupData[i].maxCows;
2691
+ cowGroups[cowGroupId].type = cowGroupData[i].type;
2692
+
2693
+ if (cowGroupData[i].potentialCoordinates != null) {
2694
+ for (var j = 0; j < cowGroupData[i].potentialCoordinates.length; j++) {
2695
+ var cowData = {
2696
+ coordinates: [cowGroupData[i].potentialCoordinates[j]],
2697
+ group: cowGroups[cowGroupId]
2698
+ };
2699
+ new InternalCow(cowData);
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ // Trigger change listener on cow group select box to set initial min/max values
2705
+ $('#cow_group_select').change();
2706
+
2707
+ markCowNodes();
2708
+ }
2709
+
2386
2710
  // Load in block data
2387
2711
  if(state.blocks) {
2388
2712
  for(var i = 0; i < BLOCKS.length; i++) {
@@ -2401,20 +2725,29 @@ ocargo.LevelEditor = function(levelId) {
2401
2725
  }
2402
2726
 
2403
2727
  // Load in language data
2404
- var languageSelect = $('#languageSelect');
2405
- if(state.blocklyEnabled && state.pythonEnabled) {
2728
+ var languageSelect = $('#language_select');
2729
+ if (state.blockly_enabled && state.python_view_enabled){
2730
+ languageSelect.val('blocklyWithPythonView');
2731
+ } else if(state.blockly_enabled && state.python_enabled) {
2406
2732
  languageSelect.val('both');
2407
- } else if(state.pythonEnabled) {
2733
+ } else if(state.python_enabled) {
2408
2734
  languageSelect.val('python');
2409
2735
  } else {
2410
2736
  languageSelect.val('blockly');
2411
2737
  }
2412
2738
  languageSelect.change();
2413
2739
 
2740
+ // Load in description and hint data
2741
+ $('#subtitle').val(state.subtitle);
2742
+ $('#description').val(state.lesson);
2743
+ $('#hint').val(state.hint);
2744
+
2414
2745
  // Other data
2415
2746
  if(state.max_fuel) {
2416
2747
  $('#max_fuel').val(state.max_fuel);
2417
2748
  }
2749
+
2750
+ needsApproval = state.needs_approval;
2418
2751
  }
2419
2752
 
2420
2753
  function loadLevel(levelID) {
@@ -2455,28 +2788,42 @@ ocargo.LevelEditor = function(levelId) {
2455
2788
  return false;
2456
2789
  }
2457
2790
  // Check to see if start and end nodes have been marked
2458
- if (!originNode || !destinationNode) {
2459
- var noStartOrEnd = interpolate(
2460
- gettext('In %(map_icon)s %(map_label)s menu, click on %(mark_start_icon)%(mark_start_label) or ' +
2461
- '%(mark_end_icon)s%(mark_end_label)s then select the square where you want the road to start or end.'
2791
+ if (!originNode) {
2792
+ var noStart = interpolate(
2793
+ gettext('In %(map_icon)s%(map_label)s menu, click on %(mark_start_icon)s%(mark_start_label)s ' +
2794
+ 'and then select the square where you want the road to start.'
2462
2795
  ), {
2463
2796
  map_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/map.svg', 'popupIcon'),
2464
2797
  map_label: '<b>' + gettext('Map') + '</b>',
2465
2798
  mark_start_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/origin.svg', 'popupIcon'),
2466
2799
  mark_start_label: '<b>' + gettext('Mark start') + '</b>',
2467
- mark_end_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/destination.svg', 'popupIcon'),
2468
- mark_end_label: '<b>' + gettext('Mark end') + '</b>'
2469
2800
  },
2470
2801
  true
2471
2802
  );
2472
- ocargo.Drawing.startPopup(gettext('Oh no!'), gettext('You forgot to mark the start and end points.'), noStartOrEnd);
2803
+ ocargo.Drawing.startPopup(gettext('Oh no!'), gettext('You forgot to mark the start point.'), noStart);
2473
2804
  return false;
2474
2805
  }
2475
2806
 
2476
- // Check to see if path exists from start to end
2477
- if (!areDestinationsReachable(originNode, [destinationNode], nodes)) {
2807
+ if (houseNodes.length === 0) {
2808
+ var noHouses = interpolate(
2809
+ gettext('In %(map_icon)s%(map_label)s menu, click on %(add_house_icon)s%(add_house_label)s ' +
2810
+ 'and then select the square(s) where you want to add houses for delivery.'
2811
+ ), {
2812
+ map_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/map.svg', 'popupIcon'),
2813
+ map_label: '<b>' + gettext('Map') + '</b>',
2814
+ add_house_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/add_house.svg', 'popupIcon'),
2815
+ add_house_label: '<b>' + gettext('Add house') + '</b>'
2816
+ },
2817
+ true
2818
+ );
2819
+ ocargo.Drawing.startPopup(gettext('Oh no!'), gettext('You forgot to mark the houses.'), noHouses);
2820
+ return false;
2821
+ }
2822
+
2823
+ // Check to see if path exists from start to each house
2824
+ if (!areDestinationsReachable(originNode, houseNodes, nodes)) {
2478
2825
  ocargo.Drawing.startPopup(gettext('Something is wrong...'),
2479
- gettext('There is no way to get from the start to the destination.'),
2826
+ gettext('There is no way to get from the start to all of your houses.'),
2480
2827
  gettext('Edit your level to allow the driver to get to the end.'));
2481
2828
  return false;
2482
2829
  }
@@ -2509,10 +2856,13 @@ ocargo.LevelEditor = function(levelId) {
2509
2856
 
2510
2857
  function canShare() {
2511
2858
  if (!saveState.isSaved()) {
2512
- ocargo.Drawing.startPopup(gettext('Sharing'), '', gettext('Please save your level before continuing!'));
2859
+ ocargo.Drawing.startPopup("Sharing", "", "Please save your level before continuing!");
2513
2860
  return false;
2514
2861
  } else if (hasLevelChangedSinceSave()) {
2515
- ocargo.Drawing.startPopup(gettext('Sharing'), '', gettext('Please save your latest changes!'));
2862
+ ocargo.Drawing.startPopup("Sharing", "", "Please save your latest changes!");
2863
+ return false;
2864
+ } else if (needsApproval) {
2865
+ ocargo.Drawing.startPopup("Sharing", "", "Your teacher hasn't approved your level so you can't share it yet. Please let your teacher know they need to approve it first.")
2516
2866
  return false;
2517
2867
  }
2518
2868
  return true;
@@ -2546,7 +2896,7 @@ ocargo.LevelEditor = function(levelId) {
2546
2896
 
2547
2897
  notLoggedInMessages.push(interpolate(gettext('You can log in as a %(student_login_url)s, '
2548
2898
  + '%(teacher_login_url)s or %(independent_login_url)s.'), {
2549
- student_login_url: '<a href="' + Urls.student_login() + '">' + pgettext('login_url', 'student') + '</a>',
2899
+ student_login_url: '<a href="' + Urls.student_login_access_code() + '">' + pgettext('login_url', 'student') + '</a>',
2550
2900
  teacher_login_url: '<a href="' + Urls.teacher_login() + '">' + pgettext('login_url', 'teacher') + '</a>',
2551
2901
  independent_login_url: '<a href="' + Urls.independent_student_login() + '">'
2552
2902
  + pgettext('login_url', 'independent student') + '</a>'
@@ -2575,7 +2925,7 @@ ocargo.LevelEditor = function(levelId) {
2575
2925
  // you can copy and paste into a Django migration file
2576
2926
  var state = extractState();
2577
2927
 
2578
- var boolFields = ["pythonEnabled", "blocklyEnabled", 'fuel_gauge', 'direct_drive'];
2928
+ var boolFields = ["python_enabled", "blockly_enabled", 'fuel_gauge'];
2579
2929
  var stringFields = ['path', 'traffic_lights', 'cows', 'origin', 'destinations'];
2580
2930
  var otherFields = ['max_fuel'];
2581
2931
 
@@ -2626,9 +2976,8 @@ ocargo.LevelEditor = function(levelId) {
2626
2976
  if (!this.valid) {
2627
2977
  throw "Error: cannot create actual cow from invalid internal cow!";
2628
2978
  }
2629
-
2630
2979
  // Where the cow is placed.
2631
- var coordinates = this.controlledNode.coordinate;
2980
+ var coordinates = this.coordinate;
2632
2981
  var strCoordinates= {'x':coordinates.x, 'y':coordinates.y};
2633
2982
 
2634
2983
  return { "coordinates": [strCoordinates],
@@ -2650,17 +2999,50 @@ ocargo.LevelEditor = function(levelId) {
2650
2999
 
2651
3000
  };
2652
3001
 
3002
+ this.isOnRoad = function() {
3003
+ return this.coordinate && ocargo.Node.findNodeByCoordinate(this.coordinate, nodes);
3004
+ }
3005
+
3006
+ this.updateTheme = function() {
3007
+ let newType = currentTheme == THEMES.city ? ocargo.Cow.PIGEON : ocargo.Cow.WHITE;
3008
+ let transformDimensions = this["image"]["_"]["transform"][0]
3009
+ let rotateDimensions = this["image"]["_"]["transform"][1]
3010
+ let x = transformDimensions[1]
3011
+ let y = transformDimensions[2]
3012
+ let r = 0;
3013
+ if (rotateDimensions) {
3014
+ r = rotateDimensions[1];
3015
+ }
3016
+
3017
+ this.image.remove();
3018
+
3019
+ this.image = drawing.createCowImage(newType);
3020
+ if (this.isOnRoad()) {
3021
+ let controlledNode = ocargo.Node.findNodeByCoordinate(coordinates, nodes);
3022
+ drawing.setCowImagePosition(this.coordinate, this.image, controlledNode);
3023
+ } else {
3024
+ this.image.transform("t" + x + "," + y + " r" + r);
3025
+ }
3026
+
3027
+ setupCowListeners(this);
3028
+ }
3029
+
2653
3030
  this.image = drawing.createCowImage(data.group.type);
2654
3031
  this.valid = false;
2655
3032
 
2656
-
2657
3033
  if ( data.coordinates && data.coordinates.length > 0 ) {
2658
- var coordinates = new ocargo.Coordinate(data.coordinates[0].x, data.coordinates[0].y);
2659
- this.controlledNode = ocargo.Node.findNodeByCoordinate(coordinates, nodes);
3034
+ this.coordinate = new ocargo.Coordinate(data.coordinates[0].x, data.coordinates[0].y);
3035
+ this.valid = isValidDraggedCowPlacement(this.coordinate, this);
2660
3036
 
2661
- if (this.controlledNode) {
2662
- this.valid = true;
2663
- drawing.setCowImagePosition(coordinates, this.image, this.controlledNode);
3037
+ if (this.isOnRoad()) {
3038
+ const controlledNode = ocargo.Node.findNodeByCoordinate(this.coordinate, nodes);
3039
+ drawing.setCowImagePosition(this.coordinate, this.image, controlledNode);
3040
+ } else {
3041
+ const box = this.image.getBBox();
3042
+ // calculate position of the image
3043
+ const paperX = (this.coordinate.x + 1) * GRID_SPACE_SIZE - box.width/2;
3044
+ const paperY = (GRID_HEIGHT - this.coordinate.y) * GRID_SPACE_SIZE - box.height/2;
3045
+ this.image.transform('t' + paperX + ',' + paperY );
2664
3046
  }
2665
3047
  } else {
2666
3048
  this.image.transform('...t' + (-paper.scrollLeft()) + ',' + paper.scrollTop());
@@ -2668,6 +3050,7 @@ ocargo.LevelEditor = function(levelId) {
2668
3050
 
2669
3051
  setupCowListeners(this);
2670
3052
  this.image.attr({'cursor':'pointer'});
3053
+ this.image.attr({'position': 'absolute'});
2671
3054
  cows.push(this);
2672
3055
 
2673
3056
  }
@@ -2797,7 +3180,7 @@ ocargo.LevelEditor = function(levelId) {
2797
3180
  /******************/
2798
3181
 
2799
3182
  $(function() {
2800
- var editor = new ocargo.LevelEditor(LEVEL);
3183
+ new ocargo.LevelEditor(LEVEL);
2801
3184
  var subtitle = interpolate(
2802
3185
  gettext('Click %(help_icon)s%(help_label)s for clues on getting started.'), {
2803
3186
  help_icon: ocargo.jsElements.image(ocargo.Drawing.imageDir + 'icons/help.svg', 'popupHelp'),
@@ -2808,24 +3191,16 @@ $(function() {
2808
3191
  if (LEVEL === null){
2809
3192
  ocargo.Drawing.startPopup(gettext('Welcome to the Level editor!'), subtitle, '');
2810
3193
  } else {
2811
- var optionAFunc = function(){
2812
- $(".ocargo-modal").hide();
2813
- $(".modal-overlay").hide();
2814
- };
2815
-
2816
- var optionBFunc = function(){
2817
- window.location.replace("/rapidrouter/custom/"+LEVEL+"/");
2818
- };
3194
+ let buttons = '';
3195
+ buttons += ocargo.button.dismissButtonHtml("edit_button", "Edit");
3196
+ buttons += ocargo.button.redirectButtonHtml("play_button", Urls.levels() + "custom/" + LEVEL, "Play");
2819
3197
 
2820
- ocargo.Drawing.startOptionsPopup(
3198
+ ocargo.Drawing.startPopup(
2821
3199
  gettext('Welcome back!'),
2822
3200
  gettext('Would you like to edit or play with your design?'),
2823
3201
  '',
2824
- optionAFunc,
2825
- optionBFunc,
2826
- "Edit",
2827
- "Play",
2828
- ''
3202
+ false,
3203
+ buttons,
2829
3204
  );
2830
3205
  }
2831
3206