> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.OyQtf99prDW/rev.4145
 * 
 * authors: 
 *   J.D. Zamfirescu
 *   J.D. Zamfirescu
 *   J.D. Zamfirescu

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



init();

// level 1
down();
down();
down();
down();
down();
down();
down();
right();
right();
right();
up();
up();
right();
right();
right();
right();

// level 2
var doSeven = function(dir) {
    dir();
    dir();
    dir();
    dir();
    dir();
    dir();
    dir();
}
down();
doSeven(down);
right();
right();
doSeven(up);
right();
right();
doSeven(down);
right();
right();
doSeven(up);
right();
right();
doSeven(down);
right();

// level 3
right();
right();
if (color() == "blue") {
    down();
} else {
    up();
}
right();
right();
if (color() == "blue") {
    down();
} else {
    up();
}
right();
right();
if (color() == "blue") {
    down();
} else {
    up();
}
right();
right();
right();

// level 4
goUntilColor = function(dir, c) {
    if (color() != c) {
        dir()
        goUntilColor(dir, c);
    }
}
goUntilColor(down, "red");
right();
right();
goUntilColor(up, "red");
right();
right();
goUntilColor(down, "red");
right();
right();
goUntilColor(up, "red");
right();
right();
goUntilColor(down, "red");
right();
























// ROBOT CODE

size(340, 340);
background(255);

var MOVETIME = 200;

var position = null;
function init() {
    window.sim = {
        position: null,
        levels: [],
        simulation: [],
        level: 0
    };
    with(window.sim) {
        levels = [
            {trash: new PositionSet([ {x: 5, y: 5}, {x: 7, y: 5} ]),
             obstacles: makeObstacles(2, 0, 1, 7) },
            {trash: new PositionSet([ {x: 9, y: 8 } ]),
             obstacles: makeObstacles(1,0,1,8).concat(makeObstacles(3,2,1,8)).concat(makeObstacles(5,0,1,8)).concat(makeObstacles(7,2,1,8)).concat(makeObstacles(9,0,1,8)) },
            level3(),
            level4()
        ];
        position = levels[level].start || {x: 0, y: 0};
        simulation.push({type: "level", level: level, pos: position});
        console.log("done!", window.sim);
    }
}
var position = window.sim.position;
var levels = window.sim.levels;
var simulation = window.sim.simulation;
var level = window.sim.level;
console.log("simulation: ", simulation);

function color() {
    with (window.sim) {
        if (! levels[level].colors) { return false; }
        var c = levels[level].colors.contains(position.x, position.y);
        return c ? c.hue : false;
    }
}

function level3() {
    var holes = [5];
    for (var i = 1; i <= 3; ++i) {
        holes.push(holes[i-1]+(Math.random()<0.5 ? -1 : 1));
    }
    var start = 1;
    var obstacles = new PositionSet([]);
    var colors = new PositionSet([]);
    for (var i = 0; i < holes.length; ++i) {
        obstacles = obstacles
            .concat(makeObstacles(start+i*2, 0, 1, holes[i]))
            .concat(makeObstacles(start+i*2, holes[i]+1, 1, (9-holes[i])));
        if (i > 0) {
            colors.push({x: (start-1)+i*2, y: holes[i-1], hue: holes[i] < holes[i-1] ? "red" : "blue"});
        }
    }
    var ret = {
        start: { x: 0, y: 5 },
        trash: new PositionSet([ { x:9, y: holes[holes.length-1] } ]),
        obstacles: obstacles,
        colors: colors
    };
    return ret;    
}

function level4() {
    var holes = [
        Math.floor(Math.random()*5+5),
        Math.floor(Math.random()*5),
        Math.floor(Math.random()*5+5),
        Math.floor(Math.random()*5) 
    ];
    var start = 1;
    var obstacles = new PositionSet([]);
    var colors = new PositionSet([{x:8, y:9, hue:"red"}]);
    for (var i = 0; i < holes.length; ++i) {
        obstacles = obstacles
            .concat(makeObstacles(start+i*2, 0, 1, holes[i]))
            .concat(makeObstacles(start+i*2, holes[i]+1, 1, (9-holes[i])));
        colors.push({x: (start-1)+i*2, y: holes[i], hue: "red"});
    }
    var ret = {
        start: { x: 0, y: 0 },
        trash: new PositionSet([ { x:9, y: 9 } ]),
        obstacles: obstacles,
        colors: colors
    };
    return ret;
}

function addCommand(op) {
    with (window.sim) {
        var newPos = applyCommand(op);
        if (newPos) {
            position = newPos;
            simulation.push({type: "move", pos: position});
            var trash = levels[level].trash.contains(position.x, position.y);
            if (trash) {
                trash.found = true;
                simulation.push({type: "found-trash", pos: position});
            }
            if (levels[level].trash.count("found", true) == levels[level].trash.list.length) {
                // advance level
                level++;
                if (level < levels.length) {
                    position = levels[level].start || {x: 0, y: 0};
                    simulation.push({type: "level", level: level, pos:    position});
                }
            }
        } else {
            simulation.push({type: "move", pos: position});
        }
    }
}
function left() {
    addCommand('left');
}
function right() {
    addCommand('right');
}
function up() {
    addCommand('up');
}
function down() {
    addCommand('down');
}

