GOPHERSPACE.DE - P H O X Y
gophering on dusted.dk
document.addEventListener('DOMContentLoaded', ()=>{
    const pos = {
        x: 5,
        y: 10,
        d: 0,
    };

    const pan = {
        x: 0, 
        y: 0
    };

    const labels = document.getElementById('labels');
    const txtBox = document.getElementById('txtBox');
    const data= document.getElementById('data');
    const importBtn = document.getElementById('importBtn');
    const can = document.getElementById('can');
    const ctx = can.getContext("2d");

    ctx.translate(0.5,0.5);
    ctx.imageSmoothingEnabled= false;

    const coffset = {
        x: can.getBoundingClientRect().left,
        y: can.getBoundingClientRect().top
    };

    let doorStyle=false;
    let chromaBg=false;

    let graph = [{"t":"seen","x":10,"y":5},{"t":"lvlup","x":10,"y":5},{"t":"seen","x":10,"y":6},{"t":"seen","x":10,"y":7},{"t":
"seen","x":10,"y":9},{"t":"seen","x":11,"y":9},{"t":"seen","x":12,"y":9},{"t":"seen","x":12,"y":10},{"t":"seen","x":12,"y":11},
{"t":"lvldn","x":12,"y":11},{"t":"seen","x":12,"y":7},{"t":"seen","x":12,"y":6},{"t":"seen","x":12,"y":5},{"t":"seen","x":13,"y
":7},{"t":"seen","x":14,"y":7},{"t":"seen","x":14,"y":6},{"t":"seen","x":14,"y":5},{"t":"seen","x":15,"y":5},{"t":"seen","x":16
,"y":5},{"t":"seen","x":17,"y":5},{"t":"seen","x":18,"y":5},{"t":"seen","x":12,"y":12},{"t":"seen","x":12,"y":13},{"t":"seen","
x":13,"y":13},{"t":"seen","x":14,"y":13},{"t":"seen","x":16,"y":13},{"t":"seen","x":17,"y":13},{"t":"seen","x":18,"y":13},{"t":
"seen","x":16,"y":12},{"t":"seen","x":16,"y":11},{"t":"seen","x":16,"y":10},{"t":"seen","x":16,"y":9},{"t":"seen","x":15,"y":9}
,{"t":"seen","x":14,"y":9},{"t":"seen","x":14,"y":10},{"t":"seen","x":14,"y":11},{"t":"seen","x":17,"y":9},{"t":"seen","x":18,"
y":9},{"t":"seen","x":18,"y":8},{"t":"seen","x":18,"y":7},{"t":"seen","x":18,"y":11},{"t":"seen","x":18,"y":12},{"t":"seen","x"
:16,"y":7},{"t":"seen","x":14,"y":21},{"t":"lvlup","x":14,"y":21},{"t":"seen","x":13,"y":21},{"t":"half","x":13,"y":22},{"t":"s
een","x":12,"y":21},{"t":"half","x":11,"y":22},{"t":"seen","x":11,"y":21},{"t":"seen","x":15,"y":21},{"t":"seen","x":16,"y":21}
,{"t":"fence","d":"v","x":17,"y":21},{"t":"seen","x":18,"y":21},{"t":"half","x":18,"y":22},{"t":"seen","x":19,"y":21},{"t":"see
n","x":20,"y":21},{"t":"half","x":20,"y":20},{"t":"seen","x":16,"y":20},{"t":"seen","x":16,"y":19},{"t":"txt","v":"Mines of Mt.
 Drash","x":10,"y":3},{"t":"txt","v":"Level 1","x":13,"y":8},{"t":"txt","v":"Level 2","x":14,"y":22},{"t":"door","d":"h","x":16
,"y":22,"hidden":false},{"t":"door","d":"h","x":18,"y":10,"hidden":false},{"t":"door","d":"v","x":15,"y":11,"hidden":false},{"t
":"door","d":"h","x":18,"y":6,"hidden":true},{"t":"door","d":"h","x":16,"y":6,"hidden":false},{"t":"door","d":"v","x":13,"y":11
,"hidden":false},{"t":"door","d":"h","x":12,"y":8,"hidden":false},{"t":"door","d":"h","x":10,"y":8,"hidden":true},{"t":"door","
d":"v","x":11,"y":5,"hidden":false},{"t":"door","d":"v","x":13,"y":9,"hidden":false},{"t":"door","d":"v","x":15,"y":7,"hidden":
false},{"t":"door","d":"v","x":15,"y":13,"hidden":false},{"t":"door","d":"h","x":16,"y":8,"hidden":false}];

    function draw() {
        ctx.save();
        ctx.clearRect(0,0, can.width, can.height);
        if(chromaBg) {
            ctx.rect(0,0, can.width, can.height);
            ctx.fillStyle = "#00ffff";
            ctx.fill();
            ctx.fillStyle="black";

        }
        ctx.transform(1, 0, 0, 1, pan.x*64, pan.y*64);

        // Draw stuff from graph
        labels.innerHTML='';
        graph.forEach( item=>{
            drawFunc[item.t](item);
            if(item.t === 'txt') {
                addLabel(item);
            }
        });
        // Draw cursor
        ctx.beginPath();
        ctx.arc( pos.x*32, pos.y*32, 10,0,  2*Math.PI);
        ctx.fillStyle = 'rgba(255,255,0,0.5)';
        ctx.fill();
        ctx.stroke();
        ctx.fillStyle = "red";

        ctx.beginPath();
        switch(pos.d) {
            case 0:
                ctx.arc(pos.x*32, pos.y*32-5 ,3, 0, 2*Math.PI);
                break;
            case 1:
                ctx.arc(pos.x*32+5, pos.y*32, 3, 0, 2*Math.PI);
                break;
            case 2:
                ctx.arc(pos.x*32, pos.y*32+5, 3, 0, 2*Math.PI);
                break;
            case 3:
                ctx.arc(pos.x*32-5, pos.y*32, 3, 0, 2*Math.PI);
                break;
        }
        ctx.fill();

        ctx.restore();
        data.value = JSON.stringify(graph, null, 4);
    }

    function addLabel(item) {
        const link = document.createElement('a');
        const br = document.createElement('br');
        const linkTxt = document.createTextNode(item.v);
        link.appendChild(linkTxt);
        link.title='Show '+item.v;
        link.onclick=panTo.bind(null, item);
        link.href='#';
        labels.appendChild(link);
        labels.appendChild(br);
    }


    function panTo(item) {
        pan.x = -(item.x/2);
        pan.y = -(item.y/2);
        pan.x += 4;
        pan.y += 4;
        draw();
    }

    const drawFunc = {
        seen: drawSeen,
        half: drawHalfSeen,
        lvlup: drawLvlUp,
        lvldn: drawLvlDown,
        door: drawDoor,
        fence: drawFence,
        txt: drawTxt,
    };
    
    function drawTxt(item) {
        ctx.fillStyle = "green";
        ctx.font = "16px Arial";
        ctx.fillText(item.v, item.x*32-10, item.y*32+6);
    }
    function drawSeen(item) {
        if(item.hidden) {
            drawHalfSeen(item);
            return;
        }

        ctx.beginPath();
        ctx.rect( item.x*32-16, item.y*32-16, 32, 32);
        ctx.fillStyle='#aaaaff';
        ctx.fill();

        //For each wall
        //Top:
        if(nothing(item.x, item.y-1)) {
            ctx.beginPath();
            ctx.moveTo( item.x*32-16, item.y*32-16 );
            ctx.lineTo( item.x*32+16, item.y*32-16);
            ctx.stroke();
        }
        //bottom:
        if(nothing(item.x, item.y+1)) {
            ctx.beginPath();
            ctx.moveTo( item.x*32-16, item.y*32+16 );
            ctx.lineTo( item.x*32+16, item.y*32+16);
            ctx.stroke();
        }
        //left
        if(nothing(item.x-1, item.y)) {
            ctx.beginPath();
            ctx.moveTo( item.x*32-16, item.y*32+16 );
            ctx.lineTo( item.x*32-16, item.y*32-16);
            ctx.stroke();
        }
        //right
        if(nothing(item.x+1, item.y)) {
            ctx.beginPath();
            ctx.moveTo( item.x*32+16, item.y*32+16 );
            ctx.lineTo( item.x*32+16, item.y*32-16);
            ctx.stroke();
        }
    }

    function nothing(x,y) {
        return !graph.some( item=> (item.x===x && item.y===y && item.t !== 'txt'));
    }

    function drawHalfSeen(item) {
        ctx.beginPath();
        ctx.rect( item.x*32-16, item.y*32-16, 32, 32);
        ctx.fillStyle='#aaaaaa';
        ctx.fill();

    }

    function drawLvlUp(item) {
        drawSeen(item);
        ctx.beginPath();
        ctx.moveTo( item.x*32 , item.y*32-9  ); // top
        ctx.lineTo( item.x*32+9 , item.y*32+9  ); // right
        ctx.lineTo( item.x*32-9 , item.y*32+9  ); // left
        ctx.closePath();
        ctx.fillStyle ='#348078';
        ctx.fill();
    }

    function drawLvlDown(item) {
        drawSeen(item);
        ctx.beginPath();
        ctx.moveTo( item.x*32 , item.y*32+9  ); // bottom
        ctx.lineTo( item.x*32+9 , item.y*32-9  ); // right
        ctx.lineTo( item.x*32-9 , item.y*32-9  ); // left
        ctx.closePath();
        ctx.fillStyle ='#348078';
        ctx.fill();
    } 

    function drawFence(item) {
        drawSeen(item);

        ctx.beginPath();
        if(item.d === 'v') {
            ctx.moveTo( item.x*32, item.y*32 - 16);
            ctx.lineTo( item.x*32, item.y*32+16);
        } else {
            ctx.moveTo( item.x*32-16, item.y*32);
            ctx.lineTo( item.x*32+16, item.y*32);
        }
        ctx.strokeStyle="brown";
        ctx.stroke();
        ctx.strokeStyle="black";
    }

    function drawDoor(item) {
    
        drawSeen({x:item.x, y:item.y});
        ctx.beginPath();
        switch(item.d) {
            case 'h':
                if(doorStyle) {
                    ctx.moveTo( item.x*32-16, item.y*32-16)
                    ctx.lineTo( item.x*32+16, item.y*32-16);
                    ctx.moveTo( item.x*32-16, item.y*32+16)
                    ctx.lineTo( item.x*32+16, item.y*32+16);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.rect( item.x*32-8, item.y*32-18, 16, 5);
                    ctx.rect( item.x*32-8, item.y*32+14, 16, 5);
                } else {
                    ctx.moveTo( item.x*32-16, item.y*32)
                    ctx.lineTo( item.x*32+16, item.y*32);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.rect( item.x*32-8, item.y*32-2, 16, 5);
                }
                break;
            case 'v':
                if(doorStyle) {
                    ctx.moveTo( item.x*32+16, item.y*32+16)
                    ctx.lineTo( item.x*32+16, item.y*32-16);
                    ctx.moveTo( item.x*32-16, item.y*32+16)
                    ctx.lineTo( item.x*32-16, item.y*32-16);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.rect( item.x*32-18, item.y*32-8, 5, 16);
                    ctx.rect( item.x*32+14, item.y*32-8, 5, 16);
                } else {
                    ctx.moveTo( item.x*32, item.y*32+16)
                    ctx.lineTo( item.x*32, item.y*32-16);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.rect( item.x*32-2, item.y*32-8, 5, 16);
                }
                break;
        }

        if(item.hidden) {
            ctx.fillStyle = '#a86f32';
        } else {
            ctx.fillStyle = "#bebebe";
        }

        ctx.fill();
        ctx.stroke();


    }

    window.addEventListener('keydown', (e)=>{
        if(document.activeElement.tagName !== 'BODY') {
            return;
        }
        let prevent=true;
        switch(e.which) {
            case 38: // up
                pos.y--;
                pos.d=0;
                updatePanForCursor();
                break;
            case 40: // down
                pos.y++;
                pos.d=2;
                updatePanForCursor();
                break;
            case 37: // left
                pos.x--;
                pos.d=3;
                updatePanForCursor();
                break;
            case 39: // right
                pos.x++;
                pos.d=1;
                updatePanForCursor();
                break;
            case 32: // spacebar
                toggleCellSeen(1);
                break;
//            case 17: // Ctrl
                break;
            case 49: // 1: doors
                toggleCellSeen(2);
                break;
            case 50: // 2: half-seen
                toggleCellSeen(1,true);
                break;
            case 51: // 3:  lvl up
                toggleCellSeen(4);
                break;
            case 52:
                toggleCellSeen(5);
                break;
            case 16: // shift
                toggleHidden();
                break;
            case 53: // 5: vfence
                toggleCellSeen(6);
                break;
            case 54: // 6:
                break;
            case 104: // keypad up: pan
                pan.y++;
                break;
            case 98: // keypad down
            case 101: // keypad down
                pan.y--;
                break;
            case 100: // left
                pan.x++;
                break;
            case 102:
                pan.x--;
                break;
            case 13: // Enter = text
                toggleCellSeen(8);
                break;
            case 82: // r = rotate
                pos.d++;
                if(pos.d===4) pos.d=0;
                break;
            case 69: // e = rotate the other way
                pos.d--;
                if(pos.d <0) pos.d =3;
                break;
            case 68: // d = doorStyle
                doorStyle = !doorStyle;
                break;
            case 67: // c = toggle chroma bg
                chromaBg = !chromaBg;
                break;
            default:
                console.log('unhandled keycode: '+e.which);
                prevent=false;
        }

        if(prevent) {
            e.preventDefault();
        }


        draw();

    });

    function updatePanForCursor() {
        if(pos.x*32 + pan.x*64 > 512) {
            pan.x--;
        }
        if(pos.x*32 + pan.x*64 < 0) {
            pan.x++;
        }
        if(pos.y*32 + pan.y*64 > 512) {
            pan.y--;
        }
        if(pos.y*32 + pan.y*64 < 0) {
            pan.y++;
        }
    }

    function toggleHidden() {
        graph.forEach( item=> {
            if(item.x === pos.x && item.y === pos.y) {
                item.hidden=!item.hidden;
            }
        });
    }

    function toggleCellSeen(type, hidden) {

        //Find any cells at cursor pos
        let found=false;
        graph = graph.filter( item =>{
            if(item.x === pos.x && item.y === pos.y ) {
                found=true;
                return false;
            }
            return true;
        });

        if(!found) {
            switch(type) {
                case 1:
                    graph.push( { t: 'seen', x: pos.x, y: pos.y, hidden:hidden});
                    break;
                case 2: // doors
                    addInline('door');
                    break;
                case 4: // level up
                    graph.push( { t:'lvlup', x: pos.x, y: pos.y } );
                    break;
                case 5: // level down
                    graph.push( { t:'lvldn', x: pos.x, y: pos.y } );
                    break;
                case 6: // fence
                    addInline('fence');
                    break;
                case 8: //text
                    graph.push( { t: 'txt', v: txtBox.value, x: pos.x, y: pos.y });
                    break;
            }
        }

        // Sort the array, doors last!
        graph = graph.sort( (a,b)=>{
            if(a.t === 'door') {
                return 1;
            }
            return -1;
        });


        
    }

    function addInline(t) {
        if( !nothing(pos.x, pos.y-1) || !nothing(pos.x, pos.y+1))
        {
                    graph.push( { t, d:'h', x: pos.x, y: pos.y } );
        } else if(!nothing(pos.x-1, pos.y) || !nothing(pos.x+1, pos.y) ) {
                    graph.push( { t, d:'v', x: pos.x, y: pos.y } );
        }
    }
    

    can.addEventListener('click', (e)=>{
        const x = e.layerX - coffset.x;
        const y = e.layerY - coffset.y;
        pos.x = Math.round( ((x - pan.x*64)/32) );
        pos.y = Math.round( ((y - pan.y*64)/32) );
        draw();
    });

    importBtn.addEventListener('click', ()=> {
        try {
            graph = JSON.parse(data.value);
        } catch(ignored) {
            graph = [];
        }

        draw();
    });

    draw();
});