From 24a650a3969dc4a1e5a493469dc5b427326b242c Mon Sep 17 00:00:00 2001 From: Rishav Agarwal Date: Mon, 19 Jun 2017 19:50:32 +0530 Subject: [PATCH] Adds Visualization for a vacuum world with no observation --- 4-Beyond-Classical-Search/c_erraticVacuum.js | 385 ++++++++++--------- 4-Beyond-Classical-Search/c_noObservation.js | 76 ++++ 4-Beyond-Classical-Search/erraticVacuum.js | 31 +- 4-Beyond-Classical-Search/index.html | 76 +++- 4-Beyond-Classical-Search/main.css | 4 +- 5 files changed, 365 insertions(+), 207 deletions(-) create mode 100644 4-Beyond-Classical-Search/c_noObservation.js diff --git a/4-Beyond-Classical-Search/c_erraticVacuum.js b/4-Beyond-Classical-Search/c_erraticVacuum.js index a8399b3..27e099a 100644 --- a/4-Beyond-Classical-Search/c_erraticVacuum.js +++ b/4-Beyond-Classical-Search/c_erraticVacuum.js @@ -1,217 +1,244 @@ -$(document).ready(function() { +class MovesRecorder { + constructor(element) { + this.list = element; + //Clears the existing elements + this.clear(); + this.moves = []; + } - class MovesRecorder { + clear() { + this.moves = []; + this.list.selectAll('*').remove(); + } - constructor(selector) { - this.list = $(selector); - //Clears the existing elements - this.clear(); - this.moves = []; - } + addLeft() { + this.moves.push('left'); + this.list.append('li') + .attr('class', 'list-group-item text-center moves-list-item waiting-move') + .text('Left'); + } - clear() { - this.moves = []; - this.list.empty(); - } + addRight() { + this.moves.push('right'); + this.list.append('li') + .attr('class', 'list-group-item text-center moves-list-item waiting-move') + .text('Right'); + } - addLeft() { - this.moves.push('left'); - this.list.append( - $('
  • ').attr('class', 'list-group-item text-center moves-list-item waiting-move').append('Left') - ) - } + addSuck() { + this.moves.push('suck'); + this.list.append('li') + .attr('class', 'list-group-item text-center moves-list-item waiting-move') + .text('Suck'); + } - addRight() { - this.moves.push('right'); - this.list.append( - $('
  • ').attr('class', 'list-group-item text-center moves-list-item waiting-move').append('Right') - ) - } + markUndone() { + this.list.selectAll('*') + .classed('done-move', false) + .classed('active', false) + .classed('waiting-move', true); + } - addSuck() { - this.moves.push('suck'); - this.list.append( - $('
  • ').attr('class', 'list-group-item text-center moves-list-item waiting-move').append('Suck') - ); + markDone(i) { + if (i - 1 > 0) { + this.list.select('li:nth-child(' + (i) + ')') + .classed('active', false) + .classed('done-move', true); } + } - markUndone() { - this.list.find('*').each(function() { - $(this).removeClass('done-move').removeClass('active').addClass('waiting-move') - }); + * getMoves() { + for (let i = 0; i < this.moves.length; i++) { + this.list.select('li:nth-child(' + (i + 1) + ')') + .classed('waiting-move', false) + .classed('active', true); + yield this.moves[i]; + this.list.select('li:nth-child(' + (i + 1) + ')') + .classed('active', false) + .classed('done-move', true); } + } - markDone(i) { - if (i - 1 > 0) { - this.list.find('li:nth-child(' + (i) + ')').removeClass('active').addClass('done-move'); - } - } +}; + +class VacuumWorldDiagram { + constructor(selector, h, w) { + this.h = h; + this.w = w; + this.selector = selector; + this.root = selector; + this.padding = 25; + this.floorCount = 5; + this.floorWidth = Math.floor(((this.w - this.padding) / this.floorCount) - this.padding); + this.floorHeight = this.h / 10; + this.robotHeight = this.h / 3; + this.robotWidth = this.floorWidth; + this.delay = 800; + this.svg = this.root.html("") + .append('svg') + .attr('height', this.h) + .attr('width', this.w); + this.xScale = state => this.padding + state * (this.padding + this.floorWidth); + this.yScale = state => (2 * this.h) / 3; + this.robotY = this.yScale(0) - this.floorHeight - this.robotHeight / 2 - this.h / 20; + } - * getMoves() { - for (let i = 0; i < this.moves.length; i++) { - this.list.find('li:nth-child(' + (i + 1) + ')').removeClass('waiting-move').addClass('active'); - yield this.moves[i]; - this.list.find('li:nth-child(' + (i + 1) + ')').removeClass('active').addClass('done-move'); - } + init(movesRecorder) { + this.svg.selectAll('*').remove(); + this.world = new ErraticWorld(this.floorCount); + this.world.randomize(); + this.initialStates = []; + for (let i = 0; i < this.world.dirt.length; i++) { + this.initialStates.push(this.world.dirt[i]); } + this.initialLocation = this.world.robotLocation; + this.movesRecorder = movesRecorder; + this.floors = []; - }; - - class ErraticWorldDiagram { - constructor(selector, h, w) { - this.h = h; - this.w = w; - this.padding = 25; - this.floorCount = 5; - this.floorWidth = Math.floor(((this.w - this.padding) / this.floorCount) - this.padding); - this.floorHeight = 40; - this.robotHeight = 100; - this.robotWidth = this.floorWidth; - this.delay = 800; - this.svg = d3.select(selector).html("") - .append('svg') - .attr('height', this.h) - .attr('width', this.w); - this.xScale = state => this.padding + state * (this.padding + this.floorWidth); - this.yScale = state => 200; - } + this.drawAll(); + } - init() { - this.svg.selectAll('*').remove(); - this.erraticWorld = new ErraticWorld(this.floorCount); - this.erraticWorld.randomize(); - this.initialStates = []; - for (let i = 0; i < this.erraticWorld.dirt.length; i++) { - this.initialStates.push(this.erraticWorld.dirt[i]); - } - this.initialLocation = this.erraticWorld.robotLocation; - this.movesRecorder = new MovesRecorder('#movesList'); - this.floors = []; + drawAll() { + this.dirt = this.world.dirt; + for (let i = 0; i < this.floorCount; i++) { + this.floors[i] = this.svg + .append('rect') + .attr('class', 'floor') + .attr('x', this.xScale(i)) + .attr('y', this.yScale(i)) + .attr('width', this.floorWidth) + .attr('height', this.floorHeight) + .attr('stroke', 'black') + .classed('dirty', this.dirt[i]); + } + + this.robot = this.svg.append('g') + .attr('class', 'cleaner') + .append('svg:image') + .attr('xlink:href', '../third-party/vacuum-cleaner.svg') + .attr('height', this.robotHeight) + .attr('width', this.robotWidth) + .attr('x', this.xScale(this.world.robotLocation)) + .attr('y', this.robotY) + .on('click', () => { + this.suckAction() + }); + } - this.drawAll(); + reset() { + this.stopMoves(); + this.world.robotLocation = this.initialLocation; + this.world.dirt = []; + for (let i = 0; i < this.initialStates.length; i++) { + this.world.dirt.push(this.initialStates[i]); } - - drawAll() { - this.dirt = this.erraticWorld.dirt; - for (let i = 0; i < this.floorCount; i++) { - this.floors[i] = this.svg - .append('rect') - .attr('class', 'floor') - .attr('x', this.xScale(i)) - .attr('y', this.yScale(i)) - .attr('width', this.floorWidth) - .attr('height', this.floorHeight) - .attr('stroke', 'black') - .classed('dirty', this.dirt[i]); - } - - this.robot = this.robot = this.svg.append('g') - .attr('class', 'cleaner') - .append('svg:image') - .attr('xlink:href', '../third-party/vacuum-cleaner.svg') - .attr('height', this.robotHeight) - .attr('width', this.robotWidth) - .attr('x', this.xScale(this.erraticWorld.robotLocation)) - .attr('y', this.yScale(0) - this.floorHeight - this.robotHeight / 2 - 10) - .on('click', () => { - this.suckAction() - }); - this.bindClicks(); + this.robot.remove(); + for (let i = 0; i < this.floors.length; i++) { + this.floors[i].remove(); } + this.drawAll(); + } - reset() { - this.stopMoves(); - this.erraticWorld.robotLocation = this.initialLocation; - this.erraticWorld.dirt = []; - for (let i = 0; i < this.initialStates.length; i++) { - this.erraticWorld.dirt.push(this.initialStates[i]); + + playMoves() { + this.moveList = this.movesRecorder.getMoves(); + this.stopMoves(); + this.intervalFunction = setInterval(() => { + let next = this.moveList.next(); + switch (next.value) { + case 'left': + this.moveRobotLeft(); + break; + case 'right': + this.moveRobotRight(); + break; + case 'suck': + this.suckAction(); + break; } - this.robot.remove(); - for (let i = 0; i < this.floors.length; i++) { - this.floors[i].remove(); + if (next.done) { + this.stopMoves(); } - this.drawAll(); - } + }, this.delay) + } - bindClicks() { - d3.select('#erraticMoveLeft').on('click', () => { - this.movesRecorder.addLeft() - }); - d3.select('#erraticMoveRight').on('click', () => { - this.movesRecorder.addRight() - }); - d3.select('#erraticVacuum').on('click', () => { - this.movesRecorder.addSuck() - }); - d3.select('#movesPlayBtn').on('click', () => { - this.reset(); - this.movesRecorder.markUndone(); - this.playMoves(); - }); + stopMoves() { + clearInterval(this.intervalFunction, this.delay); + } - d3.select('#movesClearBtn').on('click', () => { - this.reset(); - this.movesRecorder.clear(); - }); - } + moveRobotLeft() { + this.world.moveLeft(); + this.robot.transition() + .duration(300) + .attr('x', this.xScale(this.world.robotLocation)) + .attr('y', this.robotY); - playMoves() { - this.moveList = this.movesRecorder.getMoves(); - this.stopMoves(); - this.intervalFunction = setInterval(() => { - let next = this.moveList.next(); - switch (next.value) { - case 'left': - this.moveRobotLeft(); - break; - case 'right': - this.moveRobotRight(); - break; - case 'suck': - this.suckAction(); - break; - } - if (next.done) { - this.stopMoves(); - } - }, this.delay) - } + } - stopMoves() { - clearInterval(this.intervalFunction, this.delay); - } + moveRobotRight() { + this.world.moveRight(); + this.robot.transition() + .duration(300) + .attr('x', this.xScale(this.world.robotLocation)) + .attr('y', this.robotY); + } - moveRobotLeft() { - this.erraticWorld.moveLeft(); - this.robot.transition() - .duration(300) - .attr('x', this.xScale(this.erraticWorld.robotLocation)) - .attr('y', this.yScale(0) - this.floorHeight - this.robotHeight / 2 - 10); + suckAction() { + this.world.suck(); + for (let i = 0; i < this.floorCount; i++) { + this.floors[i].classed('dirty', this.dirt[i]); } + } - moveRobotRight() { - this.erraticWorld.moveRight(); - this.robot.transition() - .duration(300) - .attr('x', this.xScale(this.erraticWorld.robotLocation)) - .attr('y', this.yScale(0) - this.floorHeight - this.robotHeight / 2 - 10); - } +} +class ErraticWorldDiagram extends VacuumWorldDiagram { - suckAction() { - this.erraticWorld.erraticSuck(); - for (let i = 0; i < this.floorCount; i++) { - this.floors[i].classed('dirty', this.dirt[i]); - } + constructor(selector, h, w) { + super(selector.select('.canvas'), h, w); + this.selector = selector; + this.bindClicks(); + } + //Overwrite Suck Action + suckAction() { + this.world.erraticSuck(); + for (let i = 0; i < this.floorCount; i++) { + this.floors[i].classed('dirty', this.dirt[i]); } + } + bindClicks() { + this.selector.select('.left-button').on('click', () => { + this.movesRecorder.addLeft() + }); + this.selector.select('.right-button').on('click', () => { + this.movesRecorder.addRight() + }); + this.selector.select('.suck-button').on('click', () => { + this.movesRecorder.addSuck() + }); + this.selector.select('.play-button').on('click', () => { + this.reset(); + this.movesRecorder.markUndone(); + this.playMoves(); + }); + + this.selector.select('.clear-button').on('click', () => { + this.reset(); + this.movesRecorder.clear(); + }); } +} + +$(document).ready(function() { + var init = function() { - var erraticWorldDiagram = new ErraticWorldDiagram('#erraticVacuumCanvas', 300, 600); - erraticWorldDiagram.init(); + var worldDiagram = new ErraticWorldDiagram(d3.select('#erraticVacuum'), 300, 600); + var movesRecorder = new MovesRecorder(d3.select('#erraticVacuum .movesList')); + worldDiagram.init(movesRecorder); } - $('#erraticVacuumRestart').on('click', init) + $('#erraticVacuum .restart-button').on('click', init) init(); }) diff --git a/4-Beyond-Classical-Search/c_noObservation.js b/4-Beyond-Classical-Search/c_noObservation.js new file mode 100644 index 0000000..39039d6 --- /dev/null +++ b/4-Beyond-Classical-Search/c_noObservation.js @@ -0,0 +1,76 @@ +class NoObservationDiagram { + constructor(selector, h, w, count) { + this.h = h; + this.w = w; + this.selector = d3.select(selector); + this.count = count; + this.worlds = []; + this.movesRecorder = new MovesRecorder(this.selector.select('.movesList')); + + var diagramHeight = this.h / (count / 2); + var diagramWidth = this.w / 2; + + this.selector.select('#canvasWrapper').selectAll('.canvas') + .data(new Array(count)) + .enter() + .append('div') + .attr('class', (d, i) => { + return 'canvas' + i + }) + .attr('height', diagramHeight) + .attr('width', diagramWidth) + .style('float', 'left') + .style('margin-right', '6%'); + + for (let i = 0; i < count; i++) { + this.worlds.push(new VacuumWorldDiagram(this.selector.select('.canvas' + i), diagramHeight, diagramWidth)); + this.worlds[i].init(this.movesRecorder); + } + this.bindClicks(); + } + + reset() { + for (let i = 0; i < this.worlds.length; i++) { + this.worlds[i].reset(); + } + } + + playMoves() { + for (let i = 0; i < this.worlds.length; i++) { + this.worlds[i].playMoves(); + } + } + + bindClicks() { + this.selector.select('.left-button').on('click', () => { + this.movesRecorder.addLeft() + }); + this.selector.select('.right-button').on('click', () => { + this.movesRecorder.addRight() + }); + this.selector.select('.suck-button').on('click', () => { + this.movesRecorder.addSuck() + }); + this.selector.select('.play-button').on('click', () => { + this.reset(); + this.movesRecorder.markUndone(); + this.playMoves(); + }); + + this.selector.select('.clear-button').on('click', () => { + this.reset(); + this.movesRecorder.clear(); + }); + } + +} +$(document).ready(function() { + + + var init = function() { + var noObservationDiagram = new NoObservationDiagram('#noObservation', 400, 600, 8); + } + + $('#noObservation .restart-button').on('click', init) + init(); +}) diff --git a/4-Beyond-Classical-Search/erraticVacuum.js b/4-Beyond-Classical-Search/erraticVacuum.js index df75c76..22bc5ea 100644 --- a/4-Beyond-Classical-Search/erraticVacuum.js +++ b/4-Beyond-Classical-Search/erraticVacuum.js @@ -1,5 +1,4 @@ -class ErraticWorld { - +class VacuumWorld { constructor(floorCount) { this.floorCount = (floorCount == undefined) ? 2 : floorCount; this.dirt = new Array(this.floorCount).fill(false); @@ -26,6 +25,24 @@ class ErraticWorld { } } + suck() { + this.clean(this.robotLocation); + } + + randomize() { + for (let i = 0; i < this.floorCount; i++) { + this.dirt[i] = (Math.random() < 0.5); + } + this.robotLocation = Math.floor(Math.random() * this.floorCount); + } +} + +class ErraticWorld extends VacuumWorld { + + constructor(floorCount) { + super(floorCount); + } + erraticSuck() { let i = this.robotLocation; if (this.dirt[i]) { @@ -46,16 +63,6 @@ class ErraticWorld { this.makeDirt(i); } } - } - - normalSuck() { - this.clean(this.robotLocation); - } - randomize() { - for (let i = 0; i < this.floorCount; i++) { - this.dirt[i] = (Math.random() < 0.5); - } - this.robotLocation = Math.floor(Math.random() * this.floorCount); } }; diff --git a/4-Beyond-Classical-Search/index.html b/4-Beyond-Classical-Search/index.html index 1236db3..71e70a0 100644 --- a/4-Beyond-Classical-Search/index.html +++ b/4-Beyond-Classical-Search/index.html @@ -25,6 +25,7 @@ + @@ -103,47 +104,95 @@

    The erratic vacuum world

  • If the tile is dirty, the suck action cleans up the tile and sometimes also clean up dirt in adjacent tiles
  • If the tile is clean, the suck action sometimes deposits dirt on the carpet.
  • -

    Given below is a 5 tile erratic vacuum world. You can record a sequence of actions which you believe will clean up all the dirt. Use the play the button to execute that sequence. - Play the sequence multiple times to see how they can result in different end state sometimes. Use restart button to generate a new initial state. Use clear button to clear the recorded sequence - and record a new one.

    -
    +

    Given below is a 5 tile erratic vacuum world. You can record a sequence of actions which you believe will clean up all the dirt. Use the play the button to execute that sequence. Play the sequence multiple times to see how they can result in different + end state sometimes. Use restart button to generate a new initial state. Use clear button to clear the recorded sequence and record a new one.

    +
    -
    Restart
    +
    Restart
    -
    Left
    +
    Left
    -
    Vacuum
    +
    Vacuum
    -
    Right
    +
    Right
    -
    +
    -
    Play
    +
    Play
    -
    Clear
    +
    Clear
    Moves
    -
    -
      +
      +
        + +
      +
      +
    + +
    + + + +

    Searching with Partial Observations

    Now we come back to a world where the actions of the robot are deterministic again (no erratic behavior like before) but, the robot no longer has complete sense of its current state or its environment. +

    Vacuum World with no observation

    +

    In this world, the vacuum cleaner has no idea initially about its own location and the location of dirt in the world. Since the robot has no percept, it should be able to figure out a sequence of actions that will work despite its current state.

    +

    Given below are 8 random initial states. You can record a sequence of actions and see it in action just like before. Assume that illegal moves (like moving right in the right-most tile) have no effect on the world.

    +

    Try to find a sequence of actions that will lead to a final state (Clean all the dirt), no matter what the initial state of the world.

    +
    +
    +
    +
    +
    Restart
    +
    +
    +
    +
    +
    Left
    +
    +
    +
    Vacuum
    +
    +
    +
    Right
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Play
    +
    +
    +
    Clear
    +
    +
    +
    Moves
    +
    +
    @@ -151,7 +200,6 @@
    Moves
    -

    And-Or-Graph-Search

    diff --git a/4-Beyond-Classical-Search/main.css b/4-Beyond-Classical-Search/main.css index bf95ab5..eb20d43 100644 --- a/4-Beyond-Classical-Search/main.css +++ b/4-Beyond-Classical-Search/main.css @@ -50,11 +50,11 @@ fill: hsl(0, 50%, 50%); transition: fill 0.1s; } -#movesWrapper { +.movesWrapper { overflow-y: scroll; max-height: 250px; } -#movesList { +.movesList { padding-left: 0; } .moves-list-item {