Test runner.

master
Yorick van Pelt 2017-12-02 17:02:28 +01:00
parent ddc8b023d4
commit 0fb8f8220f
3 changed files with 148 additions and 16 deletions

View File

@ -33,7 +33,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<object id="robotbodySVG" type="image/svg+xml" data="robotbody.svg" width=0 ></object>
<canvas id="arenaLemming" width="450" height="450" style="cursor: initial;"></canvas>
<br>
<table border="0">
<table border="0" style="float: left; ">
<tbody>
<tr><th colspan="2" align="center">Simulation properties</th><td></td></tr>
<tr><td>Simulation steps: <span id="SimStepLabel"></span></td></tr>
@ -46,8 +46,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</td>
<td>Sensor values<span id="SensorLabel"></span></td>
</tr>
<tr><th colspan="2" align="center">Display</th></tr>
<tr><th colspan="2" align="center">Robot Display</th></tr>
<tr><td colspan="2" id="bayDisplay"></td></tr>
<tr><th colspan="2">Experiments</th></tr>
<tr><td><label for="robotAmnt">Robot Count</label></td><td><input id="robotAmnt" type="number" value="1" min="0" max="3" /></td></tr>
<tr><td><label for="ffwdtimes">nr of experiments</label></td><td><input id="ffwdtimes" type="number" value="10" min="1" max="20" /></td></tr>
<tr><td><label for="wanderRate">robot wander rate</label></td><td><input id="wanderRate" type="number" value="0.0002" step="0.0001"></td></tr>
<tr><td><label for="boxFric">box friction</label></td><td><input id="boxFric" type="number" value="0.001" step="0.0005" min=0></td></tr>
<tr><td><input type="button" value="Restart Slow" onclick="initSimulation(); sim.start()" /><input type="button" value="toggle pause" onclick="sim.toggle()"></td><td><input type="button" value="run experiments" onclick="runExperiments()" /><input type="button" value="clear" onclick="resultsTable.clear()" /></td></tr>
<tr><td></td></tr>
</tbody></table>
<table id="results"></table>
</body>
</html>

View File

