Compare commits

...

7 Commits

Author SHA1 Message Date
Ninluc d6e2fc7be9 Minimum performance delta 2026-11-03 08:30:41 +01:00
Ninluc 3d2f98d56e Added tunnel mix map 2026-11-03 08:30:28 +01:00
Ninluc 0fd490b97c Score weights Rebalancing 2026-10-31 17:00:21 +01:00
Ninluc 104297e88b Added intermediate maps 2026-10-31 17:00:12 +01:00
Ninluc 104c6fac63 Better performance score 2026-10-31 14:50:25 +01:00
Ninluc a7a3d973a3 Play all maps each generation
Also disabled some too hard maps
2026-10-26 15:53:27 +01:00
Ninluc f56b8c1e9c Better scoring and map selection + Map delta 2026-10-23 17:54:13 +02:00
20 changed files with 699 additions and 70 deletions
+57 -10
View File
@@ -22,7 +22,7 @@ any map when no such map exists.
The score is from 0 to 100: The score is from 0 to 100:
`40 * coin_progress + 40 * distance_progress + 20 * won` `50 * coin_progress + 10 * distance_progress + 40 * won`
where `coin_progress` is collected coins divided by the map's total coins, where `coin_progress` is collected coins divided by the map's total coins,
`distance_progress` is progress from the spawn point to the finish, and `won` `distance_progress` is progress from the spawn point to the finish, and `won`
@@ -37,21 +37,29 @@ are requested separately on every turn.
``` ```
Every model gets a separate `Game` instance and can run in parallel. Valid Every model gets a separate `Game` instance and can run in parallel. Valid
requests. Valid moves are `left_down`, `left`, `left_up`, `up`, `right_up`, moves are `left_down`, `left`, `left_up`, `up`, `right_up`,
`right`, and `right_down`. Send `null` for a model when it has no more moves; `right`, and `right_down`. Send `null` for a model when it has no more moves;
that model is stopped for the current generation. that model is stopped for the current generation.
The response identifies the selected map and returns the initial state: The initial response is a `move_request`. It contains the selected map and the
initial state of every model. The map is sent only in this initial response;
clients should cache it for the rest of the simulation.
```json ```json
{ {
"ok":true, "ok":true,
"type":"move_request", "type":"move_request",
"map":{"name":"default","difficulty":30.0,"layout":[["stone", "stone"]]}, "map":{"name":"default","difficulty":30.0,"layout":[["stone", "stone"]]},
"results":{"model-a":{"won":false,"coins":0,"moves":0,"position":[1,4],"finished":false},"model-b":{"won":false,"coins":0,"moves":0,"position":[1,4],"finished":false}} "results":{"model-a":{"won":false,"coins":0,"moves":0,"position":[1,4],"finished":false,"changes":[]},"model-b":{"won":false,"coins":0,"moves":0,"position":[1,4],"finished":false,"changes":[]}}
} }
``` ```
After receiving this response, send one move for every model:
```json
{"type":"step","moves":{"model-a":"right","model-b":"right_up"}}
```
## Advance one turn ## Advance one turn
The client must send exactly one move for every model on each turn. The server The client must send exactly one move for every model on each turn. The server
@@ -62,10 +70,49 @@ predict the next moves.
{"type":"step","moves":{"model-a":"right","model-b":null}} {"type":"step","moves":{"model-a":"right","model-b":null}}
``` ```
The response is `move_request` with the selected map and updated per-model states, asking the The response is another `move_request` containing the updated state and score
client for the next move of every model. When every model has finished, the for every model. Each model also receives `changes`, a cumulative list of
response type becomes `simulation_result` and includes the best model's blocks that differ from the base map. For example, a collected coin is sent as
server-computed `score`, which is used to choose the next training map. `{"position":[4,7],"type":null}`. Unchanged turns return an empty list. It
asks the client for the next move of every unfinished model. A model that has
no more moves can send `null`:
The result score is `1000` for a win, plus `10` per coin, minus the number of ```json
moves. Malformed requests receive `{"ok":false,"error":"..."}`. {"type":"step","moves":{"model-a":"right","model-b":null}}
```
When every model has finished, the response type becomes `simulation_result`.
It contains the final state and score for every model, plus the best score used
to choose the next training map:
```json
{
"ok":true,
"type":"simulation_result",
"results":{
"model-a":{
"won":true,
"coins":3,
"moves":18,
"position":[23,1],
"finished":true,
"changes":[{"position":[4,7],"type":null}],
"score":92
},
"model-b":{
"won":false,
"coins":1,
"moves":7,
"position":[8,4],
"finished":true,
"changes":[],
"score":24
}
},
"score":92
}
```
The score is calculated by the server using the weights described above. The
top-level `score` is the best model score for the completed generation.
Malformed requests receive `{"ok":false,"type":"error","error":"..."}`.
+4
View File
@@ -0,0 +1,4 @@
# Améliorations
- Pénaliser les mouvements impossibles : intéressant, même pour l'algo génétique, pourrait optimiser le genome
- Pénaliser le fait de terminer la simulation sur une case où l'on est déjà passé ou pénaliser de revenir sur une case où l'on est passé
+138 -46
View File
@@ -24,6 +24,7 @@ of moves to be executed in a single game simulation, based on the map min_moves
""" """
import json import json
import math
import os import os
import random import random
import socketserver import socketserver
@@ -43,12 +44,19 @@ from game.mapSet import MapSet
class AiHttpGameHandler(GameHandler): class AiHttpGameHandler(GameHandler):
DEFAULT_HOST = "127.0.0.1" DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8765 DEFAULT_PORT = 8765
VALIDATION_DIFFICULTY = 70 VALIDATION_DIFFICULTY = 90
REPLAY_FRAME_DELAY = 0.1 REPLAY_FRAME_DELAY = 0.1
# Scoring weights TOTAL MUST BE 100 # Scoring weights TOTAL MUST BE 100
COINS_WEIGHT = 40 COINS_WEIGHT = 50
DISTANCE_WEIGHT = 20 STEPS_WEIGHT = 20
WIN_WEIGHT = 40 DISTANCE_WEIGHT = 10
WIN_WEIGHT = 20
PLAY_ALL_MAPS_PER_GENERATION = False # Are all maps played during a generation or only MAPS_PER_GENERATION maps per generation are played
MAPS_PER_GENERATION = 3 # Evaluate across x maps per generation. Only used if PLAY_ALL_MAPS_PER_GENERATION is False. If True, all maps are played in a generation.
MAP_PASS_MIN_SCORE = 20 # Minimum score to consider a map passed. Only used if PLAY_ALL_MAPS_PER_GENERATION is False. If True, all maps are played in a generation.
MAP_DIFFICULTY_VARIANCE = 5 # Lower value will make the map selection more variable, higher value will make the map selection more consistent with the current performance. Only used if PLAY_ALL_MAPS_PER_GENERATION is False. A value too high may produce no evolving behavior
CONSISTENCY_PENALTY_WEIGHT = 0.4 # Penalty for inconsistent map scores
MINIMUM_PERFORMANCE_DELTA = 0.05 # Minimum performance delta to add every generation. This ensures that the map will always change even if the ai is stuck in a local optimum. Only used if PLAY_ALL_MAPS_PER_GENERATION is False. A value too high may produce no evolving behavior
def __init__(self, host: str | None = None, port: int | None = None, def __init__(self, host: str | None = None, port: int | None = None,
max_workers: int | None = None): max_workers: int | None = None):
@@ -62,6 +70,10 @@ class AiHttpGameHandler(GameHandler):
game_map for game_map in self.map_set.maps game_map for game_map in self.map_set.maps
if game_map.difficulty >= self.VALIDATION_DIFFICULTY if game_map.difficulty >= self.VALIDATION_DIFFICULTY
] or list(self.map_set.maps) ] or list(self.map_set.maps)
if (self.COINS_WEIGHT + self.DISTANCE_WEIGHT + self.STEPS_WEIGHT + self.WIN_WEIGHT) != 100:
raise ValueError("Scoring weights must sum to 100.")
def run(self) -> None: def run(self) -> None:
handler = self handler = self
@@ -73,6 +85,10 @@ class AiHttpGameHandler(GameHandler):
"best_run": None, "best_run": None,
"replay_condition": Condition(), "replay_condition": Condition(),
"client_connected": True, "client_connected": True,
"stagnant_generations": 0,
"map_round": 0,
"cumulative_scores": {},
"last_results": {}
} }
replay_thread = Thread(target=handler._replay_best_runs, args=(session,)) replay_thread = Thread(target=handler._replay_best_runs, args=(session,))
replay_thread.start() replay_thread.start()
@@ -81,12 +97,9 @@ class AiHttpGameHandler(GameHandler):
for raw_request in self.rfile: for raw_request in self.rfile:
try: try:
request = json.loads(raw_request) request = json.loads(raw_request)
# print(f"AI request received: {request.get('type')} : {request}", flush=True)
response = handler._handle_request(request, session) response = handler._handle_request(request, session)
except (json.JSONDecodeError, TypeError, ValueError, KeyError) as error: except (json.JSONDecodeError, TypeError, ValueError, KeyError) as error:
response = {"ok": False, "type": "error", "error": str(error)} response = {"ok": False, "type": "error", "error": str(error)}
# print(f"AI request error: {error}", flush=True)
# print(f"AI response sent: {response.get('type')}", flush=True)
compact_response = json.dumps(response, separators=(",", ":")) compact_response = json.dumps(response, separators=(",", ":"))
self.wfile.write((compact_response + "\n").encode("utf-8")) self.wfile.write((compact_response + "\n").encode("utf-8"))
self.wfile.flush() self.wfile.flush()
@@ -128,15 +141,30 @@ class AiHttpGameHandler(GameHandler):
raise ValueError("Simulation requests must belong to a TCP session.") raise ValueError("Simulation requests must belong to a TCP session.")
if session.get("simulations"): if session.get("simulations"):
raise ValueError("A simulation is already active in this session.") raise ValueError("A simulation is already active in this session.")
session["generation"] = session.get("generation", 0) + 1
selected_map = self._select_map(session.get("performance", 0.0), validation)
session["last_result"] = None
# print( # Initialize generation round tracking and select 3 maps upfront if starting fresh
# f"Selected map for simulation: {selected_map.name} " if session.get("map_round", 0) == 0:
# f"(difficulty: {selected_map.difficulty})", session["generation"] = session.get("generation", 0) + 1
# flush=True, session["map_scores"] = {model.get("id"): [] for model in models}
# )
# Pre-select the 3 maps for this entire generation based on current performance
if validation:
session["generation_maps"] = [
random.choice(self.validation_maps)
for _ in range(self.MAPS_PER_GENERATION)
]
else:
perf = session.get("performance", 0.0)
if self.PLAY_ALL_MAPS_PER_GENERATION:
session["generation_maps"] = self.map_set.get_maps()
else:
session["generation_maps"] = sorted(self.map_set.get_maps_by_difficulty(round(perf), self.MAPS_PER_GENERATION), key=lambda m: m.difficulty)
# Fetch the pre-selected map for the current round
map_round = session.get("map_round", 0)
selected_map = session["generation_maps"][map_round]
session["last_result"] = None
simulations = {} simulations = {}
for model in models: for model in models:
@@ -146,9 +174,7 @@ class AiHttpGameHandler(GameHandler):
if model_id in simulations: if model_id in simulations:
raise ValueError(f"Duplicate model id '{model_id}'.") raise ValueError(f"Duplicate model id '{model_id}'.")
simulation = AiSimulation(selected_map) simulation = AiSimulation(selected_map)
# print(f"Starting simulation for model '{model_id}'", flush=True)
simulation.start() simulation.start()
# print(f"Model '{model_id}' is ready for its first move", flush=True)
simulations[model_id] = simulation simulations[model_id] = simulation
session["simulations"] = simulations session["simulations"] = simulations
@@ -165,7 +191,6 @@ class AiHttpGameHandler(GameHandler):
raise ValueError("No simulation is active. Send a 'simulate' request first.") raise ValueError("No simulation is active. Send a 'simulate' request first.")
if not session.get("simulations"): if not session.get("simulations"):
if session.get("last_result") is not None: if session.get("last_result") is not None:
print("Repeated step after simulation completion; returning final result", flush=True)
return session["last_result"] return session["last_result"]
raise ValueError("No simulation is active. Send a 'simulate' request first.") raise ValueError("No simulation is active. Send a 'simulate' request first.")
moves = request.get("moves") moves = request.get("moves")
@@ -189,7 +214,7 @@ class AiHttpGameHandler(GameHandler):
simulation = simulations[model_id] simulation = simulations[model_id]
# print(f"Starting step for model '{model_id}'", flush=True) # print(f"Starting step for model '{model_id}'", flush=True)
result = simulation.step(move) result = simulation.step(move)
result["score"] = self._score_result(simulation, result) result["score"] = self._score_simulation(simulation, result)
# print(f"Finished step for model '{model_id}'", flush=True) # print(f"Finished step for model '{model_id}'", flush=True)
return model_id, result return model_id, result
@@ -198,23 +223,70 @@ class AiHttpGameHandler(GameHandler):
# print(f"Completed step for models: {list(moves)}", flush=True) # print(f"Completed step for models: {list(moves)}", flush=True)
all_finished = all(result["finished"] for result in results.values()) all_finished = all(result["finished"] for result in results.values())
response = {"ok": True, "type": "move_request", "results": results} response = {"ok": True, "type": "move_request", "results": results}
if all_finished: if all_finished:
response["type"] = "simulation_result" session["map_round"] += 1
best_score = max(result["score"] for result in results.values())
best_model_id = max(results, key=lambda model_id: results[model_id]["score"]) # Accumulate scores for this map round
response["score"] = best_score for model_id, res in results.items():
session["performance"] = best_score session["map_scores"][model_id].append(res["score"])
session["last_result"] = response
session["simulations"] = None # Check if we have completed all maps for this generation
session["best_run"] = ( if session["map_round"] < len(session["generation_maps"]):
deepcopy(session["map"]), # More maps remaining in this generation! Tell client to simulate next map.
list(simulations[best_model_id].moves), response["type"] = "map_round_complete"
session["generation"], response["map_round"] = session["map_round"]
) session["simulations"] = None
replay_condition = session.get("replay_condition") session["last_result"] = response
if replay_condition is not None: else:
with replay_condition: # All maps completed -> finalize generation results (Average scores)
replay_condition.notify_all() response["type"] = "simulation_result"
final_results = {}
# Calculate final scores for each model based on their score on every map
for model_id, scores in session["map_scores"].items():
# Calculate Mean Squared Error from perfection (100)
squared_errors = [(100.0 - score) ** 2 for score in scores]
mse = sum(squared_errors) / len(squared_errors)
rmse = math.sqrt(mse)
# Final score penalizes variance/drops sharply based on RMSE
final_score = round(max(0.0, min(100.0, 100.0 - rmse)), 2)
# Attach final structural properties from the last results set
final_results[model_id] = deepcopy(results[model_id])
final_results[model_id]["score"] = final_score
response["results"] = final_results
best_score = max(res["score"] for res in final_results.values())
best_model_id = max(final_results, key=lambda mid: final_results[mid]["score"])
response["score"] = best_score
# --- Performance Update ---
# Average score of the 90th percentile of the final results
sorted_scores = sorted((res["score"] for res in final_results.values()), reverse=True)
top_90_count = max(1, round(len(sorted_scores) * 0.9))
top_90_scores = sorted_scores[:top_90_count]
avg_top_90_score = sum(top_90_scores) / len(top_90_scores)
performance_delta = (avg_top_90_score - self.MAP_PASS_MIN_SCORE) / self.MAP_DIFFICULTY_VARIANCE
performance_delta = max(self.MINIMUM_PERFORMANCE_DELTA, performance_delta)
session["performance"] = max(0.0, session.get("performance", 0.0) + performance_delta)
# print(f"Generation {session['generation']} completed on difficulty {map_difficulty}. Best score: {best_score:.2f}. Updated performance: {session['performance']:.2f}", flush=True)
# Reset round counter for the next generation
session["map_round"] = 0
session["last_result"] = response
session["simulations"] = None
session["best_run"] = (
deepcopy(session["map"]),
list(simulations[best_model_id].moves),
session["generation"],
session["performance"],
)
replay_condition = session.get("replay_condition")
if replay_condition is not None:
with replay_condition:
replay_condition.notify_all()
return response return response
def _replay_best_runs(self, session: dict) -> None: def _replay_best_runs(self, session: dict) -> None:
@@ -231,7 +303,7 @@ class AiHttpGameHandler(GameHandler):
return return
self._show_best_run(*best_run) self._show_best_run(*best_run)
def _show_best_run(self, game_map, moves: list[str], generation: int) -> None: def _show_best_run(self, game_map, moves: list[str], generation: int, performance: float) -> None:
game = Game(game_map, GameState()) game = Game(game_map, GameState())
remaining_moves = iter(moves) remaining_moves = iter(moves)
@@ -247,11 +319,15 @@ class AiHttpGameHandler(GameHandler):
frame_lines = [] frame_lines = []
for index, line in enumerate(game_string.splitlines()): for index, line in enumerate(game_string.splitlines()):
if index == 0: if index == 0:
line += "\tCoins: " + str(game.gameState.coins)
elif index == 1:
line += "\tMoves: " + str(game.gameState.movesCount)
elif index == 2:
line += "\tGeneration: " + str(generation) line += "\tGeneration: " + str(generation)
elif index == 1:
line += "\tPerformance: " + str(round(performance, 2))
elif index == 2:
line += "\tCoins: " + str(game.gameState.coins)
elif index == 3:
line += "\tMoves: " + str(game.gameState.movesCount)
elif index == 4:
line += "\tMove: " + moves[game.gameState.movesCount - 1] if len(moves) >= game.gameState.movesCount else "\tMove: None"
frame_lines.append(line) frame_lines.append(line)
print("\033[2J\033[H\n\n\n" + "\n".join(frame_lines), flush=True) print("\033[2J\033[H\n\n\n" + "\n".join(frame_lines), flush=True)
sleep(self.REPLAY_FRAME_DELAY) sleep(self.REPLAY_FRAME_DELAY)
@@ -264,20 +340,34 @@ class AiHttpGameHandler(GameHandler):
results = {} results = {}
for model_id, simulation in simulations.items(): for model_id, simulation in simulations.items():
result = simulation.result() result = simulation.result()
result["score"] = self._score_result(simulation, result) result["score"] = self._score_simulation(simulation, result)
results[model_id] = result results[model_id] = result
return results return results
def _select_map(self, performance: float, validation: bool): def _select_map(self, performance: float, validation: bool):
if validation: if validation:
return random.choice(self.validation_maps) return random.choice(self.validation_maps)
return self.map_set.get_maps_by_difficulty(round(performance), 1)[0] maps = self.map_set.get_maps_by_difficulty(round(performance), 2)
return random.choice(maps)
def _score_result(self, simulation: AiSimulation, result: dict) -> int: def _score_simulation(self, simulation: AiSimulation, result: dict) -> int:
if (self.COINS_WEIGHT + self.DISTANCE_WEIGHT + self.WIN_WEIGHT) != 100:
raise ValueError("Scoring weights must sum to 100.")
game_map = simulation.game.map game_map = simulation.game.map
raw_score = self._score_result(game_map, result)
max_score = self._get_map_max_score(game_map)
if max_score == 0:
return 0
percentage = (raw_score / max_score) * 100.0
return round(max(0.0, min(100.0, percentage)))
def _get_map_max_score(self, game_map: Map) -> int:
result = {}
result["position"] = game_map.get_first_block_type_position("finish")
result["coins"] = game_map.count_blocks_of_type("coin")
result["won"] = True
result["moves"] = game_map.min_moves
return self._score_result(game_map, result)
def _score_result(self, game_map: Map, result: dict) -> int:
finish = game_map.get_first_block_type_position("finish") finish = game_map.get_first_block_type_position("finish")
spawn = game_map.get_first_block_type_position("spawn_point") spawn = game_map.get_first_block_type_position("spawn_point")
total_coins = game_map.count_blocks_of_type("coin") total_coins = game_map.count_blocks_of_type("coin")
@@ -290,11 +380,13 @@ class AiHttpGameHandler(GameHandler):
distance_progress = 1.0 if start_distance == 0 else max( distance_progress = 1.0 if start_distance == 0 else max(
0.0, min(1.0, 1.0 - remaining_distance / start_distance) 0.0, min(1.0, 1.0 - remaining_distance / start_distance)
) )
steps_progress = 0.0 if result["moves"] == 0 else min(1.0, 1.0 - result["moves"] / game_map.min_moves)
coin_progress = 0.0 if total_coins == 0 else min(1.0, result["coins"] / total_coins) coin_progress = 0.0 if total_coins == 0 else min(1.0, result["coins"] / total_coins)
return round( return round(
self.COINS_WEIGHT * coin_progress self.COINS_WEIGHT * coin_progress
+ self.DISTANCE_WEIGHT * distance_progress + self.DISTANCE_WEIGHT * distance_progress
+ self.STEPS_WEIGHT * steps_progress
+ self.WIN_WEIGHT * result["won"] + self.WIN_WEIGHT * result["won"]
) )
+11
View File
@@ -14,6 +14,7 @@ class AiSimulation:
def __init__(self, game_map: Map): def __init__(self, game_map: Map):
self.game = Game(deepcopy(game_map), GameState()) self.game = Game(deepcopy(game_map), GameState())
self._base_map_types = self.game.map.to_type_dict()
min_moves = self.game.map.get_min_moves() min_moves = self.game.map.get_min_moves()
self.max_moves = min_moves * 2 if min_moves is not None else None self.max_moves = min_moves * 2 if min_moves is not None else None
self._condition = Condition() self._condition = Condition()
@@ -65,8 +66,18 @@ class AiSimulation:
"moves": state.movesCount, "moves": state.movesCount,
"position": list(state.get_player_position()), "position": list(state.get_player_position()),
"finished": state.stopGame, "finished": state.stopGame,
"changes": self._get_map_changes(),
} }
def _get_map_changes(self) -> list[dict]:
current_map_types = self.game.map.to_type_dict()
changes = []
for y, row in enumerate(current_map_types):
for x, block_type in enumerate(row):
if block_type != self._base_map_types[y][x]:
changes.append({"position": [x, y], "type": block_type})
return changes
def close(self) -> None: def close(self) -> None:
self.stop_for_generation() self.stop_for_generation()
+8 -2
View File
@@ -71,6 +71,12 @@ class Map:
def to_type_dict(self): def to_type_dict(self):
"""Returns a matrix containing block types and None for empty spaces.""" """Returns a matrix containing block types and None for empty spaces."""
return [ return [
[block.type if block is not None else None for block in row] [self._visible_block_type(block) for block in row]
for row in self.layout for row in self.layout
] ]
@staticmethod
def _visible_block_type(block):
if block is None or getattr(block, "collected", False):
return None
return block.type
+3
View File
@@ -21,6 +21,9 @@ class MapSet:
raise IndexError("Map index out of range.") raise IndexError("Map index out of range.")
return self.maps[index] return self.maps[index]
def get_maps(self):
return sorted(self.maps, key=lambda m: m.difficulty)
def get_maps_by_difficulty(self, desired_difficulty: int, num_maps: int): def get_maps_by_difficulty(self, desired_difficulty: int, num_maps: int):
""" """
Returns a list of maps with difficulty levels as close as possible to the desired difficulty level. Returns a list of maps with difficulty levels as close as possible to the desired difficulty level.
+1 -1
View File
@@ -18,7 +18,7 @@ layout = """
## ° ###^######## ## ° ###^########
## # ########## ## # ##########
#S ##^##### #S ##^#####
######### ##########
""" """
# Ya moyen d'optimiser le trajet sur les surfaces plates en faisant des sauts au lieu de marcher normalement # Ya moyen d'optimiser le trajet sur les surfaces plates en faisant des sauts au lieu de marcher normalement
+24
View File
@@ -0,0 +1,24 @@
difficulty = 85
min_moves = 40
layout = """
#####
###### ########
## ####
### °E##
### ° °#####
##°°° ° °° ° ° °## ######
#°####### ## # # ### ######
# #° °°° ##
## ##°° ° ##°° ^###
# ###°° ####° ##
# #####^#####°° ##
# #####°####° #
# ° °°°°# #
# ° °°# #####^^##
## ° °###^########
## °# ##########
#S °##^#####
##########
"""
# Big_peanut avec des pièces partout (HAARRRRRR !)
+50
View File
@@ -14,6 +14,56 @@ layout = """
#000000000000000000000000000000KKKKKKKKKKKKKKKKKKXXKKKKKXXXXNWMMNNXXXNNNNNNNNWWWWWMMMMMMMMMMMMMMMMMMM #000000000000000000000000000000KKKKKKKKKKKKKKKKKKXXKKKKKXXXXNWMMNNXXXNNNNNNNNWWWWWMMMMMMMMMMMMMMMMMMM
#0000000000000000000000000000000KKKKKKKKKKKKKKKKKXWWXKKKKXXXNNWNXXXXXXNNNNNNNMWWWMMMMMMMMMMMMMMMMMMMM #0000000000000000000000000000000KKKKKKKKKKKKKKKKKXWWXKKKKXXXNNWNXXXXXXNNNNNNNMWWWMMMMMMMMMMMMMMMMMMMM
#00000000000000000000000000000000KKKKKKKKKKKKKKKKXMMNKKKKXXXXXXXXXXXXXXNNNNNNNWWWWMMMMMMMMMMMMMMMMMM0 #00000000000000000000000000000000KKKKKKKKKKKKKKKKXMMNKKKKXXXXXXXXXXXXXXNNNNNNNWWWWMMMMMMMMMMMMMMMMMM0
+2 -2
View File
@@ -1,7 +1,7 @@
difficulty = 20 difficulty = 30
min_moves = 28 min_moves = 28
layout = """ layout = """
# # # ° ° ° ° ° ° ° ° ° ° ° #
#E # #E #
##########################°^# ##########################°^#
# #°#°# #°#°# #°#°#°### # #°#°# #°#°# #°#°#°###
@@ -1,4 +1,4 @@
difficulty = 10 difficulty = 25
min_moves = 12 min_moves = 12
layout = """ layout = """
######## ########
@@ -8,7 +8,7 @@ layout = """
# ## # ##
# ### # ###
# ## # ##
# ## ## # °## ##
# ### ## # ### ##
# ° # # ° #
# ^ # # ^ #
+1 -1
View File
@@ -1,4 +1,4 @@
difficulty = 13 difficulty = 20
min_moves = 24 min_moves = 24
layout = """ layout = """
E## E##
+1 -1
View File
@@ -1,4 +1,4 @@
difficulty = 15 difficulty = 10
min_moves = 14 min_moves = 14
layout = """ layout = """
@@ -1,6 +1,7 @@
difficulty = 60 difficulty = 70
min_moves = 6 min_moves = 6
layout = """ layout = """
# #
# S # # S #
# ##### # # ##### #
# # # #
+1 -1
View File
@@ -1,4 +1,4 @@
difficulty = 0 difficulty = 5
min_moves = 11 min_moves = 11
layout = """ layout = """
# # # #
+9
View File
@@ -0,0 +1,9 @@
difficulty = 7
min_moves = 11
layout = """
# #
#E S#
#########################
"""
# Teach to not only go right
+3 -3
View File
@@ -1,18 +1,18 @@
difficulty = 40 difficulty = 40
min_moves = 6 min_moves = 8
layout = """ layout = """
#^ ^# #^ ^#
#^ S^# #^ S^#
#^ #^# #^ #^#
#^ ^# #^ ^#
#^# ^# #^# ^#
#^ ^# #^ °^#
#^ #^# #^ #^#
#^# ^# #^# ^#
#^ #^# #^ #^#
#^# ^# #^# ^#
#^ ^# #^ ^#
#^ ^# #^ °^#
#^ #^# #^ #^#
#^ ^# #^ ^#
#^ ^# #^ ^#
+128
View File
@@ -0,0 +1,128 @@
difficulty = 70
min_moves = 16
layout = """
#####################
# ° ° #
# ## ## #
# °### °### #
# #### #### #
#S ##### ##### E#
#####################
"""
#000000000000000000000000000000KKKKKKKKKKKKKKKKKKXXKKKKKXXXXNWMMNNXXXNNNNNNNNWWWWWMMMMMMMMMMMMMMMMMMM
#0000000000000000000000000000000KKKKKKKKKKKKKKKKKXWWXKKKKXXXNNWNXXXXXXNNNNNNNMWWWMMMMMMMMMMMMMMMMMMMM
#00000000000000000000000000000000KKKKKKKKKKKKKKKKXMMNKKKKXXXXXXXXXXXXXXNNNNNNNWWWWMMMMMMMMMMMMMMMMMM0
#OOO0000000000000000000000000000000KKK00KKKKKKKKKKNWXKKKKXXXXXXXXXNNXXXNNNNNNNWWWWMMMMMMMMNX0OOXMMMOl
#OOOO00000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXXXNWNXXXNNNNNNWWWWWMMMMMMNkK0xKMMMNdl
#OOOO000000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXNWWNXXXXNNNNNNWWWWMMMMMMNXMNKXWWWOol
#OOO00000KKKKKKKXXXXKKK000000000000000000KKKKKKKKKKXXXXXKKXXKKXXNWWWWXXXXXNNNNNWWWWWMMMMMMMOkNWWWWdlc
#OO00KXNNNNWWWWWWWWWNKK0000000000000000000KKKKKOdcokooONNNNNXXKXNWWWWNXXXXNXNNNWWWWWWMMMMMMOdXWWWXolc
#OO0KKKNWNNNNNXXXXKKK00000000000000000000KKKKKd',,coc,'codOXWXXXNWWWWWXXXXXNXNNNWWWWWWMMMMMXx0NNNXool
#OOO00000000000000000000000000000000000KKKK0o,',.',,,;;cl:oookKXNWWWNNXXXXXNNNNNWWWWWWMMMMMMxxXNXKlll
#OOOOOO000000000000000000000000000000000KKKO:''',,;:ccloddkKOxokXNWWNK0XKXKx0KXNNWWWWWWWMMMM0d0K00ocl
#OOOOOOO00000000000000000000000000000000KK0x;'..';cccccoookO00OOXXXKKKKKKK0dkKXNNWNNWWWWWMMMNdkKK0xc:
#OOOOOO00000000000000000000000000000000KKKO,...';cllcclldxxkO0OkXK0000K0KKKOo0KXNNNNNWWWWWMMMkkkdl:cc
#0O0OOOO00000000000000000000000000000000KK0:...,:lol::::clooodxOXK000K000K0OoO0KXXNNNNWWWWX0x:,:c::::
#0OO0OOOO000000000000000000000000000000000O:,;';llc:,''.';lo.;cOXK000000000OdoOO0KXNNNWWWN;;::;::::::
#00OOOOOOOOOOO0000000000000000000000000000Oo';,cllccccc:;;:xddk0XK000000000000KKKKlXNNNNNOc::;;::::::
#00OOOOOOOOOOOO0000000000000000000000000000x,,;cllcc:cl:',,cdxk0XKKKKK0KK00000000KKKKOx0Oxd:;;;::;;;;
#x00OOOOOOOOOOOOOOOO00000000000000000000000kc..colc:c::;,';cdoxKXXXKK000OOOOOOOO000000dOxxk,,,,;;;;;;
#xO00OOOOOOOOOOOOOOOOOOO00000000000000000000, cllc:c;'.;odddddkO0K00000OOOkkkkkkxxddddxxcOc',,;;;;;;
#Od000OOOOOOOOOOOOOOOOOO000000000000K0KKKXO'. cllccc:;;:oddooookO000OOkxdoolccccloddxkOK0xk',,;;;,,;
#0kx000OOOOOOOO0000000000000000000KKKKKKKKx.. .:c:;:;'.,ccclldxxxkO0kl::ccllodxxkOOO000KXXx,,,,,,,,,
#00kkOO000000000OOOOO000000KKK000000000000d'... ..'......cclooddddxkOOOkdddxxkkkkOOO0000KXXk,',,,,,,,
#00OocokOkkOOOO000KKK000000000Okkxxxxxxdo:.. ....'......:o;oxdllooddxkOOOkxxkkkkkkkOOOO0KXOlc'''',,,,
#000Oxk00KK0000000000000000Oxllodddddol:..'. .;;,,,,'..,:cclxxkkocllodxkkOOkxxxxxxkkkOO00dlOl........
#00000000000000000000OOOOOkollloddxxxxd:;;x,',:ccc:::;,,:lolcddxxxdolooxxkOOkkxxxxxxkkOkcclc,:;,,....
#000000000000000OOkxxddlccllloooodxxddc....:ooccllllol:,,lddo:loddxxxoloooxO0Okxdddoool:c:c:clldo,,,,
#000000OOkxxdolccccllllolllloooddddocc....,dkxl:lxxxxxoc,;lool;:oxkkkxoloodxOK0Oxol:,',:ccllllllllloo
#kxdoolcclloodddddxxxxxxoooooddddddc,ccxl:dxxxx::kkkkkko;':lloooccooxkxooooxkOKK0dcc;.:....lllllllloo
#loddxxxxkkkxxxxxxxxxxxxloooooddxxo:..'c,lxxxddlldk00Okxc,,codxxlo:l::':cooodkO0KKOo:.,....:lllllllll
#kkkkkkkkkkkkkkkkxxxxxxxollolllodoc' .cxoc::locck000Oxc;':dxkxcO:d;;,. ;loodkO0XNN0l.'',,:cccccllll
#kkkkkkkkkkkkkxxxxxxxxxxol;dl',:l;.. .,,;...',;dl:dkOOxl;'codd:,..',,;;' ;loodk0KNWWN0c'cccccccccccl
#kkkkkkkkkkkkkxxxxxxxxxl:,,;cllooc, ,d...;:;;....,;..;ll:;,,:'........':, :lood0XXNWWWNklccc::cccccc
#kkkkkkkkkkkkkxxxxddddd:cclloooddoc'.: .:ldd:'....,,,;;.,;';:;',,;,'.... .coood0NNXNNNXxccc:::::ccc
#kkkkkkkkkkkkxxxxddddooclllloodddoc. ..'.,'..',,;clolcc:cooc;,.....'. 'looldOXXXXX0xlc:::::::::
#kkkkkkkkkkxxxxdddoooooclllooddddol' .......',:loddddolc;cdddooc;'.. .;. ,clllldxkOOOko::::::::::
#kkkkkkkxxxxxddddoooollcllooodxxdol' .';:::clloooddxxddlc:lxxkxdoc;,. ,. .,ccccccllodxx:::::;;;;;
#kkkkkkxxxxxdddoooolllllllooodxxxdo; .';:ccccllooddddxxxxoc:lxkxxxxlc:'. . .,;:;:::coo:;;;;;;:cll
#kkkkxxxxxdddooolllllllclllooodxxxkxdl,. ';:clllllloodxxxxxxdll;:xkkkxdolc,. .'.',,,,;...:llllc;,,
#kkkxxxxddddooollllllllcclllloodddxkkkkkoc;cllllcllodxxxkkOkxdxd:dO000OOxxdo:. .,,,...';:clc;;,,,,,,
#kkxxxxdddoooolllllllllc:cclllloooddxxxkxxoccllllloodddxxxkxdoc:cdlloodxxkO00k, ..:cccc:;;,,,,,,,,,,,
#kxxxxddooooollllclccccc:,,;:ccllloooooccoddoc;;cllcc:;,,oc''',,lkkd:'..'l,.:ll:cc;,,,,,,,,,,,,,,,,,,
#xxxxxddooollllccccccccccc:;.,;;::ccllc:ccccc:lxkcclc, ...;lddxO0Oddd:;;:::;,,,,,,,',,',,,',,,,,,,,
#xxxdddooollllccccccccccc::;;...,;:::;.;,,'';c;,ldkkkxxdolodxdxkxxdddoc:,,,,,''''''''''''''''''',,,,,
#xxdddoolllllccccccc::::::;;. .;;,,. ..;',clooddddxxddxdolc:,,'''''''''''''''''''''''''''''''''
#dddooolllllccccc:::::::;;,. .;:,. ..'.... .;:cllooooddo:,'''''''''''''''''''''''''''''''''''''''
#dddoolllllcccccc:::::;;;' .;;..,:looodddol:'..';::cccc'....'''.'''....'....''....''''''...''''''
#dooollllccccc::::::;;;;. .. ';,':oodddxxxxxxkkxo;. ................................ ...''''''
#ooolllccc:::::::;;;;;;. ';;codddxxxxxxxxkkkkkxl. .................................... ...''''''
#ollccccc:::::::;;;;;;, ;cloddxxxxkkxkkkkkkkxoc;;::;;;,.........................................
#lllcccc:::::;;;;;;;;;' .coodddxxxxkkkkkkkkxxd';::::::::.........................................
#lcccc::;;;;;;;;;;;,,,' clooddxxxxxkkkkkkkkxdd:.;:::::::;........................................
#cccc::;;;;;;;;,,,,,,,,. :llodddxxxxkkOOOOkkxddoc. ,::cc:;,...................................
#c::::;;;;;;,,,,,,,,'''' ;lloooddxxxxkOOOOOkkxdooc. .:c;.. ............................
#:::;;,,,,,,,,,,'''''''. .cloooddxxxxkkOOOOkxxdool; .:cc' ....................... ...
#::;;,,,,,,,,,,'''''.. ;ooooddxxxkkOOOOkxxddoolc' . .;cc;................. ......
#:;;,',,''''''''''.. ,ooooddxxxxkkOkxxddoolllc:. . ,clc........... . ......,:
#;;,'''''''''''''. :oooooooddxxkkkxddoollccc:'. . 'cc, ......;:cl
#;,'''''''''''.. 'lolllllooooddxxddoollcccc:,. .. .cc . .. .,;cccl
#;'.'''...''.. 'coollllllooooooooollcc:::::,. . .c. . . ..';:ccll
#.''........ ,loolllllllooooooollcc::::::;,. . ..';::cccl
+131
View File
@@ -0,0 +1,131 @@
difficulty = 80
min_moves = 28
layout = """
####################
########## E#####
######### ##########
########## ########
########### #
############ #### #
# ## ###
# #### ^^^ ####
# ##### ### #####
#S###### ######
####################
"""
#000000000000000000000000000000KKKKKKKKKKKKKKKKKKXXKKKKKXXXXNWMMNNXXXNNNNNNNNWWWWWMMMMMMMMMMMMMMMMMMM
#0000000000000000000000000000000KKKKKKKKKKKKKKKKKXWWXKKKKXXXNNWNXXXXXXNNNNNNNMWWWMMMMMMMMMMMMMMMMMMMM
#00000000000000000000000000000000KKKKKKKKKKKKKKKKXMMNKKKKXXXXXXXXXXXXXXNNNNNNNWWWWMMMMMMMMMMMMMMMMMM0
#OOO0000000000000000000000000000000KKK00KKKKKKKKKKNWXKKKKXXXXXXXXXNNXXXNNNNNNNWWWWMMMMMMMMNX0OOXMMMOl
#OOOO00000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXXXNWNXXXNNNNNNWWWWWMMMMMMNkK0xKMMMNdl
#OOOO000000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXNWWNXXXXNNNNNNWWWWMMMMMMNXMNKXWWWOol
#OOO00000KKKKKKKXXXXKKK000000000000000000KKKKKKKKKKXXXXXKKXXKKXXNWWWWXXXXXNNNNNWWWWWMMMMMMMOkNWWWWdlc
#OO00KXNNNNWWWWWWWWWNKK0000000000000000000KKKKKOdcokooONNNNNXXKXNWWWWNXXXXNXNNNWWWWWWMMMMMMOdXWWWXolc
#OO0KKKNWNNNNNXXXXKKK00000000000000000000KKKKKd',,coc,'codOXWXXXNWWWWWXXXXXNXNNNWWWWWWMMMMMXx0NNNXool
#OOO00000000000000000000000000000000000KKKK0o,',.',,,;;cl:oookKXNWWWNNXXXXXNNNNNWWWWWWMMMMMMxxXNXKlll
#OOOOOO000000000000000000000000000000000KKKO:''',,;:ccloddkKOxokXNWWNK0XKXKx0KXNNWWWWWWWMMMM0d0K00ocl
#OOOOOOO00000000000000000000000000000000KK0x;'..';cccccoookO00OOXXXKKKKKKK0dkKXNNWNNWWWWWMMMNdkKK0xc:
#OOOOOO00000000000000000000000000000000KKKO,...';cllcclldxxkO0OkXK0000K0KKKOo0KXNNNNNWWWWWMMMkkkdl:cc
#0O0OOOO00000000000000000000000000000000KK0:...,:lol::::clooodxOXK000K000K0OoO0KXXNNNNWWWWX0x:,:c::::
#0OO0OOOO000000000000000000000000000000000O:,;';llc:,''.';lo.;cOXK000000000OdoOO0KXNNNWWWN;;::;::::::
#00OOOOOOOOOOO0000000000000000000000000000Oo';,cllccccc:;;:xddk0XK000000000000KKKKlXNNNNNOc::;;::::::
#00OOOOOOOOOOOO0000000000000000000000000000x,,;cllcc:cl:',,cdxk0XKKKKK0KK00000000KKKKOx0Oxd:;;;::;;;;
#x00OOOOOOOOOOOOOOOO00000000000000000000000kc..colc:c::;,';cdoxKXXXKK000OOOOOOOO000000dOxxk,,,,;;;;;;
#xO00OOOOOOOOOOOOOOOOOOO00000000000000000000, cllc:c;'.;odddddkO0K00000OOOkkkkkkxxddddxxcOc',,;;;;;;
#Od000OOOOOOOOOOOOOOOOOO000000000000K0KKKXO'. cllccc:;;:oddooookO000OOkxdoolccccloddxkOK0xk',,;;;,,;
#0kx000OOOOOOOO0000000000000000000KKKKKKKKx.. .:c:;:;'.,ccclldxxxkO0kl::ccllodxxkOOO000KXXx,,,,,,,,,
#00kkOO000000000OOOOO000000KKK000000000000d'... ..'......cclooddddxkOOOkdddxxkkkkOOO0000KXXk,',,,,,,,
#00OocokOkkOOOO000KKK000000000Okkxxxxxxdo:.. ....'......:o;oxdllooddxkOOOkxxkkkkkkkOOOO0KXOlc'''',,,,
#000Oxk00KK0000000000000000Oxllodddddol:..'. .;;,,,,'..,:cclxxkkocllodxkkOOkxxxxxxkkkOO00dlOl........
#00000000000000000000OOOOOkollloddxxxxd:;;x,',:ccc:::;,,:lolcddxxxdolooxxkOOkkxxxxxxkkOkcclc,:;,,....
#000000000000000OOkxxddlccllloooodxxddc....:ooccllllol:,,lddo:loddxxxoloooxO0Okxdddoool:c:c:clldo,,,,
#000000OOkxxdolccccllllolllloooddddocc....,dkxl:lxxxxxoc,;lool;:oxkkkxoloodxOK0Oxol:,',:ccllllllllloo
#kxdoolcclloodddddxxxxxxoooooddddddc,ccxl:dxxxx::kkkkkko;':lloooccooxkxooooxkOKK0dcc;.:....lllllllloo
#loddxxxxkkkxxxxxxxxxxxxloooooddxxo:..'c,lxxxddlldk00Okxc,,codxxlo:l::':cooodkO0KKOo:.,....:lllllllll
#kkkkkkkkkkkkkkkkxxxxxxxollolllodoc' .cxoc::locck000Oxc;':dxkxcO:d;;,. ;loodkO0XNN0l.'',,:cccccllll
#kkkkkkkkkkkkkxxxxxxxxxxol;dl',:l;.. .,,;...',;dl:dkOOxl;'codd:,..',,;;' ;loodk0KNWWN0c'cccccccccccl
#kkkkkkkkkkkkkxxxxxxxxxl:,,;cllooc, ,d...;:;;....,;..;ll:;,,:'........':, :lood0XXNWWWNklccc::cccccc
#kkkkkkkkkkkkkxxxxddddd:cclloooddoc'.: .:ldd:'....,,,;;.,;';:;',,;,'.... .coood0NNXNNNXxccc:::::ccc
#kkkkkkkkkkkkxxxxddddooclllloodddoc. ..'.,'..',,;clolcc:cooc;,.....'. 'looldOXXXXX0xlc:::::::::
#kkkkkkkkkkxxxxdddoooooclllooddddol' .......',:loddddolc;cdddooc;'.. .;. ,clllldxkOOOko::::::::::
#kkkkkkkxxxxxddddoooollcllooodxxdol' .';:::clloooddxxddlc:lxxkxdoc;,. ,. .,ccccccllodxx:::::;;;;;
#kkkkkkxxxxxdddoooolllllllooodxxxdo; .';:ccccllooddddxxxxoc:lxkxxxxlc:'. . .,;:;:::coo:;;;;;;:cll
#kkkkxxxxxdddooolllllllclllooodxxxkxdl,. ';:clllllloodxxxxxxdll;:xkkkxdolc,. .'.',,,,;...:llllc;,,
#kkkxxxxddddooollllllllcclllloodddxkkkkkoc;cllllcllodxxxkkOkxdxd:dO000OOxxdo:. .,,,...';:clc;;,,,,,,
#kkxxxxdddoooolllllllllc:cclllloooddxxxkxxoccllllloodddxxxkxdoc:cdlloodxxkO00k, ..:cccc:;;,,,,,,,,,,,
#kxxxxddooooollllclccccc:,,;:ccllloooooccoddoc;;cllcc:;,,oc''',,lkkd:'..'l,.:ll:cc;,,,,,,,,,,,,,,,,,,
#xxxxxddooollllccccccccccc:;.,;;::ccllc:ccccc:lxkcclc, ...;lddxO0Oddd:;;:::;,,,,,,,',,',,,',,,,,,,,
#xxxdddooollllccccccccccc::;;...,;:::;.;,,'';c;,ldkkkxxdolodxdxkxxdddoc:,,,,,''''''''''''''''''',,,,,
#xxdddoolllllccccccc::::::;;. .;;,,. ..;',clooddddxxddxdolc:,,'''''''''''''''''''''''''''''''''
#dddooolllllccccc:::::::;;,. .;:,. ..'.... .;:cllooooddo:,'''''''''''''''''''''''''''''''''''''''
#dddoolllllcccccc:::::;;;' .;;..,:looodddol:'..';::cccc'....'''.'''....'....''....''''''...''''''
#dooollllccccc::::::;;;;. .. ';,':oodddxxxxxxkkxo;. ................................ ...''''''
#ooolllccc:::::::;;;;;;. ';;codddxxxxxxxxkkkkkxl. .................................... ...''''''
#ollccccc:::::::;;;;;;, ;cloddxxxxkkxkkkkkkkxoc;;::;;;,.........................................
#lllcccc:::::;;;;;;;;;' .coodddxxxxkkkkkkkkxxd';::::::::.........................................
#lcccc::;;;;;;;;;;;,,,' clooddxxxxxkkkkkkkkxdd:.;:::::::;........................................
#cccc::;;;;;;;;,,,,,,,,. :llodddxxxxkkOOOOkkxddoc. ,::cc:;,...................................
#c::::;;;;;;,,,,,,,,'''' ;lloooddxxxxkOOOOOkkxdooc. .:c;.. ............................
#:::;;,,,,,,,,,,'''''''. .cloooddxxxxkkOOOOkxxdool; .:cc' ....................... ...
#::;;,,,,,,,,,,'''''.. ;ooooddxxxkkOOOOkxxddoolc' . .;cc;................. ......
#:;;,',,''''''''''.. ,ooooddxxxxkkOkxxddoolllc:. . ,clc........... . ......,:
#;;,'''''''''''''. :oooooooddxxkkkxddoollccc:'. . 'cc, ......;:cl
#;,'''''''''''.. 'lolllllooooddxxddoollcccc:,. .. .cc . .. .,;cccl
#;'.'''...''.. 'coollllllooooooooollcc:::::,. . .c. . . ..';:ccll
#.''........ ,loolllllllooooooollcc::::::;,. . ..';::cccl
+123
View File
@@ -0,0 +1,123 @@
difficulty = 60
min_moves = 20
layout = """
# # ## ##° ########### #
#S ° ° °# ° ° E#
###############################
"""
#000000000000000000000000000000KKKKKKKKKKKKKKKKKKXXKKKKKXXXXNWMMNNXXXNNNNNNNNWWWWWMMMMMMMMMMMMMMMMMMM
#0000000000000000000000000000000KKKKKKKKKKKKKKKKKXWWXKKKKXXXNNWNXXXXXXNNNNNNNMWWWMMMMMMMMMMMMMMMMMMMM
#00000000000000000000000000000000KKKKKKKKKKKKKKKKXMMNKKKKXXXXXXXXXXXXXXNNNNNNNWWWWMMMMMMMMMMMMMMMMMM0
#OOO0000000000000000000000000000000KKK00KKKKKKKKKKNWXKKKKXXXXXXXXXNNXXXNNNNNNNWWWWMMMMMMMMNX0OOXMMMOl
#OOOO00000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXXXNWNXXXNNNNNNWWWWWMMMMMMNkK0xKMMMNdl
#OOOO000000000000000000000000000000KKK00000KKKKKKKKKKKKKKKKXXXXXXNWWNXXXXNNNNNNWWWWMMMMMMNXMNKXWWWOol
#OOO00000KKKKKKKXXXXKKK000000000000000000KKKKKKKKKKXXXXXKKXXKKXXNWWWWXXXXXNNNNNWWWWWMMMMMMMOkNWWWWdlc
#OO00KXNNNNWWWWWWWWWNKK0000000000000000000KKKKKOdcokooONNNNNXXKXNWWWWNXXXXNXNNNWWWWWWMMMMMMOdXWWWXolc
#OO0KKKNWNNNNNXXXXKKK00000000000000000000KKKKKd',,coc,'codOXWXXXNWWWWWXXXXXNXNNNWWWWWWMMMMMXx0NNNXool
#OOO00000000000000000000000000000000000KKKK0o,',.',,,;;cl:oookKXNWWWNNXXXXXNNNNNWWWWWWMMMMMMxxXNXKlll
#OOOOOO000000000000000000000000000000000KKKO:''',,;:ccloddkKOxokXNWWNK0XKXKx0KXNNWWWWWWWMMMM0d0K00ocl
#OOOOOOO00000000000000000000000000000000KK0x;'..';cccccoookO00OOXXXKKKKKKK0dkKXNNWNNWWWWWMMMNdkKK0xc:
#OOOOOO00000000000000000000000000000000KKKO,...';cllcclldxxkO0OkXK0000K0KKKOo0KXNNNNNWWWWWMMMkkkdl:cc
#0O0OOOO00000000000000000000000000000000KK0:...,:lol::::clooodxOXK000K000K0OoO0KXXNNNNWWWWX0x:,:c::::
#0OO0OOOO000000000000000000000000000000000O:,;';llc:,''.';lo.;cOXK000000000OdoOO0KXNNNWWWN;;::;::::::
#00OOOOOOOOOOO0000000000000000000000000000Oo';,cllccccc:;;:xddk0XK000000000000KKKKlXNNNNNOc::;;::::::
#00OOOOOOOOOOOO0000000000000000000000000000x,,;cllcc:cl:',,cdxk0XKKKKK0KK00000000KKKKOx0Oxd:;;;::;;;;
#x00OOOOOOOOOOOOOOOO00000000000000000000000kc..colc:c::;,';cdoxKXXXKK000OOOOOOOO000000dOxxk,,,,;;;;;;
#xO00OOOOOOOOOOOOOOOOOOO00000000000000000000, cllc:c;'.;odddddkO0K00000OOOkkkkkkxxddddxxcOc',,;;;;;;
#Od000OOOOOOOOOOOOOOOOOO000000000000K0KKKXO'. cllccc:;;:oddooookO000OOkxdoolccccloddxkOK0xk',,;;;,,;
#0kx000OOOOOOOO0000000000000000000KKKKKKKKx.. .:c:;:;'.,ccclldxxxkO0kl::ccllodxxkOOO000KXXx,,,,,,,,,
#00kkOO000000000OOOOO000000KKK000000000000d'... ..'......cclooddddxkOOOkdddxxkkkkOOO0000KXXk,',,,,,,,
#00OocokOkkOOOO000KKK000000000Okkxxxxxxdo:.. ....'......:o;oxdllooddxkOOOkxxkkkkkkkOOOO0KXOlc'''',,,,
#000Oxk00KK0000000000000000Oxllodddddol:..'. .;;,,,,'..,:cclxxkkocllodxkkOOkxxxxxxkkkOO00dlOl........
#00000000000000000000OOOOOkollloddxxxxd:;;x,',:ccc:::;,,:lolcddxxxdolooxxkOOkkxxxxxxkkOkcclc,:;,,....
#000000000000000OOkxxddlccllloooodxxddc....:ooccllllol:,,lddo:loddxxxoloooxO0Okxdddoool:c:c:clldo,,,,
#000000OOkxxdolccccllllolllloooddddocc....,dkxl:lxxxxxoc,;lool;:oxkkkxoloodxOK0Oxol:,',:ccllllllllloo
#kxdoolcclloodddddxxxxxxoooooddddddc,ccxl:dxxxx::kkkkkko;':lloooccooxkxooooxkOKK0dcc;.:....lllllllloo
#loddxxxxkkkxxxxxxxxxxxxloooooddxxo:..'c,lxxxddlldk00Okxc,,codxxlo:l::':cooodkO0KKOo:.,....:lllllllll
#kkkkkkkkkkkkkkkkxxxxxxxollolllodoc' .cxoc::locck000Oxc;':dxkxcO:d;;,. ;loodkO0XNN0l.'',,:cccccllll
#kkkkkkkkkkkkkxxxxxxxxxxol;dl',:l;.. .,,;...',;dl:dkOOxl;'codd:,..',,;;' ;loodk0KNWWN0c'cccccccccccl
#kkkkkkkkkkkkkxxxxxxxxxl:,,;cllooc, ,d...;:;;....,;..;ll:;,,:'........':, :lood0XXNWWWNklccc::cccccc
#kkkkkkkkkkkkkxxxxddddd:cclloooddoc'.: .:ldd:'....,,,;;.,;';:;',,;,'.... .coood0NNXNNNXxccc:::::ccc
#kkkkkkkkkkkkxxxxddddooclllloodddoc. ..'.,'..',,;clolcc:cooc;,.....'. 'looldOXXXXX0xlc:::::::::
#kkkkkkkkkkxxxxdddoooooclllooddddol' .......',:loddddolc;cdddooc;'.. .;. ,clllldxkOOOko::::::::::
#kkkkkkkxxxxxddddoooollcllooodxxdol' .';:::clloooddxxddlc:lxxkxdoc;,. ,. .,ccccccllodxx:::::;;;;;
#kkkkkkxxxxxdddoooolllllllooodxxxdo; .';:ccccllooddddxxxxoc:lxkxxxxlc:'. . .,;:;:::coo:;;;;;;:cll
#kkkkxxxxxdddooolllllllclllooodxxxkxdl,. ';:clllllloodxxxxxxdll;:xkkkxdolc,. .'.',,,,;...:llllc;,,
#kkkxxxxddddooollllllllcclllloodddxkkkkkoc;cllllcllodxxxkkOkxdxd:dO000OOxxdo:. .,,,...';:clc;;,,,,,,
#kkxxxxdddoooolllllllllc:cclllloooddxxxkxxoccllllloodddxxxkxdoc:cdlloodxxkO00k, ..:cccc:;;,,,,,,,,,,,
#kxxxxddooooollllclccccc:,,;:ccllloooooccoddoc;;cllcc:;,,oc''',,lkkd:'..'l,.:ll:cc;,,,,,,,,,,,,,,,,,,
#xxxxxddooollllccccccccccc:;.,;;::ccllc:ccccc:lxkcclc, ...;lddxO0Oddd:;;:::;,,,,,,,',,',,,',,,,,,,,
#xxxdddooollllccccccccccc::;;...,;:::;.;,,'';c;,ldkkkxxdolodxdxkxxdddoc:,,,,,''''''''''''''''''',,,,,
#xxdddoolllllccccccc::::::;;. .;;,,. ..;',clooddddxxddxdolc:,,'''''''''''''''''''''''''''''''''
#dddooolllllccccc:::::::;;,. .;:,. ..'.... .;:cllooooddo:,'''''''''''''''''''''''''''''''''''''''
#dddoolllllcccccc:::::;;;' .;;..,:looodddol:'..';::cccc'....'''.'''....'....''....''''''...''''''
#dooollllccccc::::::;;;;. .. ';,':oodddxxxxxxkkxo;. ................................ ...''''''
#ooolllccc:::::::;;;;;;. ';;codddxxxxxxxxkkkkkxl. .................................... ...''''''
#ollccccc:::::::;;;;;;, ;cloddxxxxkkxkkkkkkkxoc;;::;;;,.........................................
#lllcccc:::::;;;;;;;;;' .coodddxxxxkkkkkkkkxxd';::::::::.........................................
#lcccc::;;;;;;;;;;;,,,' clooddxxxxxkkkkkkkkxdd:.;:::::::;........................................
#cccc::;;;;;;;;,,,,,,,,. :llodddxxxxkkOOOOkkxddoc. ,::cc:;,...................................
#c::::;;;;;;,,,,,,,,'''' ;lloooddxxxxkOOOOOkkxdooc. .:c;.. ............................
#:::;;,,,,,,,,,,'''''''. .cloooddxxxxkkOOOOkxxdool; .:cc' ....................... ...
#::;;,,,,,,,,,,'''''.. ;ooooddxxxkkOOOOkxxddoolc' . .;cc;................. ......
#:;;,',,''''''''''.. ,ooooddxxxxkkOkxxddoolllc:. . ,clc........... . ......,:
#;;,'''''''''''''. :oooooooddxxkkkxddoollccc:'. . 'cc, ......;:cl
#;,'''''''''''.. 'lolllllooooddxxddoollcccc:,. .. .cc . .. .,;cccl
#;'.'''...''.. 'coollllllooooooooollcc:::::,. . .c. . . ..';:ccll
#.''........ ,loolllllllooooooollcc::::::;,. . ..';::cccl