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":"seen","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":"seen","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(); });