Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/robots/Level12.json

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions docs/robots/charger.l
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# NEW PREDICATE: Creates a table of all robots with an empty battery.
DeadBot(robot_name:) :-
# Find the sensor data for every robot in the simulation.
Sensor(robot_name: , sensor:),
# Check if that robot has a battery and its level is zero.
sensor.self.has_battery == true,
sensor.self.battery_level == 0;

# HELPER FUNCTION: Returns true if a radar ping 'r' is a charging station.
IsChargingStation(r) = (
r.object == "beacon" && EndsWith(r.label, "_ChargingStation")
);

# HELPER FUNCTION: Finds the ANGLE of the closest DEAD BOT.
ClosestDeadBotAngle(radar) = ArgMin{
r.angle -> r.distance :-
r in radar,
r.object == "robot",
# We check if the name of the robot we see (r.label)
# exists in our table of dead bots.
DeadBot(robot_name: r.label)
};

# HELPER FUNCTION: Finds the ANGLE of the closest CHARGING STATION.
ClosestChargingStationAngle(radar) = ArgMin{
r.angle -> r.distance :-
r in radar,
IsChargingStation(r)==true
};

# A helper function to steer towards a target angle.
SteerTo(target_angle, speed) = {
left_engine: speed - target_angle,
right_engine: speed + target_angle
};

# Standard obstacle avoidance logic.
WeightedAverage(k->v) = Sum(k * v) / Sum(k);
FreedomMotion(radar) = WeightedAverage{
x.distance -> x.angle :- x in radar
};

# Returns true if the robot should be helping a dead bot.
ShouldHelpDeadBot(sensor) = (
sensor.self.can_charge == true &&
sensor.self.battery_level > 0 &&
# We count how many of the robots we see in our radar
# have a name that is in the DeadBot table.
Count{ 1 :-
r in sensor.radar,
r.object == "robot",
DeadBot(robot_name: r.label)
} > 0
);

# Returns true if the robot should be seeking a charging station.
ShouldSeekCharger(sensor) = (
sensor.self.can_charge == true &&
sensor.self.battery_level == 0 &&
Count{1 :- r in sensor.radar, IsChargingStation(r)==true} > 0
);

# --- Main Logic Starts Here (Now Explicit) ---

# PRIORITY 1: A robot that can_charge will go help a dead robot it sees.
Robot(robot_name:, desire:, memory:) :-
Sensor(robot_name:, sensor:),
# Check if the conditions for this priority are met.
ShouldHelpDeadBot(sensor)==true,
# Find the angle to the closest one.
dead_bot_angle = ClosestDeadBotAngle(sensor.radar),
desire = SteerTo(dead_bot_angle, 1),
memory = "Going to help a robot in need!";


# PRIORITY 2: If a robot that can_charge has no battery, it seeks a station.
Robot(robot_name:, desire:, memory:) :-
Sensor(robot_name:, sensor:),
# Check if the conditions for this priority are met.
ShouldSeekCharger(sensor)==true,
# Find the angle to the closest one.
station_angle = ClosestChargingStationAngle(sensor.radar),
desire = SteerTo(station_angle, 1),
memory = "My battery is empty! Seeking a charging station.";


# PRIORITY 3 (DEFAULT): All other robots will just wander around avoiding walls.
Robot(robot_name:, desire:, memory:) :-
Sensor(robot_name:, sensor:),
ShouldHelpDeadBot(sensor)==false,
ShouldSeekCharger(sensor)==false,
freedom = FreedomMotion(sensor.radar),
speed = 0.5,
desire = {
left_engine: speed - freedom+0.1,
right_engine: speed + freedom
},
memory = "Exploring the labyrinth.";
95 changes: 94 additions & 1 deletion docs/robots/editor5.html
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,70 @@
border-radius: 50%;
cursor: pointer;
}

/* The switch container */
.switch {
position: relative;
display: inline-block;
width: 50px; /* Smaller width */
height: 28px; /* Smaller height */
margin-left: auto; /* Aligns it to the right */
}