function applyCommand(command) {
    with (window.sim) {
        var np = null;
        switch(command) {
            case 'right':
                np = {x: position.x+1, y: position.y};
                break;
            case 'left':
                np = {x: position.x-1, y: position.y};
                break;
            case 'up':
                np = {x: position.x, y: position.y-1};
                break;
            case 'down':
                np = {x: position.x, y: position.y+1};
                break;
            default:
                println("unknown command: "+command);
        }
        if (np && ! levels[level].obstacles.contains(np.x, np.y)) {
            return np;
        } else {
            return false;
        }
    }
}

var GRIDWIDTH=30;
var MARGIN=20;
function makeObstacles(x, y, width, height) {
    var out = [];
    for (var i = 0; i < width; ++i) {
        for (var j = 0; j < height; ++j) {
            out.push({x: x+i, y: y+j});
        }
    }
    return new PositionSet(out);
}

function ellipseAtLoc(loc) {
    ellipse(MARGIN+(loc.x+0.5)*GRIDWIDTH, MARGIN+(loc.y+0.5)*GRIDWIDTH, GRIDWIDTH/2, GRIDWIDTH/2);
}

var obstacleGrid;
var nextPosition;
var programCounter = 0;

var moveStart = 0;
function drawCurrentPosition() {
    var p = position;
    var np = nextPosition;
    fill(238, 0, 0); // red
    ellipseAtLoc({ 
        x: p.x+(np.x-p.x)*Math.min(MOVETIME, millis()-moveStart)/MOVETIME,
        y: p.y+(np.y-p.y)*Math.min(MOVETIME, millis()-moveStart)/MOVETIME
    });
}
function updatePosition() {
    if (millis()-moveStart > MOVETIME) {
        console.log("next!");
        if (programCounter >= simulation.length) {
            clearInterval(interval);
            return;
        }
        position = nextPosition;
        var resume = false;
        while (! resume && programCounter < simulation.length) {
            var cmd = simulation[programCounter++];
            switch (cmd.type) {
                case 'level':
                    nextPosition = position = cmd.pos;
                    level = cmd.level;
                    break;
                case 'found-trash':
                    levels[level].trash.contains(cmd.pos.x, cmd.pos.y).hide = true;
                    break;
                case 'move':
                    console.log("moving to", cmd.pos.x+","+cmd.pos.y);
                    nextPosition = cmd.pos;
                    resume = true;
                    break;
                default:
                    break;
            }
        }
        moveStart = millis();
    }
}

function PositionSet(list, grid) {
    var posRegex = new RegExp("(\\d+),(\\d)");
    if (list && ! grid) {
        grid = {};
        list.forEach(function(pos) {
            grid[pos.x+","+pos.y] = pos;
        });
    }
    if (grid && ! list) {
        list = [];
        for (var k in grid) {
            var obj = grid[k];
            var parts = posRegex.exec(k);
            if (parts) {
                obj.x = parts[1];
                obj.y = parts[2];
                list.push(obj);
            }
        }
    }
    if (! grid || ! list) {
        return null;
    }
    this.list = list;
    this.grid = grid;
    this.contains = function(x, y) {
        return this.grid[x+","+y];
    }
    this.count = function(property, value) {
        return this.list.filter(function(x) { return x[property] == value; }).length;
    }
    this.push = function(pos) {
        this.list.push(pos);
        this.grid[pos.x+","+pos.y] = pos;
    }
    this.concat = function(positionSet) {
        return new PositionSet(this.list.concat(positionSet.list));
    }
    this.forEach = function(f) {
        return this.list.forEach(f);
    }
}

function setFill(hue) {
    switch (hue) {
        case "red":
            fill(255, 220, 220);
            break;
        case "blue":
            fill(200, 200, 255);
            break;
        case "green":
            fill(200, 255, 200);
            break;
        default:
            break;
    }
}

function drawColors() {
    if (levels[level].colors) {
        levels[level].colors.forEach(function(c) {
            setFill(c.hue);
            rect(MARGIN+c.x*GRIDWIDTH+1,
                 MARGIN+c.y*GRIDWIDTH+1,
                 GRIDWIDTH-1, GRIDWIDTH-1);
        });
    }
}

function drawObstacles() {
    fill(204);
    levels[level].obstacles.forEach(function(o) {
        rect(MARGIN+o.x*GRIDWIDTH,
             MARGIN+o.y*GRIDWIDTH,
             GRIDWIDTH, GRIDWIDTH);
    });
}

function redraw() {
    background(255);

    PFont fontA = loadFont("Courier New");
    textFont(fontA, 12);
    textAlign(LEFT);
    trashFound = levels[level].trash.list.length - levels[level].trash.count("hide", true);
    text("Trash remaining: "+trashFound, 5, 15);

    stroke(204);
    for (var i = 0; i <= (width-MARGIN/2)/GRIDWIDTH; ++i) {
        line(MARGIN+i*GRIDWIDTH, MARGIN, MARGIN+i*GRIDWIDTH, height-MARGIN);
    }
    for (var i = 0; i <= (height-MARGIN*2)/GRIDWIDTH; ++i) {
        line(MARGIN, MARGIN+i*GRIDWIDTH, width-MARGIN, i*GRIDWIDTH+MARGIN);
    }
    noStroke();
    drawColors();
    drawObstacles();
    fill(0, 238, 0); // green
    levels[level].trash.list.filter(function(x) { return ! x.hide; }).forEach(ellipseAtLoc);
    drawCurrentPosition();
}

function draw() {
    updatePosition();
    redraw();
}
var interval = setInterval(draw, 30);