Mentions légales du service

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • CORSE/agdbentures
1 result
Show changes
Commits on Source (22)
Showing
with 318 additions and 77 deletions
......@@ -6,9 +6,16 @@ easytracker:
engine_versions:
python make_engine.py
tests:
tests: pytest leveltests
pytest:
@echo Unit testing with pytest
pytest -rxXs
leveltest:
@echo Testing levels
./tests/testLevels.py tests/
./tests/testLevels.py tutorial/
./tests/testLevels.py basic/
.PHONY: all easytracker tests
.PHONY: all easytracker tests pytest leveltest
......@@ -103,20 +103,16 @@ If not, install them using:
apt install libjpeg-dev
```
You can now install the requirements of Agdbentures using the
following command (this time in the `agdbentures` directory, not
`easytracker`'s submodule).
Agdbentures uses other python modules that are usually easily availaible in the
package manager of your distribution. If not, they can be installed in the
python virtual environment of Agdbentures.
```console
pip3 install --user -r requirements.txt
apt install python3-termcolor python3-yaml
```
Note: it may be necessary to install the requirements in a python venv (Virtual
Environment) depending on your distribution and whether your python packages
are managed by the distribution or by pip, for instance, in the agdbentures
root directory:
You can now install the requirements of Agdbentures in a virtual environment
for Agdbentures (this time in the `agdbentures` directory, not `easytracker`'s submodule).
```console
python3 -m venv .venv
......@@ -124,6 +120,14 @@ source .venv/bin/activate
pip3 install -r requirements.txt
```
If necessary (if the packages where not available in your system's package
manager), install the following python modules in the `venv`.
```console
pip3 install termcolor yaml
```
#### Note for MAC users
It may be necessary to install `easy_install`
......@@ -152,6 +156,22 @@ Normally, at this step, all should work correctly:
# Developping in Agdbentures
## Dependencies
To test Agdbentures, you will need `pytest`.
```console
apt install python3-pytest
```
or, in the venv:
```console
pip install pytest
```
## Getting ssh access
If you cloned Agdbentures using https:// urls it is more cumbersome to work on
......
#!/usr/bin/env python3
from level.level_abc import AbstractLevel
from loader.loader import load_level, run_level, test_level
import graphic.constants as cst
from utils import lvl
class Level(AbstractLevel):
""" pointer testing """
def test(self):
import tests.lib_test as T
self.recompile_bug()
T.expect_error("Cannot find variable player_y in local or global variables")
for _ in range(5):
T.cnext()
T.expect_sub_strings("DEFAITE")
T.expect_defeat()
self.run()
self.check_validation(
should_validate=False,
substr_reasons="player_x must be equal to exit_x"
)
if __name__ == "__main__":
# run_level(Level, __file__, level_type="text")
test_level(Level, __file__)
/* @AGDB
* Test what happens when some important variables are left out.
* (E.g., deleted by mistake by user)
*
* Here, there is no engine, so we should have player_y and player_direction.
*
* available_commands: next edit step
* engine_name: none
*
* arcade_maps: main ../../test.tmx
*/
#include <stdlib.h>
int player_x = 5;
int exit_x = 9;
int main() {
player_x++;
player_x++;
player_x++;
player_x++;
exit(EXIT_SUCCESS);
}
These levels test features related to gdb or easytracker.
E.g., there is/was a bug in 02_refactoring that prevented a solution using
pointers, hence the 'pointer' test checks that we are correctly handling
pointers.
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.8" tiledversion="1.8.2" orientation="orthogonal" renderorder="right-down" width="24" height="12" tilewidth="32" tileheight="32" infinite="0" backgroundcolor="#000000" nextlayerid="3" nextobjectid="4">
<tileset firstgid="1" source="../../resources/tiles/[Base]BaseChip_pipo.json"/>
<tileset firstgid="1065" source="../../resources/tiles/[A]Grass_pipo.json"/>
<tileset firstgid="1593" source="../../resources/tiles/[A]Water_pipo.json"/>
<layer id="1" name="floor" width="24" height="12">
<data encoding="csv">
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1093,1087,1087,1087,1087,1087,1087,1087,1087,1087,1094,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1080,1415,1415,1415,1415,1415,1415,1415,1415,1415,1078,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1101,1071,1071,1071,1071,1071,1071,1071,1071,1071,1102,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,
1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079,1079
</data>
</layer>
<layer id="2" name="decorations" width="24" height="12">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,829,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,837,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="3" name="decorations_top" width="24" height="12">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="4" name="walls" width="24" height="12">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="4" name="objects">
<object id="2" name="exit" x="431.333" y="177.333">
<point/>
</object>
<object id="3" name="start" x="174.667" y="176">
<point/>
</object>
</objectgroup>
</map>
#!/usr/bin/env python3
from level.level_abc import AbstractLevel
from lib.loader.loader import load_level, run_level, test_level
from loader.loader import load_level, run_level, test_level
class Level(AbstractLevel):
......
......@@ -2,8 +2,8 @@
from level.level_abc import AbstractLevel
from level.action import Action
from lib.loader.loader import load_level, run_level, test_level
from lib.utils import lvl
from loader.loader import load_level, run_level, test_level
from utils import lvl
class Level(AbstractLevel):
......
......@@ -2,7 +2,7 @@
from level.level_abc import AbstractLevel
from level.action import Action
from lib.loader.loader import load_level, run_level, test_level
from loader.loader import load_level, run_level, test_level
class Level(AbstractLevel):
......
......@@ -99,13 +99,21 @@ class Level(AbstractLevel):
def test(self):
import tests.lib_test as T
T.crun()
T.unexpect_agdb_string("run is not a valid command.")
# for _ in range(240):
# T.cnext()
self.recompile_bug()
T.ccontinue()
T.expect_defeat()
self.run()
self.recompile_answer()
T.ccontinue()
T.expect_victory()
self.run()
self.recompile_fix("straight")
T.ccontinue()
self.check_validation(should_validate=False,substr_reasons="Player strayed out of the path.")
self.run()
if __name__ == "__main__":
# run_level(Level, __file__, level_type="text")
......
......@@ -41,6 +41,9 @@
* arcade_maps: main ../tutorial.tmx
* arcade_maps: main_offset 38 0
*
* map_width: 100
* map_height: 9
*
* coordinates: wop_stairs_start wop_stairs_talks wop_too_long
*
* WOP: message intro
......
......@@ -405,7 +405,7 @@ class SpObject(arcade.Sprite, SpCoordinates):
appearing = False
if "visible" in payload:
if not self.visible and payload['visible']:
appearing = True # selfect has just appeared
appearing = True # object has just appeared
self.visible = payload["visible"]
if "direction" in payload:
......
......@@ -1498,13 +1498,13 @@ class GameView(EmptyView):
obj.append_action(payload)
return
if payload.get("talks", False):
if payload.get('talks', False):
log.error("Must not use the 'talks' field anymore")
return
log.debug(f"... move action for {obj}")
if 'action' not in payload:
payload['action'] = Action("move")
payload['action'] = Action('move')
obj.append_action(payload)
return
......
......@@ -267,7 +267,17 @@ class LevelArcade(AbstractLevel, Thread):
def update_time_played(self, start_time_level):
time_played = time.time() - start_time_level
ProgressionManager().change_time_played(self.level_name, int(time_played))
try:
ProgressionManager().change_time_played(self.level_name, int(time_played))
except IndexError:
lvl.error("Error while trying to save time. Time not saved.")
def update_number_restarts(self):
try:
ProgressionManager().add_restart(self.level_name)
except IndexError:
lvl.error("Error while trying to add a restart. Not added.")
def run(self):
"""
......@@ -288,7 +298,8 @@ class LevelArcade(AbstractLevel, Thread):
self.do_start()
start_time_level = time.time()
# pause_type = self.tracker.pause_reason.type
# We do not initialize to self.tracker.pause_reason as it would be
# a BREAKPOINT (temporary breakpoint at start of program)
pause_type = PauseReasonType.START
self.print_prompt()
......@@ -335,7 +346,7 @@ class LevelArcade(AbstractLevel, Thread):
if ret == 'restart':
do_restart_level = True
self.update_time_played(start_time_level)
ProgressionManager().add_restart(self.level_name)
self.update_number_restarts()
break
if ret == 'validate': # early launch validation
exit_code = 0
......@@ -355,7 +366,7 @@ class LevelArcade(AbstractLevel, Thread):
# If we arrive there, the inferior has exited, need to know
# if the level is validated or not
self.update_time_played(start_time_level)
if exit_code is None:
exit_code = self.tracker.exit_code
lvl.info(f"Inferior exited with {exit_code} as exit code.")
......@@ -404,7 +415,7 @@ class LevelArcade(AbstractLevel, Thread):
print(f"Return value: {ret}")
elif payload_type == 'control' and payload['command'] == 'restart':
ProgressionManager().add_restart(self.level_name)
self.update_number_restarts()
break
else:
lvl.debug(f"Payload received while level is finished: {payload}")
......@@ -47,20 +47,15 @@ class LevelTest(LevelText):
"""Run in test mode."""
T.print_info(f"Testing level {self.level_name}")
# try:
# self.prepare_tracker()
# except Exception as e:
# T.error("Cannot start tracker")
# T.print_error(e)
# return
self.do_start()
if not self.tracker:
T.error("Cannot start tracker")
return
# self.restart()
self.tracker.send_direct_command("start")
self.print_stdout()
pause_type = self.tracker.pause_reason.type
pause_type = PauseReasonType.START # mimick level_arcade
while pause_type != PauseReasonType.EXITED:
# Fetch variables
......@@ -103,6 +98,7 @@ class LevelTest(LevelText):
def apply_command(self, in_loop, dummy=False):
if dummy:
# Nothing to do, this is not a real command
cmd = T._Cmd.dummy
cmdstr = "dummy"
args = []
......@@ -111,8 +107,11 @@ class LevelTest(LevelText):
if self.debug:
print(f"Next command: {cmdstr} {args} {cmd} {in_loop=}")
# Check if command shoud be sent to gdb via the level interface
if cmd in T.gdb_exec_commands:
if not in_loop:
# We have a gdb command that manipulate execution (such as
# `next`), but the execution in finished.
T.error(f"Test exited but command found: {cmd} ({cmdstr})")
else:
try:
......
......@@ -4,7 +4,7 @@ from copy import deepcopy
from typing import Optional, Union
from level.objects import Object, Player, Wop, Enemy, Coordinates
from lib.utils import lvl
from utils import lvl
_TILES_DICT = {
"WATER": "~",
......
......@@ -28,6 +28,7 @@ _DIRECTION_DICT = {
"DIR_UP": cst.Direction.UP,
"DIR_DOWN": cst.Direction.DOWN,
"DIR_HERE": cst.Direction.HERE,
None: cst.Direction.RIGHT,
}
......@@ -72,14 +73,19 @@ def find_var_in_frames(frames, name):
return var
return None
def frames_then_global(memory, name):
def find_in_global(memory, name):
globs = memory["global_variables"]
if name not in globs:
lvl.error(f"Cannot find variable {name} in local or global variables")
return None
return globs[name].value
def frames_then_global(memory, name):
frames = memory["stack"]
var = find_var_in_frames(frames, name)
if var is not None:
return var
return globs[name].value
return find_in_global(memory, name)
def get_top_map(frames):
......@@ -255,9 +261,9 @@ class Object:
old_x = self.coord_x
old_y = self.coord_y
if self.var_x:
self.coord_x = global_vars[self.var_x].value
self.coord_x = find_in_global(memory, self.var_x)
if self.var_y:
self.coord_y = global_vars[self.var_y].value
self.coord_y = find_in_global(memory, self.var_y)
if old_x != self.coord_x or old_y != self.coord_y:
lvl.debug(f"Position changed for {self}")
self.has_changed = True
......@@ -376,7 +382,7 @@ class Player(Object):
if self.mode != "x_only":
self.coord_y = frames_then_global(memory, 'player_y')
self.direction = _DIRECTION_DICT[global_vars["player_direction"].value]
self.direction = _DIRECTION_DICT[find_in_global(memory, "player_direction")]
elif self.mode in ["simple_map", "simple_game"]:
if self.mode == "simple_map":
......@@ -411,9 +417,6 @@ class Player(Object):
else:
raise NotImplementedError
self.coord_x = global_vars["player_x"].value
self.coord_y = global_vars["player_y"].value
self.direction = global_vars["player_direction"].value
if old_x != self.coord_x or old_y != self.coord_y or old_dir != self.direction:
self.has_changed = True
......
......@@ -137,7 +137,7 @@ import subprocess
from collections import defaultdict
from typing import Optional
from os.path import dirname
from lib.utils import lvl
from utils import lvl
from verif_lib import VerifyCondition, VerifyMustCall, VerifyChangeInFunction
import graphic.constants as cst
from language import Lang
......@@ -395,6 +395,7 @@ def extract_metadata(source_file):
comments = strip_comments(source_file)
metadata = {
"exec_name": "main",
"alias": {
"player_x": "player_x",
"player_y": "player_y",
......@@ -568,7 +569,12 @@ def extract_metadata(source_file):
ident = "wop"
if tail.startswith("message"):
key, ident_message = tail.split()
try:
key, ident_message = tail.split()
except ValueError as e:
lvl.error(f"Too many parts while trying to parse message: {tail}")
raise e
strlang = key[7:] # get last part, EN or FR, if any
if not strlang:
......@@ -616,6 +622,10 @@ def extract_metadata(source_file):
metadata["level_path"] = level_path
metadata["level_name"] = level_name
if not metadata["exec_name"]:
lvl.error("The 'exec_name' metadata field cannot be empty")
exit(1)
return defaultdict(lambda: None, metadata)
......
......@@ -27,8 +27,10 @@ def load_level(level_specific: type, level_type: str = "text", level_in_queue=No
:param level_type: Visual interface that is either text, curses or arcade.
"""
level_cls = get_level(level_specific, level_type)
if level_type == "arcade":
raise NotImplementedError
## This part is deprecated, not used anymore to load the levels.
## Need to resurect it for pytest?
from level.arcade_utils.loader import StandaloneLoader
controler = StandaloneLoader(level_cls)
else: # Arcade
......
......@@ -19,7 +19,7 @@ PYTHON_COMMAND = "python3"
def check_init() -> bool:
"""check if the levels are initialized or not"""
if not os.path.exists(PROGRESS_FILE):
if not os.path.exists(PROGRESS_FILE) or os.stat(PROGRESS_FILE).st_size == 0:
return False
return True
......@@ -30,10 +30,18 @@ def find_executable(dirname) -> str:
return os.path.basename(path)
raise RuntimeError(f"Cannot find executable in {dirname}")
def get_category_name(level_name):
splt = level_name.split("/")
assert len(splt) == 2
return tuple(splt)
if len(splt) == 2:
return tuple(splt)
name = splt.pop()
cat = '/'.join(splt)
log.warning("Level has too many directories, things might break")
log.warning(f"\t(category: {cat} and name: {name})")
return (cat, name)
class HorizontalLevels:
"""A set of levels that can be done in parallel"""
......@@ -51,9 +59,17 @@ class HorizontalLevels:
def parse_progression(self, levels: list[dict]):
res = []
category,_ = get_category_name(self.levels[0])
category, _ = get_category_name(self.levels[0])
for level in levels:
res.append((category, level["name"], level["done"], level["time_played"], level["restarts"]))
res.append(
(
category,
level["name"],
level["done"],
level["time_played"],
level["restarts"],
)
)
self.dones = res
def set_done(self, level_name):
......@@ -64,8 +80,10 @@ class HorizontalLevels:
for i, level_path in enumerate(self.levels):
# log.debug(f"Checking line {level_name} vs {level_path}")
if level_name == level_path:
category,name = get_category_name(level_name)
self.dones = [(category, name, True, self.dones[i][3], self.dones[i][4])]
category, name = get_category_name(level_name)
self.dones = [
(category, name, True, self.dones[i][3], self.dones[i][4])
]
def is_done(self, level_name: str) -> bool:
"""
......@@ -73,7 +91,7 @@ class HorizontalLevels:
"""
for i, level_path in enumerate(self.levels):
if level_name == level_path:
return self.dones[0][2] # TODO this seems fishy...
return self.dones[0][2] # TODO this seems fishy...
return False
def make_levels(self):
......@@ -94,7 +112,11 @@ class HorizontalLevels:
log.debug(res.stderr)
def list_undone(self):
return [level for level, (_,_,done,_,_) in zip(self.levels, self.dones) if not done]
return [
level
for level, (_, _, done, _, _) in zip(self.levels, self.dones)
if not done
]
def all_done(self):
return all(done for (_, _, done, _, _) in self.dones)
......@@ -206,10 +228,12 @@ class ProgressionManager:
categories = []
for category in self.categories:
categories.append({
"name": category[0],
"levels": [],
})
categories.append(
{
"name": category[0],
"levels": [],
}
)
line = 0
for levels_line in self.progress:
......@@ -219,13 +243,15 @@ class ProgressionManager:
for category in categories:
category_name, level_name, is_done, time_played, restarts = level
if category_name == category["name"]:
category["levels"].append({
"name": level_name,
"done": is_done,
"line": line,
"time_played": time_played,
"restarts": restarts,
})
category["levels"].append(
{
"name": level_name,
"done": is_done,
"line": line,
"time_played": time_played,
"restarts": restarts,
}
)
break
line += 1
......@@ -327,7 +353,7 @@ class ProgressionManager:
force = Config.MODE == 'test'
# if the working directory already exists, no need to re-create the
# if the working directory already exists, no need to re-create the
# level, unless we are in test mode
if os.path.exists(compile_dir) and not force:
return
......@@ -340,7 +366,9 @@ class ProgressionManager:
elif Config.MODE == 'answer':
modes = ["--answer"]
elif Config.MODE == 'normal':
modes = ["--release"] # , "--answer"] does not seem useful to always also generate the answer
modes = [
"--release"
] # , "--answer"] does not seem useful to always also generate the answer
else:
log.error(f"Cannot make level, unknown mode '{Config.MODE}'")
return
......@@ -363,7 +391,7 @@ class ProgressionManager:
# compute the starting level number and the size of the category
start_n = 0
n = 0
for (cat_name, l) in self.categories:
for cat_name, l in self.categories:
if cat_name == category:
n = l
break
......@@ -381,7 +409,7 @@ class ProgressionManager:
def __iter__(self):
for levels in self.progress:
yield from zip(levels.levels, [done for (_,_,done,_,_) in levels.dones])
yield from zip(levels.levels, [done for (_, _, done, _, _) in levels.dones])
def all_done_category(self, category: str) -> bool:
"""Returns wether all the levels in the category are done"""
......@@ -399,16 +427,18 @@ class ProgressionManager:
return True
def _increment_field(self, level_name: str, field: str, incr: int):
def _increment_field(self, level_name: str, field: str, incr: int):
"""Increment a given field for a level in a json structure"""
category,name = get_category_name(level_name)
category, name = get_category_name(level_name)
json_data = json.load(open(PROGRESS_FILE))
categoryIndex = 0
while json_data["categories"][categoryIndex]["name"] != category:
categoryIndex += 1
levelIndex = 0
while json_data["categories"][categoryIndex]["levels"][levelIndex]["name"] != name:
while (
json_data["categories"][categoryIndex]["levels"][levelIndex]["name"] != name
):
levelIndex += 1
json_data["categories"][categoryIndex]["levels"][levelIndex][field] += incr
......@@ -422,3 +452,9 @@ class ProgressionManager:
def add_restart(self, level_name: str):
"""Add a restart to a level"""
self._increment_field(level_name, field='restarts', incr=1)
def get_time_played(self, level_name: str):
raise NotImplementedError
def get_restarts(self, level_name: str):
raise NotImplementedError