/* Hide the default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}

/* The slider track */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #555; /* Off color to match your theme */
-webkit-transition: .4s;
transition: .4s;
}

/* The slider handle (the circle) */
.slider:before {
position: absolute;
content: "";
height: 20px; /* Adjusted size */
width: 20px; /* Adjusted size */
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}

/* --- The Magic: What happens when the checkbox is checked --- */

/* Change the background of the track to green */
input:checked + .slider {
background-color: #34c759; /* Green to match your 'Download' button */
}

/* Move the handle to the right */
input:checked + .slider:before {
-webkit-transform: translateX(22px); /* Adjusted distance */
-ms-transform: translateX(22px);
transform: translateX(22px);
}

/* Rounded sliders */
.slider.round {
border-radius: 28px;
}

.slider.round:before {
border-radius: 50%;
}
/* Modal styles for alerts and editing */
.modal-backdrop {
position: fixed;
Expand Down Expand Up @@ -352,6 +415,36 @@ <h3 id="editTitle">Edit Object</h3>
<label for="editOffBeacon">Off Beacon:</label>
<select id="editOffBeacon"></select>
</div>
<div class="edit-row" id="editChargingStationRow" style="display: none;">
<label for="editChargingStation">Charging Station:</label>
<label class="switch">
<input type="checkbox" id="editChargingStation">
<span class="slider round"></span>
</label>
</div>
<div class="edit-row" id="hasBatteryRow" style="display: none;">
<label for="hasBatteryToggle">Has Battery:</label>
<label class="switch">
<input type="checkbox" id="hasBatteryToggle">
<span class="slider round"></span>
</label>
</div>
<div class="edit-row" id="canChargeRow" style="display: none;">
<label for="canChargeToggle">Can Charge:</label>
<label class="switch">
<input type="checkbox" id="canChargeToggle">
<span class="slider round"></span>
</label>
</div>
<div class="edit-row" id="batteryConsumptionRow" style="display: none;">
<label for="batteryConsumptionSlider">Consumption Rate:</label>
<input type="range" id="batteryConsumptionSlider" min="0" max="1" step="0.01" value="1">
<span id="batteryConsumptionValue" style="width: 40px; text-align: right;">1.00</span>
</div>
<div class="edit-row" id="batteryCapacityRow" style="display: none;">
<label for="batteryCapacityInput">Battery Capacity:</label>
<input type="number" id="batteryCapacityInput" value="100" min="0">
</div>
<div class="edit-row" id="editStickySwitchRow" style="display: none;">
<label for="editStickySwitch">Sticky Switch:</label>
<input type="checkbox" id="editStickySwitch">
Expand Down
105 changes: 99 additions & 6 deletions docs/robots/editor5.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ const editStickySwitch = document.getElementById('editStickySwitch');
// --- *** 1. ADDED IMPENETRABLE ELEMENTS *** ---
const editImpenetrableRow = document.getElementById('editImpenetrableRow');
const editImpenetrable = document.getElementById('editImpenetrable');
const editChargingStationRow = document.getElementById('editChargingStationRow');
const editChargingStation = document.getElementById('editChargingStation');
const hasBatteryRow = document.getElementById('hasBatteryRow');
const hasBatteryToggle = document.getElementById('hasBatteryToggle');
const canChargeRow = document.getElementById('canChargeRow');
const canChargeToggle = document.getElementById('canChargeToggle');

const batteryConsumptionRow = document.getElementById('batteryConsumptionRow');
const batteryConsumptionSlider = document.getElementById('batteryConsumptionSlider');
const batteryConsumptionValue = document.getElementById('batteryConsumptionValue');

const batteryCapacityRow = document.getElementById('batteryCapacityRow');
const batteryCapacityInput = document.getElementById('batteryCapacityInput');
// --- END NEW ---

// --- Resize Modal elements ---
Expand Down Expand Up @@ -97,6 +110,10 @@ const BEACON_SIZE = 4; // Half-size (total size 8x8)
const BEACON_COLOR = '#00FF00'; // Bright green
const DEFAULT_AREA_COLOR = '#FFFF00'; // Yellow

// Default Battery Constants
const DEFAULT_BATTERY_CAPACITY = 100;
const DEFAULT_BATTERY_CONSUMPTION_RATE = 0.5;

// Custom alert function
function customAlert(message) {
alertMessage.textContent = message;
Expand Down Expand Up @@ -327,12 +344,22 @@ function handlePlaceObject(mouseX, mouseY) {
let newType = null;

if (currentTool === 2) { // Place Robot
const newRobot = { x: mouseX, y: mouseY, name: "Anon", color: "#BBBBBB", angle: 0 };
const newRobot = {
x: mouseX,
y: mouseY,
name: "Anon",
color: "#BBBBBB",
angle: 0,
has_battery: false, // Default is false
can_charge: false,
battery_consumption_rate: DEFAULT_BATTERY_CONSUMPTION_RATE, // Default value
battery_capacity: DEFAULT_BATTERY_CAPACITY // Default value
};
newIndex = robots.push(newRobot) - 1;
newType = 'robot';
} else if (currentTool === 3) { // Place Beacon
beacons_placed += 1;
const newBeacon = { x: mouseX, y: mouseY, name: "B" + beacons_placed };
const newBeacon = { x: mouseX, y: mouseY, name: "B" + beacons_placed, charging_station: false};
newIndex = beacons.push(newBeacon) - 1;
newType = 'beacon';
} else if (currentTool === 4) { // Place Area
Expand Down Expand Up @@ -391,6 +418,10 @@ function openEditPanel(type, index) {
selectedObjectIndex = index;

// Hide all optional rows by default
hasBatteryRow.style.display = 'none';
canChargeRow.style.display = 'none';
batteryConsumptionRow.style.display = 'none';
batteryCapacityRow.style.display = 'none';
editColorRow.style.display = 'none';
editAngleRow.style.display = 'none';
angleEditorRow.style.display = 'none';
Expand All @@ -401,6 +432,7 @@ function openEditPanel(type, index) {
editStickySwitchRow.style.display = 'none';
// --- *** 3. HIDE IMPENETRABLE ROW BY DEFAULT *** ---
editImpenetrableRow.style.display = 'none';
editChargingStationRow.style.display = 'none';

if (type === 'robot') {
const obj = robots[index];
Expand All @@ -414,10 +446,31 @@ function openEditPanel(type, index) {
editAngleRow.style.display = 'flex';
angleEditorRow.style.display = 'flex';
drawAngleEditor(angleDeg);
hasBatteryRow.style.display = 'flex'; // Show the main toggle

const robotHasBattery = obj.has_battery ?? false; // Default to false for old files
hasBatteryToggle.checked = robotHasBattery;

const robotCanCharge = obj.canCharge ?? false;
canChargeToggle.checked = robotCanCharge;

// Populate the input fields, with defaults for old files
batteryConsumptionSlider.value = obj.batteryConsumptionRate ?? DEFAULT_BATTERY_CONSUMPTION_RATE;
batteryConsumptionValue.textContent = parseFloat(batteryConsumptionSlider.value).toFixed(2);
batteryCapacityInput.value = obj.batteryCapacity ?? DEFAULT_BATTERY_CAPACITY;

// Set initial visibility of the conditional fields
batteryConsumptionRow.style.display = robotHasBattery ? 'flex' : 'none';
batteryCapacityRow.style.display = robotHasBattery ? 'flex' : 'none';
canChargeRow.style.display = robotHasBattery ? 'flex' : 'none';
} else if (type === 'beacon') {
const obj = beacons[index];
editTitle.textContent = "Edit Beacon";
editTitle.textContent = "Edit Beacon Modified";
editName.value = obj.name;
// Show the charging station toggle
editChargingStationRow.style.display = 'flex';
// Use `?? true` to default to checked for old beacons without this property
editChargingStation.checked = obj.charging_station ?? false;
// All optional rows remain hidden
} else if (type === 'area') {
const obj = areas[index];
Expand Down Expand Up @@ -559,9 +612,22 @@ function saveObjectChanges() {
robot.name = newName;
robot.color = editColor.value;
robot.angle = parseInt(editAngle.value, 10) * Math.PI / 180;
robot.has_battery = hasBatteryToggle.checked;
// Parse as numbers, with fallbacks in case the input is empty
if (robot.has_battery) {
// Parse as numbers, with fallbacks in case the input is empty
robot.battery_consumption_rate = parseFloat(batteryConsumptionSlider.value);
robot.can_charge = canChargeToggle.checked;
robot.battery_capacity = parseInt(batteryCapacityInput.value, 10) || DEFAULT_BATTERY_CAPACITY;
} else {
robot.battery_consumption_rate = 0;
robot.can_charge = false;
robot.battery_capacity = 0;
}
} else if (selectedObjectType === 'beacon') {
const beacon = beacons[selectedObjectIndex];
beacon.name = newName;
beacon.charging_station = editChargingStation.checked;
} else if (selectedObjectType === 'area') {
const area = areas[selectedObjectIndex];
area.name = newName;
Expand Down Expand Up @@ -619,10 +685,14 @@ downloadButton.addEventListener('click', () => {

// Filter out temporary beacon/area properties for export if not needed in simulator
const robotsExport = robots.map(r => ({
x: r.x, y: r.y, name: r.name, color: r.color, angle: r.angle
x: r.x, y: r.y, name: r.name, color: r.color, angle: r.angle,
has_battery: r.has_battery,
can_charge: r.can_charge,
battery_consumption_rate: r.battery_consumption_rate,
battery_capacity: r.battery_capacity
}));
const beaconsExport = beacons.map(b => ({
x: b.x, y: b.y, name: b.name
x: b.x, y: b.y, name: b.name, charging_station: b.charging_station
}));
const areasExport = areas.map(a => ({
x: a.x, y: a.y, name: a.name, color: a.color, radius: a.radius,
Expand Down Expand Up @@ -686,11 +756,25 @@ fileInput.addEventListener('change', (event) => {
// Load robots
robots = data.robots || [];
// Ensure angles are numbers (sometimes load as strings from JSON)
robots.forEach(r => r.angle = parseFloat(r.angle || 0));
robots.forEach(r => {
r.angle = parseFloat(r.angle || 0);
// Set defaults if properties are missing from the loaded file
r.has_battery = r.has_battery ?? false;
r.can_charge = r.can_charge ?? false;
r.battery_consumption_rate = r.battery_consumption_rate ?? DEFAULT_BATTERY_CONSUMPTION_RATE;
r.battery_capacity = r.battery_capacity ?? DEFAULT_BATTERY_CAPACITY;
});

// Load beacons
beacons = data.beacons || [];

beacons.forEach(b => {
// If charging_station is undefined (e.g., from an old file), default to false.
if (b.charging_station === undefined) {
b.charging_station = false;
}
});

// Load areas
areas = data.areas || [];
// Ensure new properties have defaults if not present in old files
Expand Down Expand Up @@ -777,6 +861,15 @@ editAngle.addEventListener('input', (e) => {
}
});

hasBatteryToggle.addEventListener('change', () => {
const isEnabled = hasBatteryToggle.checked;
canChargeRow.style.display = isEnabled ? 'flex' : 'none';
batteryConsumptionRow.style.display = isEnabled ? 'flex' : 'none';
batteryCapacityRow.style.display = isEnabled ? 'flex' : 'none';
});
batteryConsumptionSlider.addEventListener('input', () => {
batteryConsumptionValue.textContent = parseFloat(batteryConsumptionSlider.value).toFixed(2);
});

// Resize Modal Buttons
resizeButton.addEventListener('click', () => {
Expand Down
Loading