@ -198,11 +198,20 @@ class Simulation {
this.bay = new Bay()
}
step() {
destroy() {
this.stop()
Matter.Engine.clear(this.engine)
this.elem.removeMouseTracker()
this.bay.destroy()
}
step(draw = true) {
// console.log("stepping", this.id)
// advance simulation by one step (except MatterJS engine's physics)
if (this.curSteps < simInfo.maxSteps) {
this.bay.repaint();
this.draw();
if (draw) {
this.bay.repaint();
this.draw();
}
this.robots.forEach(robot => {
robot.updateSensors();
robot.move();
@ -214,7 +223,7 @@ class Simulation {
})
// count and display number of steps
this.curSteps += 1;
document.getElementById("SimStepLabel").innerHTML =
if (draw) document.getElementById("SimStepLabel").innerHTML =
padnumber(this.curSteps, 5) +
' of ' +
padnumber(simInfo.maxSteps, 5);
@ -262,6 +271,19 @@ class Simulation {
if (this.doContinue) this.stop()
else this.start()
}
runSteps(steps) {
this.stop()
const sensorDebug_ = simInfo.debugSensors
simInfo.debugSensors = false
for(let i = 0; i < steps-1; i++) {
Matter.Engine.update(this.engine, 1000/60);
this.step(false)
}
simInfo.debugSensors = sensorDebug_
Matter.Engine.update(this.engine, 1000/60);
this.step(true)
}
}
function init() { // called once when loading HTML file
// the pathseg polyfill will run in the html document
@ -269,14 +291,106 @@ function init() { // called once when loading HTML file
// regular function and inject it into the svg using eval
const svg = document.getElementById('robotbodySVG')
svg.contentWindow.eval('(' + window.pathseg.toString() + ')()')
sim = new Simulation()
sim.init()
sim.addRobot(new Lemming({
color: [255, 255, 255], // color of the robot shape
init: {x: 50, y: 50, angle: 0}, // initial position and orientation
}))
resultsTable = new ResultsTable(document.getElementById("results"))
resultsTable.clear()
initSimulation()
sim.start()
};
function initSimulation() {
if (sim) {
sim.stop()
sim.destroy()
}
simInfo.boxFric = +document.getElementById("boxFric").value
simInfo.wanderRate = +document.getElementById("wanderRate").value
sim = new Simulation()
sim.init()
let noRobots = +document.getElementById('robotAmnt').value
while(noRobots--) {
sim.addRobot(new Lemming({
// random color
color: [Math.random()*255, Math.random()*255, Math.random()*255], // color of the robot shape
init: {x: 50*noRobots + 50, y: 50, angle: 0}, // initial position and orientation
}))
}
}
function promiseTimeout(time) {
return new Promise((yes, no) => setTimeout(yes, time))
}
function blocksSorted(sim) {
const {width, height} = sim
return sim.world.composites[0].bodies.every(({position: {x, y}, color: [r, g, b]}) => {
const dist = Math.min(x - 10, width - 10 - x, y - 10, height - 10 - y)
// if red block: ignore
if (r > g + b) return true
// blue block
if (b > r + g) {
const dist = Math.min(x - 10, width - 10 - x, y - 10, height - 10 - y)
// distance from wall
return dist < 70
} else {
console.warn("unknown block type")
}
})
}
function redblocksatwall(sim) {
const {width, height} = sim
let result = 0
sim.world.composites[0].bodies.forEach(({position: {x, y}, color: [r, g, b]}) => {
const dist = Math.min(x - 10, width - 10 - x, y - 10, height - 10 - y)
// if red block: count
if (r > g + b) {
if (dist < 70) result++
}
})
return result
}
async function experimentRunner(stopCondition=blocksSorted) {
initSimulation()
while(!stopCondition(sim) && sim.curSteps < simInfo.maxSteps) {
sim.runSteps(500)
await promiseTimeout(50)
}
// save data
const rv = {
steps: sim.curSteps,
done: stopCondition(sim),
redblocks: redblocksatwall(sim),
robots: sim.robots.length
}
sim.destroy()
sim = null
return rv
}
async function runExperiments() {
let count = +document.getElementById('ffwdtimes').value
while(count--) {
await (experimentRunner().then(x => resultsTable.add(x)))
}
}
class ResultsTable {
constructor(elem) {
this.elem = elem
}
clear() {
this.elem.innerHTML = "<tr><th colspan=4>Experiment Results</th></tr>"
const tr = document.createElement('tr')
const headers = ["robots", "steps", "sorted", "red blocks@wall"]
headers.forEach(txt => {
tr.appendChild(document.createElement('th')).appendChild(document.createTextNode(txt))
})
this.elem.appendChild(tr)
}
add({steps, done, redblocks, robots}) {
const tr = document.createElement('tr')
;[robots, steps, done, redblocks].forEach(txt => {
tr.appendChild(document.createElement('td')).appendChild(document.createTextNode(txt))
})
this.elem.appendChild(tr)
}
}
class Bay {
constructor() {
@ -288,6 +402,9 @@ class Bay {
this.elem.style.backgroundColor = 'silver'
addMouseTracker(this.elem);
}
destroy() {
this.elem.removeMouseTracker()
}
load(robot) {
this.robot = robot
robot.sensors.forEach(sensor => {

View File

@ -20,7 +20,7 @@ function addMouseTracker(elem) {
elem.mouse.interactiveElements = [];
elem.getInteractiveElement = findElement;
elem.addEventListener("mousedown",
function(event) {
elem.mouse.downListener = function(event) {
this.mouse.buttons = event.buttons;
this.mouse.x = event.offsetX;
this.mouse.y = event.offsetY;
@ -30,7 +30,7 @@ function addMouseTracker(elem) {
}
});
elem.addEventListener("mouseup",
function(event) {
elem.mouse.upListener = function(event) {
this.mouse.buttons = event.buttons;
if (this.mouse.targetElement != null) {
this.mouse.targetElement.endAction(event);
@ -38,7 +38,7 @@ function addMouseTracker(elem) {
}
});
elem.addEventListener("mousemove",
function(event) {
elem.mouse.moveListener = function(event) {
this.mouse.hasFocus = true;
this.mouse.x = event.offsetX;
this.mouse.y = event.offsetY;
@ -54,12 +54,19 @@ function addMouseTracker(elem) {
}
});
elem.addEventListener("mouseout",
function(event) {
elem.mouse.outListener = function(event) {
this.mouse.hasFocus = false;
});
elem.addInteractiveElement = function(obj) {
if (!obj.mouseHit) obj.mouseHit = mouseHit
this.mouse.interactiveElements.push(obj);
elem.removeMouseTracker = function() {
elem.removeEventListener("mousedown", this.mouse.downListener)
elem.removeEventListener("mouseup", this.mouse.upListener)
elem.removeEventListener("mousemove", this.mouse.moveListener)
elem.removeEventListener("mouseout", this.mouse.outListener)
delete elem.mouse
}
};
}
function mouseHit(x, y) {