1
0
Fork 0

split adaptive game (game object) and assistant

This commit is contained in:
Lili (Tlapka) 2021-03-22 13:09:27 +01:00
parent 6946ffccc0
commit ab2b6da3d3
2 changed files with 243 additions and 240 deletions

View File

@ -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)

View File

@ -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.