split adaptive game (game object) and assistant
This commit is contained in:
parent
6946ffccc0
commit
ab2b6da3d3
|
@ -0,0 +1,241 @@
|
|||
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
|
||||
import yaml # reads level configurations from a .yml file
|
||||
|
||||
|
||||
class NoLevelFoundError(Exception):
|
||||
"""Error thrown by the Game class when a nonexistant level is selected."""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class Game:
|
||||
"""Class representing the adaptive game, its progress, setup+solving times.
|
||||
|
||||
An object of the class will remember the possible branching of the game,
|
||||
it will keep track of the level and branch the player is on,
|
||||
and it will save loading times and solving times of the player."""
|
||||
|
||||
def __init__(self, mapping_file, game_directory,
|
||||
level_number=0, level_branch=''):
|
||||
"""Create an object of class Game.
|
||||
|
||||
The game itself is NOT started at this point. To start it, call
|
||||
self.start_game() next.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mapping_file
|
||||
location of the .yml file with level mappings.
|
||||
game_directory
|
||||
directory with the game files (Vagrantfile, etc.)
|
||||
levelNumber
|
||||
number of level to start on
|
||||
(useful when continuing a running game)
|
||||
levelBranch
|
||||
branch of level being started on
|
||||
(useful when continuing a running game)"""
|
||||
self.game_directory = game_directory
|
||||
self.read_mapping(mapping_file)
|
||||
|
||||
self.load_times = []
|
||||
self.solving_times = []
|
||||
self.level_log = []
|
||||
|
||||
self.game_start_time = 0
|
||||
self.level_start_time = 0
|
||||
|
||||
self.level = level_number
|
||||
self.branch = level_branch
|
||||
|
||||
self.game_in_progress = False
|
||||
self.game_finished = False
|
||||
self.game_end_time = 0
|
||||
|
||||
def start_game(self):
|
||||
"""Start a new game, if there isn't one already in progress."""
|
||||
if (self.game_in_progress or self.game_finished):
|
||||
return False
|
||||
self.game_in_progress = True
|
||||
self.level = 1
|
||||
self.level_log.append("level1") # add level 1 to log
|
||||
start_time = time.time()
|
||||
subprocess.run(["vagrant", "up"], cwd=self.game_directory,
|
||||
env=dict(os.environ, ANSIBLE_ARGS='--tags \"setup\"'))
|
||||
end_time = time.time()
|
||||
load_time = int(end_time - start_time)
|
||||
self.load_times.append(load_time)
|
||||
self.level_start_time = time.time()
|
||||
self.game_start_time = time.time()
|
||||
return True
|
||||
|
||||
def finish_game(self):
|
||||
"""Mark game as not in progress and log end time, if on the last level.
|
||||
|
||||
Return false if prerequisites were not met, true otherwise."""
|
||||
if (self.next_level_exists() or not self.game_in_progress):
|
||||
return False
|
||||
self.solving_times.append(int(time.time() - self.level_start_time))
|
||||
self.game_end_time = time.time()
|
||||
self.game_in_progress = False
|
||||
self.game_finished = True
|
||||
return True
|
||||
|
||||
def next_level_exists(self):
|
||||
"""Return true if next level exists."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
return next_level in self.level_mapping
|
||||
|
||||
def next_level_is_forked(self):
|
||||
"""Return true if next level is forked."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
if next_level in self.level_mapping:
|
||||
if len(self.level_mapping[next_level]) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def next_level_branch_check(self, input_text):
|
||||
"""Check if `input_text` is a branch of next level, return next level branch name.
|
||||
|
||||
`input_text` can be a full level name, partial level name or just
|
||||
a branch letter. Ex. "level4a", "4a" and "a" are all fine."""
|
||||
next_level = "level" + str(self.level + 1)
|
||||
if "level" + str(self.level + 1) + input_text in self.level_mapping[next_level]:
|
||||
return input_text
|
||||
if "level" + input_text in self.level_mapping[next_level]:
|
||||
return input_text[1:]
|
||||
if input_text in self.level_mapping[next_level]:
|
||||
return input_text[6:]
|
||||
raise NoLevelFoundError("No branch called {} found.".format(input_text))
|
||||
|
||||
def next_level(self, next_branch_input=""):
|
||||
"""Advance the game to next level with branch `next_branch_input`.
|
||||
Because `next_branch_input` can be supplied by user, perform check
|
||||
if it is real first.
|
||||
|
||||
Throws NoLevelFoundError if there is no such level present.
|
||||
next_branch_input == '' is understood as no branch selected, a level
|
||||
without possible branching."""
|
||||
next_branch = ""
|
||||
if not self.game_in_progress:
|
||||
raise NoLevelFoundError("Can't continue, (S)tart the game first!")
|
||||
if not self.next_level_exists():
|
||||
raise NoLevelFoundError("No next level found! Perhaps you already finished the game?")
|
||||
if self.next_level_is_forked():
|
||||
next_branch = self.next_level_branch_check(next_branch_input)
|
||||
# throws NoLevelFoundError
|
||||
elif next_branch_input != "":
|
||||
raise NoLevelFoundError("Next level has no branch, but branch was given.")
|
||||
self.solving_times.append(int(time.time() - self.level_start_time))
|
||||
self.level += 1
|
||||
self.branch = next_branch
|
||||
level_name = "level" + str(self.level) + self.branch
|
||||
self.level_log.append(level_name)
|
||||
boxes = self.level_mapping["level" + str(self.level)][level_name]
|
||||
start_time = time.time()
|
||||
subprocess.run(["vagrant", "up"] + boxes + ["--provision"], cwd=self.game_directory,
|
||||
env=dict(os.environ, ANSIBLE_ARGS='--tags \"' + level_name + '\"'))
|
||||
end_time = time.time()
|
||||
load_time = int(end_time - start_time)
|
||||
self.load_times.append(load_time)
|
||||
self.level_start_time = time.time()
|
||||
|
||||
def abort_game(self):
|
||||
"""Abort game and reset attributes to their default state.
|
||||
|
||||
If a 'dumps' subfolder exists, also dumps the current game log
|
||||
to a file before aborting."""
|
||||
|
||||
if os.path.isdir("dumps"):
|
||||
self.dump_to_file("dumps/aborted_game" + str(time.time())) # TODO: maybe
|
||||
subprocess.run(["vagrant", "destroy", "-f"], cwd=self.game_directory)
|
||||
self.load_times = []
|
||||
self.solving_times = []
|
||||
self.level_log = []
|
||||
self.game_start_time = 0
|
||||
self.level_start_time = 0
|
||||
self.game_in_progress = False
|
||||
self.game_finished = False
|
||||
self.level = 0
|
||||
self.branch = ''
|
||||
# # # METHODS THAT WORK WITH FILES # # #
|
||||
def read_mapping(self, filename):
|
||||
"""Read a mapping of levels for the adaptive game from a YAML file."""
|
||||
|
||||
with open(filename) as f:
|
||||
self.level_mapping = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
def dump_to_file(self, filename):
|
||||
"""Dump the current game state and logs into a YAML file."""
|
||||
with open(filename, 'w') as f:
|
||||
yaml.dump(self, f)
|
||||
|
||||
# # # METHODS THAT OUTPUT INTO STDOUT # # #
|
||||
def print_next_level_fork_names(self):
|
||||
"""Print names of next level forks."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
print("Next level branches are:")
|
||||
if next_level in self.level_mapping:
|
||||
for branch in self.level_mapping[next_level]:
|
||||
print(branch, end=" ")
|
||||
print("")
|
||||
|
||||
def print_time(self, load_time, concise=False):
|
||||
"""Print time in minutes and seconds.
|
||||
|
||||
If concise is True, prints only numbers and letters."""
|
||||
|
||||
if load_time < 60:
|
||||
if concise:
|
||||
print("{}s".format(load_time))
|
||||
else:
|
||||
print("Time elapsed: {} seconds.".format(load_time))
|
||||
else:
|
||||
minutes = load_time // 60
|
||||
seconds = load_time % 60
|
||||
if concise:
|
||||
print("{}m{}s".format(minutes, seconds))
|
||||
else:
|
||||
print("Time elapsed: {} minutes, {} seconds.".format(minutes, seconds))
|
||||
|
||||
def print_info(self):
|
||||
"""Print info about the game in a human-readable way."""
|
||||
|
||||
if not self.game_in_progress and not self.game_finished:
|
||||
print("Game is not yet started.")
|
||||
else:
|
||||
if self.game_in_progress: # in progress implies not finished
|
||||
if self.branch:
|
||||
print("Game in progress. Level:{} Branch:{}".format(self.level, self.branch))
|
||||
else:
|
||||
print("Game in progress. Level:{}".format(self.level))
|
||||
print("Total time elapsed: ", end="")
|
||||
self.print_time(int(time.time() - self.game_start_time), True)
|
||||
else:
|
||||
print("Game succesfully finished.")
|
||||
print("Total time played: ", end="")
|
||||
self.print_time(int(self.game_end_time - self.game_start_time), True)
|
||||
|
||||
if self.level_log:
|
||||
print("Levels traversed:")
|
||||
for level in self.level_log:
|
||||
print("{} ".format(level), end="")
|
||||
print("")
|
||||
print("Loading times:")
|
||||
for i in range(len(self.load_times)):
|
||||
if i == 0:
|
||||
print("Setup + ", end="")
|
||||
print("{}: ".format(self.level_log[i]), end="")
|
||||
self.print_time(self.load_times[i], True)
|
||||
if self.solving_times:
|
||||
print("Solving times:")
|
||||
for i in range(len(self.solving_times)):
|
||||
print("{}: ".format(self.level_log[i]), end="")
|
||||
self.print_time(self.solving_times[i], True)
|
242
assistant.py
242
assistant.py
|
@ -1,246 +1,9 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from adaptive_game_module.adaptive_game import Game
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
|
||||
import yaml # reads level configurations from a .yml file
|
||||
|
||||
|
||||
class NoLevelFoundError(Exception):
|
||||
"""Error thrown by the Game class when a nonexistant level is selected."""
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class Game:
|
||||
"""Class representing the adaptive game, its progress, setup+solving times.
|
||||
|
||||
An object of the class will remember the possible branching of the game,
|
||||
it will keep track of the level and branch the player is on,
|
||||
and it will save loading times and solving times of the player."""
|
||||
|
||||
def __init__(self, mapping_file, game_directory,
|
||||
level_number=0, level_branch=''):
|
||||
"""Create an object of class Game.
|
||||
|
||||
The game itself is NOT started at this point. To start it, call
|
||||
self.start_game() next.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mapping_file
|
||||
location of the .yml file with level mappings.
|
||||
game_directory
|
||||
directory with the game files (Vagrantfile, etc.)
|
||||
levelNumber
|
||||
number of level to start on
|
||||
(useful when continuing a running game)
|
||||
levelBranch
|
||||
branch of level being started on
|
||||
(useful when continuing a running game)"""
|
||||
self.game_directory = game_directory
|
||||
self.read_mapping(mapping_file)
|
||||
|
||||
self.load_times = []
|
||||
self.solving_times = []
|
||||
self.level_log = []
|
||||
|
||||
self.game_start_time = 0
|
||||
self.level_start_time = 0
|
||||
|
||||
self.level = level_number
|
||||
self.branch = level_branch
|
||||
|
||||
self.game_in_progress = False
|
||||
self.game_finished = False
|
||||
self.game_end_time = 0
|
||||
|
||||
def start_game(self):
|
||||
"""Start a new game, if there isn't one already in progress."""
|
||||
if (self.game_in_progress or self.game_finished):
|
||||
return False
|
||||
self.game_in_progress = True
|
||||
self.level = 1
|
||||
self.level_log.append("level1") # add level 1 to log
|
||||
start_time = time.time()
|
||||
subprocess.run(["vagrant", "up"], cwd=self.game_directory,
|
||||
env=dict(os.environ, ANSIBLE_ARGS='--tags \"setup\"'))
|
||||
end_time = time.time()
|
||||
load_time = int(end_time - start_time)
|
||||
self.load_times.append(load_time)
|
||||
self.level_start_time = time.time()
|
||||
self.game_start_time = time.time()
|
||||
return True
|
||||
|
||||
def finish_game(self):
|
||||
"""Mark game as not in progress and log end time, if on the last level.
|
||||
|
||||
Return false if prerequisites were not met, true otherwise."""
|
||||
if (self.next_level_exists() or not self.game_in_progress):
|
||||
return False
|
||||
self.solving_times.append(int(time.time() - self.level_start_time))
|
||||
self.game_end_time = time.time()
|
||||
self.game_in_progress = False
|
||||
self.game_finished = True
|
||||
return True
|
||||
|
||||
def next_level_exists(self):
|
||||
"""Return true if next level exists."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
return next_level in self.level_mapping
|
||||
|
||||
def next_level_is_forked(self):
|
||||
"""Return true if next level is forked."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
if next_level in self.level_mapping:
|
||||
if len(self.level_mapping[next_level]) > 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def next_level_branch_check(self, input_text):
|
||||
"""Check if `input_text` is a branch of next level, return next level branch name.
|
||||
|
||||
`input_text` can be a full level name, partial level name or just
|
||||
a branch letter. Ex. "level4a", "4a" and "a" are all fine."""
|
||||
next_level = "level" + str(self.level + 1)
|
||||
if "level" + str(self.level + 1) + input_text in self.level_mapping[next_level]:
|
||||
return input_text
|
||||
if "level" + input_text in self.level_mapping[next_level]:
|
||||
return input_text[1:]
|
||||
if input_text in self.level_mapping[next_level]:
|
||||
return input_text[6:]
|
||||
raise NoLevelFoundError("No branch called {} found.".format(input_text))
|
||||
|
||||
def next_level(self, next_branch_input=""):
|
||||
"""Advance the game to next level with branch `next_branch_input`.
|
||||
Because `next_branch_input` can be supplied by user, perform check
|
||||
if it is real first.
|
||||
|
||||
Throws NoLevelFoundError if there is no such level present.
|
||||
next_branch_input == '' is understood as no branch selected, a level
|
||||
without possible branching."""
|
||||
next_branch = ""
|
||||
if not self.game_in_progress:
|
||||
raise NoLevelFoundError("Can't continue, (S)tart the game first!")
|
||||
if not self.next_level_exists():
|
||||
raise NoLevelFoundError("No next level found! Perhaps you already finished the game?")
|
||||
if self.next_level_is_forked():
|
||||
next_branch = self.next_level_branch_check(next_branch_input)
|
||||
# throws NoLevelFoundError
|
||||
elif next_branch_input != "":
|
||||
raise NoLevelFoundError("Next level has no branch, but branch was given.")
|
||||
self.solving_times.append(int(time.time() - self.level_start_time))
|
||||
self.level += 1
|
||||
self.branch = next_branch
|
||||
level_name = "level" + str(self.level) + self.branch
|
||||
self.level_log.append(level_name)
|
||||
boxes = self.level_mapping["level" + str(self.level)][level_name]
|
||||
start_time = time.time()
|
||||
subprocess.run(["vagrant", "up"] + boxes + ["--provision"], cwd=self.game_directory,
|
||||
env=dict(os.environ, ANSIBLE_ARGS='--tags \"' + level_name + '\"'))
|
||||
end_time = time.time()
|
||||
load_time = int(end_time - start_time)
|
||||
self.load_times.append(load_time)
|
||||
self.level_start_time = time.time()
|
||||
|
||||
def abort_game(self):
|
||||
"""Abort game and reset attributes to their default state.
|
||||
|
||||
If a 'dumps' subfolder exists, also dumps the current game log
|
||||
to a file before aborting."""
|
||||
|
||||
if os.path.isdir("dumps"):
|
||||
self.dump_to_file("dumps/aborted_game" + str(time.time())) # TODO: maybe
|
||||
subprocess.run(["vagrant", "destroy", "-f"], cwd=self.game_directory)
|
||||
self.load_times = []
|
||||
self.solving_times = []
|
||||
self.level_log = []
|
||||
self.game_start_time = 0
|
||||
self.level_start_time = 0
|
||||
self.game_in_progress = False
|
||||
self.game_finished = False
|
||||
self.level = 0
|
||||
self.branch = ''
|
||||
# # # METHODS THAT WORK WITH FILES # # #
|
||||
def read_mapping(self, filename):
|
||||
"""Read a mapping of levels for the adaptive game from a YAML file."""
|
||||
|
||||
with open(filename) as f:
|
||||
self.level_mapping = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
def dump_to_file(self, filename):
|
||||
"""Dump the current game state and logs into a YAML file."""
|
||||
with open(filename, 'w') as f:
|
||||
yaml.dump(self, f)
|
||||
|
||||
# # # METHODS THAT OUTPUT INTO STDOUT # # #
|
||||
def print_next_level_fork_names(self):
|
||||
"""Print names of next level forks."""
|
||||
|
||||
next_level = "level" + str(self.level + 1)
|
||||
print("Next level branches are:")
|
||||
if next_level in self.level_mapping:
|
||||
for branch in self.level_mapping[next_level]:
|
||||
print(branch, end=" ")
|
||||
print("")
|
||||
|
||||
def print_time(self, load_time, concise=False):
|
||||
"""Print time in minutes and seconds.
|
||||
|
||||
If concise is True, prints only numbers and letters."""
|
||||
|
||||
if load_time < 60:
|
||||
if concise:
|
||||
print("{}s".format(load_time))
|
||||
else:
|
||||
print("Time elapsed: {} seconds.".format(load_time))
|
||||
else:
|
||||
minutes = load_time // 60
|
||||
seconds = load_time % 60
|
||||
if concise:
|
||||
print("{}m{}s".format(minutes, seconds))
|
||||
else:
|
||||
print("Time elapsed: {} minutes, {} seconds.".format(minutes, seconds))
|
||||
|
||||
def print_info(self):
|
||||
"""Print info about the game in a human-readable way."""
|
||||
|
||||
if not self.game_in_progress and not self.game_finished:
|
||||
print("Game is not yet started.")
|
||||
else:
|
||||
if self.game_in_progress: # in progress implies not finished
|
||||
if self.branch:
|
||||
print("Game in progress. Level:{} Branch:{}".format(self.level, self.branch))
|
||||
else:
|
||||
print("Game in progress. Level:{}".format(self.level))
|
||||
print("Total time elapsed: ", end="")
|
||||
self.print_time(int(time.time() - self.game_start_time), True)
|
||||
else:
|
||||
print("Game succesfully finished.")
|
||||
print("Total time played: ", end="")
|
||||
self.print_time(int(self.game_end_time - self.game_start_time), True)
|
||||
|
||||
if self.level_log:
|
||||
print("Levels traversed:")
|
||||
for level in self.level_log:
|
||||
print("{} ".format(level), end="")
|
||||
print("")
|
||||
print("Loading times:")
|
||||
for i in range(len(self.load_times)):
|
||||
if i == 0:
|
||||
print("Setup + ", end="")
|
||||
print("{}: ".format(self.level_log[i]), end="")
|
||||
self.print_time(self.load_times[i], True)
|
||||
if self.solving_times:
|
||||
print("Solving times:")
|
||||
for i in range(len(self.solving_times)):
|
||||
print("{}: ".format(self.level_log[i]), end="")
|
||||
self.print_time(self.solving_times[i], True)
|
||||
|
||||
|
||||
def print_help():
|
||||
"""Print list of arguments that game_loop accepts."""
|
||||
|
@ -315,7 +78,6 @@ def check_prerequisites():
|
|||
else:
|
||||
print("NOK, VirtualBox version lower than 6 detected.")
|
||||
|
||||
|
||||
def game_loop():
|
||||
"""Interactively assist the player with playing the game.
|
||||
|
||||
|
|
Reference in New Issue