src_PDDL_pddlTemplates.js
import { isWalkableTile, TILE_TYPES } from "../utils/tile.js";
/**
* Sanitizes a raw PDDL name by trimming, converting to lowercase,
* replacing non-alphanumeric characters with underscores, and removing
* leading and trailing underscores.
* @param {string} raw - The raw name to sanitize.
* @return {string} - The sanitized PDDL name.
* @description
* This function is useful for ensuring that names used in PDDL (Planning Domain Definition Language)
* are valid and follow the naming conventions required by PDDL, such as using lowercase letters,
* numbers, and underscores, while avoiding spaces and special characters.
* @example
* sanitizePddlName(" My Name! "); // returns "my_name"
* sanitizePddlName("Invalid@Name#123"); // returns "invalid_name_123"
* sanitizePddlName(" __Extra__Underscores__ "); // returns "extra_underscores"
* sanitizePddlName("1234"); // returns "1234"
* sanitizePddlName("!@#$%^&*()"); // returns ""
*/
export function sanitizePddlName(raw) {
return raw
.toString()
.trim()
.toLowerCase()
.replace(/[^a-z0-9]/g, "_")
.replace(/_+/g, "_")
.replace(/^_+|_+$/g, "");
}
/**
* Returns a static PDDL domain definition (deliveroo-domain.pddl).
*/
export function generateDeliverooDomain() {
return `
(define (domain deliveroo)
(:requirements :strips :typing)
(:types
agent
parcel
tile
base
)
(:predicates
;; agent ?a is on tile ?t
(at ?a - agent ?t - tile)
;; parcel ?p is on tile ?t (not yet picked up)
(parcel-at ?p - parcel ?t - tile)
;; agent ?a is carrying parcel ?p
(carrying ?a - agent ?p - parcel)
;; base ?b is located on tile ?t
(base-at ?b - base ?t - tile)
;; parcel ?p has been delivered
(delivered ?p - parcel)
;; adjacency relation between tiles
(adjacent ?t1 - tile ?t2 - tile)
)
;; =====================================================
;; 1) move: move an agent from one tile to an adjacent tile
(:action move
:parameters (?a - agent ?from - tile ?to - tile)
:precondition (and
(at ?a ?from)
(adjacent ?from ?to)
)
:effect (and
(not (at ?a ?from))
(at ?a ?to)
)
)
;; =====================================================
;; 2) pickup: agent picks up a parcel on its tile
(:action pickup
:parameters (?a - agent ?p - parcel ?t - tile)
:precondition (and
(at ?a ?t)
(parcel-at ?p ?t)
)
:effect (and
(not (parcel-at ?p ?t))
(carrying ?a ?p)
)
)
;; =====================================================
;; 3) deposit: agent deposits a carried parcel at a base tile
(:action deposit
:parameters (?a - agent ?p - parcel ?b - base ?t - tile)
:precondition (and
(at ?a ?t)
(base-at ?b ?t)
(carrying ?a ?p)
)
:effect (and
(not (carrying ?a ?p))
(delivered ?p)
)
)
)
`.trim();
}
/**
* Predicates that I have in the domain-file:
* - (at ?a - agent ?t - tile)
* -(parcel-at ?p - parcel ?t - tile)
* - (carrying ?a - agent ?p - parcel)
* - (base-at ?b - base ?t - tile)
* - (delivered ?p - parcel)
* -(adjacent ?t1 - tile ?t2 - tile)
*
* So I need to use these predicates to build the problem
*
* The PDDL problem definition has the following structure:
* define (problem PROBLEM_NAME)
(:domain DOMAIN_NAME)
(:objects OBJ1 OBJ2 ... OBJ_N)
(:init ATOM1 ATOM2 ... ATOM_N)
(:goal CONDITION_FORMULA)
)
*/
export function generateDeliverooProblem(mapStore, parcelsStore, me, serverConfig) {
// Helper functions to create names for tiles, bases, and parcels
function tileNameFromCoordinates(coordinates) {
const [x, y] = coordinates.split(","); // coordinates torna tipo "3,5" or "7,2"
return sanitizePddlName(`t_${x}_${y}`);
}
// Helper function to create base names from coordinates
// e.g. "3,5" --> "base_3_5"
function baseNameFromCoordinates(coordinates) {
const [x, y] = coordinates.split(",");
return sanitizePddlName(`base_${x}_${y}`);
}
// Helper function to create parcel names
// e.g. {id: "p1", x: 3, y: 5} --> "p1"
function parcelName(p) {
return sanitizePddlName(`${p.id}`);
}
// get all walkable tiles (type != EMPTY)
const walkableTiles = [];
for (const [coordinates, tileType] of mapStore.map.entries()) {
if (isWalkableTile(tileType)) {
const [x, y] = coordinates.split(",").map(Number);
//console.log(`Adding walkable tile: ${coordKey} (${xs}, ${ys})`);
walkableTiles.push({ coordinates: coordinates, x: x, y: y });
}
}
//get adjacency facts for every walkable tile
const adjacencyFacts = [];
const directions = [
{ dx: +1, dy: 0 },
{ dx: -1, dy: 0 },
{ dx: 0, dy: +1 },
{ dx: 0, dy: -1 },
];
function isWalkable(x, y) {
const k = `${x},${y}`;
return mapStore.map.has(k) && mapStore.map.get(k) !== TILE_TYPES.EMPTY;
}
// Loop through all walkable tiles and check adjacent ones
for (const tile of walkableTiles) {
const fromName = tileNameFromCoordinates(tile.coordinates);
for (const { dx, dy } of directions) {
const nx = tile.x + dx,
ny = tile.y + dy,
nKey = `${nx},${ny}`;
if (isWalkable(nx, ny)) {
const toName = tileNameFromCoordinates(nKey);
adjacencyFacts.push(`(adjacent ${fromName} ${toName})`);
}
}
}
// declare base objects + “(base-at …)” facts
const baseCoords = Array.from(mapStore.bases); // e.g. ["3,5", "7,2", …]
const baseObjects = baseCoords.map((ck) => baseNameFromCoordinates(ck));
const baseAtFacts = baseCoords.map((ck) => {
const tileAtom = tileNameFromCoordinates(ck);
const baseAtom = baseNameFromCoordinates(ck);
return `(base-at ${baseAtom} ${tileAtom})`;
});
// declare parcel objects + “(parcel-at …)” facts (only not‐carried) ──
const parcelEntries = Array.from(parcelsStore.map.values()); // e.g. [{id: "p1", x: 3, y: 5, carriedBy: null}, {id: "p2", x: 7, y: 2, carriedBy: "agent_42"}, …]
const parcelObjects = [];
const parcelAtFacts = [];
for (const p of parcelEntries) {
if (!p.carriedBy) {
const parcelPDDL = parcelName(p); // for PPDL
parcelObjects.push(parcelPDDL);
const tilePDDL = sanitizePddlName(`t_${p.x}_${p.y}`); //For PPDL e.g. "t_3_5"
parcelAtFacts.push(`(parcel-at ${parcelPDDL} ${tilePDDL})`); // e.g. "(parcel-at p1 t_3_5)"
}
}
// declare the single agent + its “(at …)” fact
const agentPDDL = sanitizePddlName(`agent_${me.id}`);
const agentTile = sanitizePddlName(`t_${Math.round(me.x)}_${Math.round(me.y)}`);
const atAgentFact = `(at ${agentPDDL} ${agentTile})`;
// building the “:objects” section
const allTileNames = walkableTiles.map((t) => tileNameFromCoordinates(t.coordinates));
const objectsSection = `
(:objects
${agentPDDL} - agent
${parcelObjects.join(" ")} - parcel
${baseObjects.join(" ")} - base
${allTileNames.join(" ")} - tile
)
`.trim();
//building the “:init” section
const initLines = [
atAgentFact,
...parcelAtFacts,
...baseAtFacts,
...adjacencyFacts,
];
const initSection = `
(:init
${initLines.join("\n ")}
)
`.trim();
// building the “:goal” section: deliver every known parcel
const deliveredGoals = parcelObjects.map((pid) => `(delivered ${pid})`);
const goalSection = `
(:goal (and
${deliveredGoals.join("\n ")}
))
`.trim();
// combine into a full PDDL problem
const problemName = sanitizePddlName(`deliveroo_problem`);
const pddl = `
(define (problem ${problemName})
(:domain deliveroo)
${objectsSection}
${initSection}
${goalSection}
)
`.trim();
return pddl;
}