Commit 7fec1ebd authored by Leo Peron's avatar Leo Peron
Browse files

d

parent 3923d25f
# HELLTAKER SOLVER #
Projet d'IA02 par Julie Pichon, Léo Peron, Adrien Simon.
"""
Version: 1.1.1
Auteur : Sylvain Lagrue <sylvain.lagrue@hds.utc.fr>
Ce module contient différentes fonction permettant de lire des fichiers Helltaker au format défini pour le projet et de vérifier des plans.
"""
from fileinput import filename
from pprint import pprint
import sys
from typing import List
def complete(m: List[List[str]], n: int):
for l in m:
for _ in range(len(l), n):
l.append(" ")
return m
def convert(grid: List[List[str]], voc: dict):
new_grid = []
for line in grid:
new_line = []
for char in line:
if char in voc:
new_line.append(voc[char])
else:
new_line.append(char)
new_grid.append(new_line)
return new_grid
def grid_from_file(filename: str, voc: dict = {}):
"""
Cette fonction lit un fichier et le convertit en une grille de Helltaker
Arguments:
- filename: fichier contenant la description de la grille
- voc: argument facultatif permettant de convertir chaque case de la grille en votre propre vocabulaire
Retour:
- un dictionnaire contenant:
- la grille de jeu sous une forme d'une liste de liste de (chaînes de) caractères
- le nombre de ligne m
- le nombre de colonnes n
- le titre de la grille
- le nombre maximal de coups max_steps
"""
grid = []
m = 0 # nombre de lignes
n = 0 # nombre de colonnes
no = 0 # numéro de ligne du fichier
title = ""
max_steps = 0
with open(filename, "r", encoding="utf-8") as f:
for line in f:
no += 1
l = line.rstrip()
if no == 1:
title = l
continue
if no == 2:
max_steps = int(l)
continue
if len(l) > n:
n = len(l)
complete(grid, n)
if l != "":
grid.append(list(l))
if voc:
grid = convert(grid, voc)
m = len(grid)
return {"grid": grid, "title": title, "m": m, "n": n, "max_steps": max_steps}
def check_plan(plan: str):
"""
Cette fonction vérifie que votre plan est valide/
Argument: un plan sous forme de chaîne de caractères
Retour : True si le plan est valide, False sinon
"""
valid = "hbgd"
for c in plan:
if c not in valid:
return False
return True
def test():
if len(sys.argv) != 2:
sys.exit(-1)
filename = sys.argv[1]
pprint(grid_from_file(filename, {"H": "@", "B": "$", "D": "."}))
print(check_plan("erfre"))
print(check_plan("hhbbggdd"))
if __name__ == "__main__":
test()
import sys
from helltaker_utils import grid_from_file, check_plan
from collections import namedtuple
from typing import Dict, List, Tuple, Callable, Set
Action = namedtuple("action", ("verb", "direction"))
actionNames = ["move", "push"]
# "unlock", "pushBlock", "pushMob", "killMob"
actions = {d: [] for d in "hbgd"}
for d in "hbgd":
for a in actionNames:
actions[d].append(Action(a, d))
State = namedtuple(
"state",
("hero", "steps", "blocks", "key", "lock", "mobs", "safeTraps", "unsafeTraps"),
)
# ALL UTILS
# FROZEN SET (for immuable)
def add_in_frozenset(fset : frozenset, elt : Tuple[int, int]) -> frozenset:
s = {x for x in fset}
s.add(elt)
return frozenset(s)
def remove_in_frozenset(fset : frozenset,elt : Tuple[int, int]) -> frozenset:
s = {x for x in fset}
s.remove(elt)
return frozenset(s)
# DICT 2 PATH (print)
def dict2path(s: State, d: Dict[State, Tuple[State, Action]]) -> List[str]:
l = [(s, None)]
while not d[s] is None:
parent, a = d[s]
l.append((parent, a.direction))
s = parent
l.reverse()
return l
# PILE / FILES UTILS
def insert_tail(s, l):
l.append(s)
return l
def remove_head(l):
return l.pop(0), l
def remove_tail(l):
return l.pop(), l
# PARSING ELEMENTS TO FLUENTS AND NON FLUENTS
def parsingInfos(grid: List[List[str]], maxstep: int) -> Tuple[Dict[str, set], State]:
hero = None
blocks = set()
key = None
lock = None
mobs = set()
safeTraps = set()
unsafeTraps = set()
map_rules = { 'D': set(), 'S': set(), '#': set()}
for x, subgrid in enumerate(grid):
for y, c in enumerate(subgrid):
if c in "DS#O":
if c == "O":
c = "S"
map_rules[c].add((x, y))
elif c == "H":
hero = (x, y)
elif c in ["B", "P", "Q"]:
blocks.add((x, y))
elif c in ["K"]:
key = (x, y)
elif c in ["L"]:
lock = (x, y)
elif c in ["M"]:
mobs.add((x, y))
elif c in ["T", "P"]:
safeTraps.add((x, y))
elif c in ["U", "Q"]:
unsafeTraps.add((x, y))
state = State(hero, maxstep, frozenset(blocks), key, lock, frozenset(mobs), frozenset(safeTraps), frozenset(unsafeTraps))
return map_rules, state
# MOVING ONE STEP
def one_step(position: Tuple[int, int], direction: str) -> Tuple[int, int]:
x, y = position
return {"d": (x, y + 1), "g": (x, y - 1), "h": (x - 1, y), "b": (x + 1, y)}[
direction
]
# COPY AND MODIFY STATE
def modify_state_factory(state: State) -> Callable:
def mod(
hero=state.hero,
steps=state.steps,
blocks=state.blocks,
key=state.key,
lock=state.lock,
mobs=state.mobs,
safeTraps=state.safeTraps,
unsafeTraps=state.unsafeTraps,
):
return State(hero, steps, blocks, key, lock, mobs, safeTraps, unsafeTraps)
return mod
def moving_frozenset(fset: frozenset, supr: Tuple[int, int], new: Tuple[int, int]):
raw = {x for x in fset}
raw.remove(supr)
raw.add(new)
return frozenset(raw)
# CHECK IF FREE
def free_factory(map_rules):
def free(position):
return not ((position in map_rules["#"]) or (position in map_rules["D"]))
return free
def updating_state(map_rules: Dict[str, List[int]], state: State, newHero: List[int], mobs:List[Tuple[int,int]]=[], blocks:List[Tuple[int,int]]=[]):
# KILLINGS MOBS
newMobs = []
if mobs :
newMobs=list(mobs)
else:
newMobs = list(state.mobs)
for mob in newMobs:
if (mob in state.safeTraps) or (mob in map_rules["S"]):
newMobs.remove(mob)
# MOVING BLOCKS IF NECESSARY
newBlocks = []
if blocks:
print(blocks)
newBlocks = list(blocks)
else:
newBlocks = state.blocks
# DEFINE COST
cost = 0
if (newHero in state.safeTraps) or (newHero in map_rules["S"]):
cost = 2
else:
cost = 1
# GAME OVER
if state.steps - cost < 0:
return None
# SWITCHING TRAPS, UPDATING MOBS AND STEPS
else:
return modify_state_factory(state)(
hero=newHero,
safeTraps=state.unsafeTraps,
unsafeTraps=state.safeTraps,
mobs=frozenset(newMobs),
steps=state.steps - cost,
blocks=frozenset(newBlocks)
)
def do_inplace(action: Action, state: State, map_rules: Dict[str, set]):
hero = state.hero
goal = map_rules["D"]
blocks = state.blocks
mobs = state.mobs
spikes = map_rules["S"]
safeTraps = state.safeTraps
unsafeTraps = state.unsafeTraps
key = state.key
lock = state.lock
newHero = one_step(hero, action.direction)
# DEFINES FACTORIES
free = free_factory(map_rules)
if action.verb == "move":
if free(newHero) and not (newHero in mobs | blocks):
return updating_state(map_rules, state, newHero)
if action.verb == "push":
newPose = one_step(newHero, action.direction)
if (newHero in blocks | mobs) and free(newPose) and not (newPose in mobs | blocks) and not newPose == lock and not newPose == goal:
if newHero in blocks:
return updating_state(map_rules, state, newHero, blocks=moving_frozenset(blocks, newHero, newPose))
if newHero in mobs:
return updating_state(map_rules, state, newHero, mobs=moving_frozenset(mobs, newHero, newPose))
return None
# DEFINING GOAL
def goal_factory(map_rules: Dict[str, set]) -> Callable[[State], bool]:
def goals(state: State):
offsets = [(0, 1), (1, 0), (0, -1), (-1, 0)]
goalsCases = [ (state.hero[0] + x[0], state.hero[1] + x[1]) for x in offsets]
for demon in map_rules['D']:
if demon in goalsCases:
return True
return False
return goals
# DEFINING SUCCESSORS
def succ_factory(map_rules: Dict[str, set]) -> Set[Tuple[State, Action]]:
def succ(state):
l = []
for x in actions.values():
for a in x:
l.append((do_inplace(a, state, map_rules), a))
return {el[0]: el[1] for el in l}
return succ
# SEARCH ALGO
def search_with_parent(s0, goals, succ, remove, insert, debug=True):
l = [s0]
save = {s0: None}
s = s0
while l:
s, l = remove(l)
for s2, a in succ(s).items():
#if debug:
# print("S=", s2)
# print("A=", a)
if not s2:
continue
if not s2 in save:
save[s2] = (s, a)
if goals(s2):
return s2, save
insert(s2, l)
return None, save
def main():
# récupération du nom du fichier depuis la ligne de commande
filename = sys.argv[1]
# récupération de al grille et de toutes les infos
infos = grid_from_file(filename)
map_rules, s0 = parsingInfos(infos["grid"], infos["max_steps"])
end, save = search_with_parent(s0, goal_factory(map_rules), succ_factory(map_rules), remove_head, insert_tail, debug=False)
# for s, _ in dict2path(end, save):
# if s:
# print(s)
# calcul du plan
if(end):
plan = ''.join([a for _, a in dict2path(end, save) if a])
if check_plan(plan):
print("[OK]", plan)
else:
print("[Err]", plan, file=sys.stderr)
else:
print("Pas de solution trouvée")
sys.exit(2)
def testing():
mobs = set()
mobs.add((3,4))
mobs.add((1,7))
mobs.add((2,9))
frozen = frozenset(mobs)
print(moving_frozenset(frozen, (3,4), (7,6)))
pass
if __name__ == "__main__":
main()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment