diff --git a/assets/default.5f5cce99c868ff445756.js b/assets/default.5f5cce99c868ff445756.js deleted file mode 100644 index 8f193ea..0000000 --- a/assets/default.5f5cce99c868ff445756.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see default.5f5cce99c868ff445756.js.LICENSE.txt */ -!function(){var e={"./src/js/cfg-loader/cfg-loader.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js"),n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");e.exports=class{constructor(e,t){this.reader=e,this.parser=t}async load(e){const t=[],n=[];return e.forEach(((e,s)=>{n.push(this.reader(e).then((e=>this.parser(e))).then((e=>{t[s]=e})))})),Promise.all(n).then((()=>Object.assign({},...t)))}}},"./src/js/cfg-loader/cfg-reader-fetch.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js"),e.exports=function(e){return fetch(e,{cache:"no-store"}).then((e=>e.ok?e.text():""))}},"./src/js/helpers-html/i18n.js":function(e,t,n){function s(){return IMAGINARY.i18n.getLang()}function o(e){return IMAGINARY.i18n.setLang(e).then((()=>{$("[data-i18n-text]").each(((e,t)=>{$(t).html(IMAGINARY.i18n.t($(t).data("i18n-text")))}))}))}n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.promise.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),e.exports={init:function(e,t){return IMAGINARY.i18n.init({queryStringVariable:"lang",translationsDirectory:"tr",defaultLanguage:"en"}).then((()=>{const t=Object.keys(e.languages);return Promise.all(t.map((e=>IMAGINARY.i18n.loadLang(e))))})).then((()=>o(t)))},getLanguage:s,setLanguage:o,refresh:function(){o(s())}}},"./src/js/helpers-html/object-store.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js"),n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.object.from-entries.js"),n("./node_modules/core-js/modules/es.array.reverse.js");e.exports=class{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;this.fixedObjects=[],this.userObjects=[],this.loadUserObjects(),e&&this.loadFixedObjects(e)}async loadFixedObjects(e){fetch(e,{cache:"no-store"}).then((e=>e.json())).then((e=>{this.fixedObjects=e.mazes}))}loadUserObjects(){try{const e=JSON.parse(localStorage.getItem("reinforcementLearning2.mazeStore.mazes"));e&&(this.userObjects=e)}catch(e){console.error("Unable to access local storage ".concat(e))}}saveLocal(){try{localStorage.setItem("reinforcementLearning2.mazeStore.mazes",JSON.stringify(this.userObjects))}catch(e){console.error("Unable to access local storage ".concat(e))}}getAllObjects(){return{...this.getAllUserObjects(),...this.getAllFixedObjects()}}getAllFixedObjects(){return Object.fromEntries(this.fixedObjects.map(((e,t)=>["F".concat(t),e])))}getAllUserObjects(){return Object.fromEntries(this.userObjects.map(((e,t)=>["L".concat(t),e])).reverse())}get(e){return"F"===e[0]?this.fixedObjects[e.substr(1)]:this.userObjects[e.substr(1)]}set(e,t){null===e||void 0===this.userObjects[e.substr(1)]?this.userObjects.push(t):this.userObjects[e.substr(1)]=t,this.saveLocal()}}},"./src/js/helpers-html/show-fatal-error.js":function(e){e.exports=function(e,t){$("
").addClass("fatal-error").append($("
").addClass("fatal-error-text").html(e)).append($("
").addClass("fatal-error-details").html(t.message)).appendTo("body"),$("html").addClass("with-fatal-error")}},"./src/js/helpers-pixi/pixi-helpers.js":function(e,t,n){"use strict";function s(e,t,n){const s=e.getBoundingClientRect();return[t*(s.width/e.width)+s.left,n*(s.height/e.height)+s.top]}n.r(t),n.d(t,{screenCoordinates:function(){return s}})},"./src/js/helpers/array-2d.js":function(e){class t{static create(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;const s=[];for(let o=0;o0&&Array.isArray(e[0])&&e[0].length>0&&e.every((t=>t.length===e[0].length))}static size(e){return[e[0].length,e.length]}static clone(e){return e.map((e=>Array.from(e)))}static copy(e,t){for(let n=0;n({value:e,sort:Math.random()}))).sort(((e,t)=>e.sort-t.sort)).map((e=>{let{value:t}=e;return t}))}}},"./src/js/input/keyboard-controller.js":function(e){e.exports=function(e){const t={ArrowLeft:()=>{e.go("w")},ArrowRight:()=>{e.go("e")},ArrowUp:()=>{e.go("n")},ArrowDown:()=>{e.go("s")}};window.addEventListener("keydown",(e=>{t[e.code]&&(t[e.code](),e.preventDefault())}))}},"./src/js/jquery-plugins/jquery.pointerclick.js":function(){!function(e){e.fn.pointerclick=function(){return this.each((function(){let t=null;e(document).on("pointerup",(n=>{n.pointerId===t&&(t=null,e(this).trigger("i.pointerup",n))})).on("pointercancel",(n=>{n.pointerId===t&&(t=null,e(this).trigger("i.pointerup",n))})),e(this).on("pointerdown",(n=>{n.preventDefault(),t=n.pointerId,n.delegateTarget.releasePointerCapture(n.pointerId),e(this).trigger("i.pointerdown",n)})).on("pointerup",(n=>{n.preventDefault(),n.pointerId===t&&(t=null,e(this).trigger("i.pointerup",n),e(this).trigger("i.pointerclick",n))}))}))}}(jQuery)},"./src/js/model/grid.js":function(e,t,n){n("./node_modules/core-js/modules/es.regexp.exec.js"),n("./node_modules/core-js/modules/es.string.replace.js"),n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/helpers/array-2d.js");class r{constructor(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.width=e,this.height=t,this.cells=n||o.create(e,t,0),this.events=new s}static fromJSON(e){const{width:t,height:n,cells:s}=e;return new r(t,n,s)}toJSON(){return{width:this.width,height:this.height,cells:o.clone(this.cells)}}copy(e){this.width=e.width,this.height=e.height,this.replace(e.cells)}get(e,t){return this.cells[t][e]}set(e,t,n){this.cells[t][e]=n,this.events.emit("update",[[e,t,n]])}offset(e,t){return t*this.width+e}replace(e){o.copy(e,this.cells),this.events.emit("update",this.allCells())}isValidCoords(e,t){return e>=0&&t>=0&&e{let[t,n]=e;return this.isValidCoords(t,n)})).map((e=>{let[t,n]=e;return[t,n,this.get(t,n)]}))}nearbyCells(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;const s=[];for(let o=e-n;oe-n;o-=1)s.push([o,t+n]);for(let o=t+n;o>t-n;o-=1)s.push([e-n,o]);return s.filter((e=>{let[t,n]=e;return this.isValidCoords(t,n)})).map((e=>{let[t,n]=e;return[t,n,this.get(t,n)]}))}stepDistance(e,t,n,s){return Math.abs(e-n)+Math.abs(t-s)}}e.exports=r},"./src/js/model/maze.js":function(e,t,n){n("./node_modules/core-js/modules/web.url.to-json.js"),n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/model/grid.js"),r=n("./src/js/helpers/array-2d.js");class i{constructor(e,t,n,r){this.map=new o(e,t,n),this.config=r,this.robot=null,this.items=[],this.lastItemId=0,this.startPosition=[0,t-1],this.events=new s}toJSON(){const{map:e}=this;return{map:e.toJSON(),items:this.items.map((e=>{let{type:t,x:n,y:s}=e;return{type:t,x:n,y:s}}))}}static fromJSON(e){const{map:t,items:n}=e,{width:s,height:o}=t,l=new i(s,o,r.clone(t.cells));return(n||[]).forEach((e=>{let{type:t,x:n,y:s}=e;l.placeItem(t,n,s)})),l}copy(e){this.map.copy(e.map),this.clearItems(),(e.items||[]).forEach((e=>{let{type:t,x:n,y:s}=e;this.placeItem(t,n,s)})),this.lastItemId=e.lastItemId}addRobot(e){if(this.robot)throw new Error("Robot already exists");this.robot=e,e.maze=this;const[t,n]=this.startPosition;e.x=t,e.y=n}placeItem(e,t,n){let s=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];this.removeItem(t,n),this.lastItemId+=1;const o={id:this.lastItemId,type:e,x:t,y:n,picked:!1,erasable:s};this.items.push(o),this.events.emit("itemPlaced",o)}getTileType(e,t){return this.config.tileTypes&&this.config.tileTypes[this.map.get(e,t)]&&this.config.tileTypes[this.map.get(e,t)].type}getItem(e,t){return this.items.find((n=>n.x===e&&n.y===t))}removeItem(e,t){const n=this.getItem(e,t);n&&(this.events.emit("itemRemoved",n),this.items=this.items.filter((e=>e.id!==n.id)))}pickItem(e,t){const n=this.getItem(e,t);return n&&!n.picked?(this.events.emit("itemPicked",n),n.picked=!0,n):null}clearItems(){this.items.forEach((e=>{this.events.emit("itemRemoved",e)})),this.items=[]}isWalkable(e,t){return this.config.tileTypes&&this.config.tileTypes[this.map.get(e,t)]&&this.config.tileTypes[this.map.get(e,t)].walkable}isExit(e,t){return this.config.tileTypes&&this.config.tileTypes[this.map.get(e,t)]&&this.config.tileTypes[this.map.get(e,t)].exit}reset(){this.items.forEach((e=>{e.picked=!1,this.events.emit("itemReset",e)})),this.events.emit("reset")}getItemReward(e){return this.config.items[e.type]&&this.config.items[e.type].reward||0}getPositionReward(e,t){return this.config.tileTypes[this.map.get(e,t)]&&this.config.tileTypes[this.map.get(e,t)].reward||0}}e.exports=i},"./src/js/model/qlearning-ai.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.object.from-entries.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/es.array.includes.js"),n("./node_modules/core-js/modules/es.array.sort.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/model/robot.js"),{shuffleArray:r}=n("./src/js/helpers/shuffle.js");e.exports=class{constructor(e){this.robot=e,this.q=this.initQ(),this.v=this.initV(),this.r=this.initR(),this.learningRate=1,this.discountFactor=1,this.exploreRate=.2,this.learning=!0,this.events=new s,this.robot.events.on("move",((e,t,n,s,o,r)=>{this.learning&&null!==e&&this.update(e,t,n,s,o,r)}))}initQ(){const{height:e,width:t}=this.robot.maze.map,n=new Array(e);for(let s=0;s[e,0])))}return n}initV(){const{height:e,width:t}=this.robot.maze.map,n=new Array(e);for(let s=0;s{let[t]=e;return n.includes(t)}));return s.length>0?r(s).sort(((e,t)=>{let[,n]=e,[,s]=t;return n-s})).pop()[0]:null}randomPolicy(){const e=this.robot.availableDirections();return e.length?e[Math.floor(Math.random()*e.length)]:null}epsilonGreedyPolicy(){return Math.random()>=this.exploreRate?this.greedyPolicy():this.randomPolicy()}step(){const e=this.epsilonGreedyPolicy();e?this.robot.go(e):this.robot.failMove()}maxQ(e,t){const n=this.robot.availableDirectionsAt(e,t);return n.length>0?Math.max(...Object.entries(this.q[t][e]).filter((e=>{let[t]=e;return n.includes(t)})).map((e=>{let[,t]=e;return t}))):0}minQ(e,t){const n=this.robot.availableDirectionsAt(e,t);return n.length>0?Math.min(...Object.entries(this.q[t][e]).filter((e=>{let[t]=e;return n.includes(t)})).map((e=>{let[,t]=e;return t}))):0}qUpperBound(){let e=0;for(let t=0;t!==this.q.length;t+=1)for(let n=0;n!==this.q[t].length;n+=1)e=Math.max(e,this.maxQ(n,t));return e}qLowerBound(){let e=0;for(let t=0;t!==this.q.length;t+=1)for(let n=0;n!==this.q[t].length;n+=1)e=Math.min(e,this.minQ(n,t));return e}update(e,t,n,s,o,r){this.q[n][t][e]+=this.learningRate*(r+this.discountFactor*this.maxQ(s,o)-this.q[n][t][e]),this.r[o][s]=r,this.v[n][t]=this.maxQ(t,n)+this.r[n][t],this.events.emit("update")}}},"./src/js/model/robot.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js");class o{constructor(){this.maze=null,this.x=0,this.y=0,this.score=0,this.canMove=!0,this.events=new s}setPosition(e,t){const n=this.x,s=this.y;this.x=e,this.y=t,this.onMoved(null,n,s,e,t)}canMoveTo(e,t){return this.canMove&&this.maze&&this.maze.map.isValidCoords(e,t)&&this.maze.isWalkable(e,t)&&1===this.maze.map.stepDistance(this.x,this.y,e,t)}canMoveFromTo(e,t,n,s){return this.maze.map.isValidCoords(n,s)&&this.maze.isWalkable(n,s)&&!this.maze.isExit(e,t)&&1===this.maze.map.stepDistance(e,t,n,s)}moveTo(e,t,n){if(this.canMoveTo(t,n)){const s=this.x,o=this.y;this.x=t,this.y=n,this.onMoved(e,s,o,t,n)}else this.onMoveFailed(e,this.x,this.y,t,n)}failMove(){this.onMoveFailed(null,this.x,this.y,null,null)}reset(){const[e,t]=this.maze.startPosition;this.resetScore(),this.setPosition(e,t),this.events.emit("reset")}onMoved(e,t,n,s,o){let r=0;r+=this.maze.getPositionReward(s,o);const i=this.maze.pickItem(s,o);i&&(r+=this.maze.getItemReward(i)),this.events.emit("move",e,t,n,s,o,r,this.maze.getTileType(s,o)),this.addScore(r),this.maze.isExit(s,o)&&this.onExit(s,o)}onMoveFailed(e,t,n,s,o){this.events.emit("moveFailed",e,t,n,s,o)}onExit(e,t){this.events.emit("exited",e,t),this.reset()}availableDirections(){return Object.keys(o.Directions).filter((e=>this.canMoveTo(this.x+o.Directions[e][0],this.y+o.Directions[e][1])))}availableDirectionsAt(e,t){return Object.keys(o.Directions).filter((n=>this.canMoveFromTo(e,t,e+o.Directions[n][0],t+o.Directions[n][1])))}go(e){const[t,n]=o.Directions[e];this.moveTo(e,this.x+t,this.y+n)}resetScore(){this.score=0}addScore(e){this.score+=e,this.events.emit("scoreChanged",e,this.score)}}o.Directions={n:[0,-1],s:[0,1],e:[1,0],w:[-1,0]},e.exports=o},"./src/js/view-html/ai-training-view.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/view-pixi/robot-view.js"),r=n("./src/js/view-html/hold-button.js");class i{constructor(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};this.ai=e,this.robotView=t,this.options={...i.defaultOptions,...n},this.running=!1,this.turboDown=!1,this.timer=0,this.robotIdle=!0,this.robotView.events.on("idle",(()=>{this.running||!this.options.useToggleFFButton&&this.turboDown?this.ai.step():this.robotIdle=!0})),this.events=new s,this.$element=$("
").addClass("ai-training-view"),this.$runButton=this.buildButton({id:"run",icon:"static/fa/play-solid.svg",title:"Run / Pause"}).on("i.pointerclick",(()=>{this.running?(this.$runButton.css({backgroundImage:'url("static/fa/play-solid.svg")'}),this.$runButton.removeClass("active"),this.running=!1):this.robotIdle&&(this.$runButton.css({backgroundImage:'url("static/fa/pause-solid.svg")'}),this.$runButton.addClass("active"),this.running=!0,this.robotIdle=!1,this.ai.step())})).appendTo(this.$element),this.$turboButton=this.buildButton({id:"turbo",icon:"static/fa/forward-solid.svg",title:"Hold to speed up",type:this.options.useToggleFFButton?"toggle":"hold"}).on("active",(()=>{this.$turboButton.addClass("active"),this.robotView.speed=o.Speed.TURBO,this.turboDown=!0,!this.options.useToggleFFButton&&this.robotIdle&&this.ai.step()})).on("inactive",(()=>{this.$turboButton.removeClass("active"),this.robotView.speed=o.Speed.DEFAULT,this.turboDown=!1})).appendTo(this.$element),this.$stepButton=this.buildButton({id:"step",icon:"static/fa/step-forward-solid.svg",title:"Step"}).on("i.pointerclick",(()=>{this.robotIdle&&(this.robotIdle=!1,this.ai.step())})).appendTo(this.$element),this.options.showViewPolicyButton&&(this.$viewPolicyButton=this.buildButton({id:"view-policy",icon:"static/icons/eye-regular.svg",title:"View Policy",type:this.options.useToggleViewPolicyButton?"toggle":"hold"}).on("active",(()=>{this.$viewPolicyButton.addClass("active"),this.events.emit("policy-show")})).on("inactive",(()=>{this.$viewPolicyButton.removeClass("active"),this.events.emit("policy-hide")})).appendTo(this.$element)),this.$explorationRateSlider=this.buildSlider({id:"exploration-rate",title:"Exploration rate",options:{min:0,max:1,step:.1},limitLabels:["Exploit","Explore"],initialValue:this.ai.exploreRate,changeCallback:e=>{this.ai.exploreRate=e}}).appendTo(this.$element),this.$learningRateSlider=this.buildSlider({id:"learning-rate",title:"Learning rate",options:{min:0,max:1,step:.1},initialValue:this.ai.learningRate,changeCallback:e=>{this.ai.learningRate=e}}).appendTo(this.$element),this.$discountFactorSlider=this.buildSlider({id:"discount-factor",title:"Discount factor",options:{min:0,max:1,step:.1},initialValue:this.ai.discountFactor,changeCallback:e=>{this.ai.discountFactor=e}}).appendTo(this.$element),this.$clearButton=r({id:"clear",title:"Clear",holdTime:2e3}).addClass(["ai-training-view-button","ai-training-view-button-clear"]).on("hold",(()=>{this.ai.clear(),this.events.emit("training-clear")})).appendTo(this.$element),this.$clearButton.find(".text").attr({"data-i18n-text":"ai-training-view-button-clear"})}buildButton(e){const t=$("").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:n,options:s,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),l=$('
').appendTo(i),a=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(n,": ")).append(c).appendTo(l),e.limitLabels){const[n,s]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(l),$("").addClass(["slider-limit","slider-limit-max"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(l)}const u=$('').addClass("form-control-range").attr(s).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(a);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./src/js/model/maze.js");e.exports=class{constructor(e,t,n){let s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(s?n.getAllUserObjects():n.getAllObjects()).map((e=>{let[t,n]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(n)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));s&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=s.fromJSON(e),n=$('').attr({width:t.map.width,height:t.map.height}),o=n[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,n,s]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[s].color||"#000000",o.fillRect(t,n,1,1)})),n}}},"./src/js/view-html/editor/maze-editor-palette.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js");class o{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new s,this.$element.addClass("maze-editor-palette"),this.$bar1=$('
').appendTo(this.$element),this.$bar2=$('
').appendTo(this.$element),this.$bar1.append(this.buildActionButtons()),this.$bar2.append(this.buildTileButtons(t))}buildTileButtons(e){return Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return $("
").addClass("item").append($("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:n.color,backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(t)))}))}buildToolButtons(){return o.Tools.map((e=>$("").attr({type:"button",title:e.title}).addClass(["editor-palette-button","editor-palette-button-tool","editor-palette-button-tool-".concat(e.id)]).css({backgroundImage:"url(".concat(e.icon,")")}).pointerclick().on("i.pointerclick",(t=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(t.target),this.activeButton.addClass("active"),this.events.emit("change",e.id)}))))}buildItemButtons(e){return Object.entries(e.items).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return $("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-item","editor-palette-button-item-".concat(t)]).css({backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.events.emit("change","item",t)}))}))}buildActionButtons(){return o.Actions.map((e=>$("").attr({type:"button",title:e.title}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-".concat(e.id)]).css({backgroundImage:"url(".concat(e.icon,")")}).pointerclick().on("i.pointerclick",(()=>{this.events.emit("action",e.id)}))))}}o.Tools=[{id:"start",title:"Set the starting point",icon:"static/fa/robot-solid-blue.svg"},{id:"erase",title:"Remove items",icon:"static/fa/times-solid.svg"}],o.Actions=[{id:"load",title:"Load maze",icon:"static/fa/folder-open-solid.svg"},{id:"save",title:"Save maze",icon:"static/fa/save-solid.svg"},{id:"import",title:"Import maze",icon:"static/fa/file-import-solid.svg"},{id:"export",title:"Export maze",icon:"static/fa/file-export-solid.svg"}],e.exports=o},"./src/js/view-html/editor/maze-editor.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/web.url.to-json.js");const s=n("./src/js/model/maze.js"),o=n("./src/js/view-pixi/maze-view.js"),r=n("./src/js/view-html/editor/modal-load.js"),i=n("./src/js/view-html/editor/modal-save.js"),l=n("./src/js/view-html/editor/modal-export.js"),a=n("./src/js/view-html/editor/modal-import.js"),c=n("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,n,u,d){var p=this;this.$element=e,this.maze=t,this.palette=n,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const n=this.maze.getItem(e,t);n&&n.erasable&&this.maze.removeItem(e,t)},tile:(e,t,n)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===n||this.placedTileIsFixed(t,n)||(this.maze.removeItem(t,n),this.maze.map.set(t,n,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,n,!1))},item:(e,t,n)=>{this.maze.isWalkable(t,n)&&this.maze.placeItem(e,t,n)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[n,s]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,n);o<=Math.max(e,n);o+=1)for(let e=Math.min(t,s);e<=Math.max(t,s);e+=1)this.toolHandler(o,e)}else this.toolHandler(n,s);h=[n,s]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(s.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new a).show().then((e=>{e&&this.maze.copy(s.fromJSON(e))}))},export:()=>{new l(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const n=this.maze.map.get(e,t);return this.config.tileTypes[n].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let n=null,s=null;function o(){null!==s&&(clearTimeout(s),s=null),t.removeClass("held"),n=null}return $(document).on("pointerup",(e=>{e.pointerId===n&&o()})).on("pointercancel",(e=>{e.pointerId===n&&o()})),t.on("pointerdown",(o=>{null===n&&(o.preventDefault(),n=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==s&&(clearTimeout(s),s=null),t.addClass("held"),s=setTimeout((()=>{s=null,n=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/modal.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\n\nclass MazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass('maze-editor-palette');\n this.$bar1 = $('
')\n .appendTo(this.$element);\n this.$bar2 = $('
')\n .appendTo(this.$element);\n\n this.$bar1.append(this.buildActionButtons());\n\n this.$bar2.append(this.buildTileButtons(config));\n // this.$bar2.append($('
'));\n // this.$bar2.append(this.buildToolButtons(config));\n // this.$bar2.append(this.buildItemButtons(config));\n }\n\n buildTileButtons(config) {\n return Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => $('
')\n .addClass('item')\n .append(\n $('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n })\n )\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${id}`)));\n }\n\n // noinspection JSUnusedGlobalSymbols\n buildToolButtons() {\n return MazeEditorPalette.Tools.map((tool) => $('')\n .attr({\n type: 'button',\n title: tool.title,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tool',\n `editor-palette-button-tool-${tool.id}`,\n ])\n .css({\n backgroundImage: `url(${tool.icon})`,\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.events.emit('change', tool.id);\n }));\n }\n\n // noinspection JSUnusedGlobalSymbols\n buildItemButtons(config) {\n return Object.entries(config.items)\n .filter(([, props]) => props.inPalette !== false)\n .map(([id, props]) => $('')\n .attr({\n type: 'button',\n title: props.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-item',\n `editor-palette-button-item-${id}`,\n ])\n .css({\n backgroundImage: props.editorIcon ? `url(${props.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.events.emit('change', 'item', id);\n }));\n }\n\n buildActionButtons() {\n return MazeEditorPalette.Actions.map((action) => $('')\n .attr({\n type: 'button',\n title: action.title,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n `editor-palette-button-action-${action.id}`,\n ])\n .css({\n backgroundImage: `url(${action.icon})`,\n })\n .pointerclick()\n .on('i.pointerclick', () => {\n this.events.emit('action', action.id);\n }));\n }\n}\n\nMazeEditorPalette.Tools = [\n {\n id: 'start',\n title: 'Set the starting point',\n icon: 'static/fa/robot-solid-blue.svg',\n },\n {\n id: 'erase',\n title: 'Remove items',\n icon: 'static/fa/times-solid.svg',\n },\n];\n\nMazeEditorPalette.Actions = [\n // {\n // id: 'reset',\n // title: 'Reset',\n // icon: 'static/fa/sync-solid.svg',\n // },\n {\n id: 'load',\n title: 'Load maze',\n icon: 'static/fa/folder-open-solid.svg',\n },\n {\n id: 'save',\n title: 'Save maze',\n icon: 'static/fa/save-solid.svg',\n },\n {\n id: 'import',\n title: 'Import maze',\n icon: 'static/fa/file-import-solid.svg',\n },\n {\n id: 'export',\n title: 'Export maze',\n icon: 'static/fa/file-export-solid.svg',\n },\n];\n\nmodule.exports = MazeEditorPalette;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('
").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:n,options:s,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),l=$('
').appendTo(i),a=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(n,": ")).append(c).appendTo(l),e.limitLabels){const[n,s]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(l),$("").addClass(["slider-limit","slider-limit-max"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(l)}const u=$('').addClass("form-control-range").attr(s).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(a);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./src/js/model/maze.js");e.exports=class{constructor(e,t,n){let s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(s?n.getAllUserObjects():n.getAllObjects()).map((e=>{let[t,n]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(n)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));s&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=s.fromJSON(e),n=$('').attr({width:t.map.width,height:t.map.height}),o=n[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,n,s]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[s].color||"#000000",o.fillRect(t,n,1,1)})),n}}},"./src/js/view-html/editor/maze-editor-palette.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js");class o{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new s,this.$element.addClass("maze-editor-palette"),this.$bar1=$('
').appendTo(this.$element),this.$bar2=$('
').appendTo(this.$element),this.$bar1.append(this.buildActionButtons()),this.$bar2.append(this.buildTileButtons(t))}buildTileButtons(e){return Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return $("
").addClass("item").append($("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:n.color,backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(t)))}))}buildToolButtons(){return o.Tools.map((e=>$("").attr({type:"button",title:e.title}).addClass(["editor-palette-button","editor-palette-button-tool","editor-palette-button-tool-".concat(e.id)]).css({backgroundImage:"url(".concat(e.icon,")")}).pointerclick().on("i.pointerclick",(t=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(t.target),this.activeButton.addClass("active"),this.events.emit("change",e.id)}))))}buildItemButtons(e){return Object.entries(e.items).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return $("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-item","editor-palette-button-item-".concat(t)]).css({backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.events.emit("change","item",t)}))}))}buildActionButtons(){return o.Actions.map((e=>$("").attr({type:"button",title:e.title}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-".concat(e.id)]).css({backgroundImage:"url(".concat(e.icon,")")}).pointerclick().on("i.pointerclick",(()=>{this.events.emit("action",e.id)}))))}}o.Tools=[{id:"start",title:"Set the starting point",icon:"static/fa/robot-solid-blue.svg"},{id:"erase",title:"Remove items",icon:"static/fa/times-solid.svg"}],o.Actions=[{id:"load",title:"Load maze",icon:"static/fa/folder-open-solid.svg"},{id:"save",title:"Save maze",icon:"static/fa/save-solid.svg"},{id:"import",title:"Import maze",icon:"static/fa/file-import-solid.svg"},{id:"export",title:"Export maze",icon:"static/fa/file-export-solid.svg"}],e.exports=o},"./src/js/view-html/editor/maze-editor.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/web.url.to-json.js");const s=n("./src/js/model/maze.js"),o=n("./src/js/view-pixi/maze-view.js"),r=n("./src/js/view-html/editor/modal-load.js"),i=n("./src/js/view-html/editor/modal-save.js"),l=n("./src/js/view-html/editor/modal-export.js"),a=n("./src/js/view-html/editor/modal-import.js"),c=n("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,n,u,d){var p=this;this.$element=e,this.maze=t,this.palette=n,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const n=this.maze.getItem(e,t);n&&n.erasable&&this.maze.removeItem(e,t)},tile:(e,t,n)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===n||this.placedTileIsFixed(t,n)||(this.maze.removeItem(t,n),this.maze.map.set(t,n,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,n,!1))},item:(e,t,n)=>{this.maze.isWalkable(t,n)&&this.maze.placeItem(e,t,n)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[n,s]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,n);o<=Math.max(e,n);o+=1)for(let e=Math.min(t,s);e<=Math.max(t,s);e+=1)this.toolHandler(o,e)}else this.toolHandler(n,s);h=[n,s]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(s.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new a).show().then((e=>{e&&this.maze.copy(s.fromJSON(e))}))},export:()=>{new l(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const n=this.maze.map.get(e,t);return this.config.tileTypes[n].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let n=null,s=null;function o(){null!==s&&(clearTimeout(s),s=null),t.removeClass("held"),n=null}return $(document).on("pointerup",(e=>{e.pointerId===n&&o()})).on("pointercancel",(e=>{e.pointerId===n&&o()})),t.on("pointerdown",(o=>{null===n&&(o.preventDefault(),n=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==s&&(clearTimeout(s),s=null),t.addClass("held"),s=setTimeout((()=>{s=null,n=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/modal.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('
')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\n\nclass MazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass('maze-editor-palette');\n this.$bar1 = $('
')\n .appendTo(this.$element);\n this.$bar2 = $('
')\n .appendTo(this.$element);\n\n this.$bar1.append(this.buildActionButtons());\n\n this.$bar2.append(this.buildTileButtons(config));\n // this.$bar2.append($('
'));\n // this.$bar2.append(this.buildToolButtons(config));\n // this.$bar2.append(this.buildItemButtons(config));\n }\n\n buildTileButtons(config) {\n return Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => $('
')\n .addClass('item')\n .append(\n $('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n })\n )\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${id}`)));\n }\n\n // noinspection JSUnusedGlobalSymbols\n buildToolButtons() {\n return MazeEditorPalette.Tools.map((tool) => $('')\n .attr({\n type: 'button',\n title: tool.title,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tool',\n `editor-palette-button-tool-${tool.id}`,\n ])\n .css({\n backgroundImage: `url(${tool.icon})`,\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.events.emit('change', tool.id);\n }));\n }\n\n // noinspection JSUnusedGlobalSymbols\n buildItemButtons(config) {\n return Object.entries(config.items)\n .filter(([, props]) => props.inPalette !== false)\n .map(([id, props]) => $('')\n .attr({\n type: 'button',\n title: props.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-item',\n `editor-palette-button-item-${id}`,\n ])\n .css({\n backgroundImage: props.editorIcon ? `url(${props.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.events.emit('change', 'item', id);\n }));\n }\n\n buildActionButtons() {\n return MazeEditorPalette.Actions.map((action) => $('')\n .attr({\n type: 'button',\n title: action.title,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n `editor-palette-button-action-${action.id}`,\n ])\n .css({\n backgroundImage: `url(${action.icon})`,\n })\n .pointerclick()\n .on('i.pointerclick', () => {\n this.events.emit('action', action.id);\n }));\n }\n}\n\nMazeEditorPalette.Tools = [\n {\n id: 'start',\n title: 'Set the starting point',\n icon: 'static/fa/robot-solid-blue.svg',\n },\n {\n id: 'erase',\n title: 'Remove items',\n icon: 'static/fa/times-solid.svg',\n },\n];\n\nMazeEditorPalette.Actions = [\n // {\n // id: 'reset',\n // title: 'Reset',\n // icon: 'static/fa/sync-solid.svg',\n // },\n {\n id: 'load',\n title: 'Load maze',\n icon: 'static/fa/folder-open-solid.svg',\n },\n {\n id: 'save',\n title: 'Save maze',\n icon: 'static/fa/save-solid.svg',\n },\n {\n id: 'import',\n title: 'Import maze',\n icon: 'static/fa/file-import-solid.svg',\n },\n {\n id: 'export',\n title: 'Export maze',\n icon: 'static/fa/file-export-solid.svg',\n },\n];\n\nmodule.exports = MazeEditorPalette;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('
").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:n,options:s,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),a=$('
').appendTo(i),l=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(n,": ")).append(c).appendTo(a),e.limitLabels){const[n,s]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(a),$("").addClass(["slider-limit","slider-limit-max"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(a)}const u=$('').addClass("form-control-range").attr(s).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(l);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./src/js/model/maze.js");e.exports=class{constructor(e,t,n){let s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(s?n.getAllUserObjects():n.getAllObjects()).map((e=>{let[t,n]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(n)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));s&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=s.fromJSON(e),n=$('').attr({width:t.map.width,height:t.map.height}),o=n[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,n,s]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[s].color||"#000000",o.fillRect(t,n,1,1)})),n}}},"./src/js/view-html/editor/maze-editor.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/web.url.to-json.js");const s=n("./src/js/model/maze.js"),o=n("./src/js/view-pixi/maze-view.js"),r=n("./src/js/view-html/editor/modal-load.js"),i=n("./src/js/view-html/editor/modal-save.js"),a=n("./src/js/view-html/editor/modal-export.js"),l=n("./src/js/view-html/editor/modal-import.js"),c=n("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,n,u,d){var p=this;this.$element=e,this.maze=t,this.palette=n,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const n=this.maze.getItem(e,t);n&&n.erasable&&this.maze.removeItem(e,t)},tile:(e,t,n)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===n||this.placedTileIsFixed(t,n)||(this.maze.removeItem(t,n),this.maze.map.set(t,n,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,n,!1))},item:(e,t,n)=>{this.maze.isWalkable(t,n)&&this.maze.placeItem(e,t,n)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[n,s]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,n);o<=Math.max(e,n);o+=1)for(let e=Math.min(t,s);e<=Math.max(t,s);e+=1)this.toolHandler(o,e)}else this.toolHandler(n,s);h=[n,s]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(s.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new l).show().then((e=>{e&&this.maze.copy(s.fromJSON(e))}))},export:()=>{new a(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const n=this.maze.map.get(e,t);return this.config.tileTypes[n].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/exhibit-maze-editor-palette.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.object.from-entries.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/view-html/hold-button.js");e.exports=class{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new s,this.$element.addClass(["maze-editor-palette","exhibit-maze-editor-palette"]),this.$bar1=$('
').appendTo(this.$element),this.tileButtons=this.buildTileButtons(t),this.$bar1.append(Object.values(this.tileButtons)),this.resetMapButton=o({holdTime:2e3}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-reset-map"]).on("hold",(()=>{this.events.emit("action","reset-map")})).appendTo(this.$element).find(".text").html("Reset map").attr({title:"Reset map","data-i18n-text":"editor-palette-button-action-reset-map"})}buildTileButtons(e){return Object.fromEntries(Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return[t,$("
").addClass(["item"]).attr("data-tile-id",t).append($("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:n.color,backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(n.type)))]})))}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let n=null,s=null;function o(){null!==s&&(clearTimeout(s),s=null),t.removeClass("held"),n=null}return $(document).on("pointerup",(e=>{e.pointerId===n&&o()})).on("pointercancel",(e=>{e.pointerId===n&&o()})),t.on("pointerdown",(o=>{null===n&&(o.preventDefault(),n=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==s&&(clearTimeout(s),s=null),t.addClass("held"),s=setTimeout((()=>{s=null,n=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/modal.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('
')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\nconst createHoldButton = require('./hold-button');\n\nclass ExhibitMazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass(['maze-editor-palette', 'exhibit-maze-editor-palette']);\n this.$bar1 = $('
')\n .appendTo(this.$element);\n\n this.tileButtons = this.buildTileButtons(config);\n this.$bar1.append(Object.values(this.tileButtons));\n\n this.resetMapButton = createHoldButton({\n holdTime: 2000,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n 'editor-palette-button-action-reset-map',\n ])\n .on('hold', () => {\n this.events.emit('action', 'reset-map');\n })\n .appendTo(this.$element)\n .find('.text')\n .html('Reset map')\n .attr({\n title: 'Reset map',\n 'data-i18n-text': 'editor-palette-button-action-reset-map',\n });\n }\n\n buildTileButtons(config) {\n return Object.fromEntries(\n Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => [id, $('
')\n .addClass(['item'])\n .attr('data-tile-id', id)\n .append($('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n }))\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${typeCfg.type}`))])\n );\n }\n}\n\nmodule.exports = ExhibitMazeEditorPalette;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('
").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:n,options:s,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),a=$('
').appendTo(i),l=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(n,": ")).append(c).appendTo(a),e.limitLabels){const[n,s]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(a),$("").addClass(["slider-limit","slider-limit-max"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(a)}const u=$('').addClass("form-control-range").attr(s).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(l);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./src/js/model/maze.js");e.exports=class{constructor(e,t,n){let s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(s?n.getAllUserObjects():n.getAllObjects()).map((e=>{let[t,n]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(n)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));s&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=s.fromJSON(e),n=$('').attr({width:t.map.width,height:t.map.height}),o=n[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,n,s]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[s].color||"#000000",o.fillRect(t,n,1,1)})),n}}},"./src/js/view-html/editor/maze-editor.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/web.url.to-json.js");const s=n("./src/js/model/maze.js"),o=n("./src/js/view-pixi/maze-view.js"),r=n("./src/js/view-html/editor/modal-load.js"),i=n("./src/js/view-html/editor/modal-save.js"),a=n("./src/js/view-html/editor/modal-export.js"),l=n("./src/js/view-html/editor/modal-import.js"),c=n("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,n,u,d){var p=this;this.$element=e,this.maze=t,this.palette=n,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const n=this.maze.getItem(e,t);n&&n.erasable&&this.maze.removeItem(e,t)},tile:(e,t,n)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===n||this.placedTileIsFixed(t,n)||(this.maze.removeItem(t,n),this.maze.map.set(t,n,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,n,!1))},item:(e,t,n)=>{this.maze.isWalkable(t,n)&&this.maze.placeItem(e,t,n)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[n,s]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,n);o<=Math.max(e,n);o+=1)for(let e=Math.min(t,s);e<=Math.max(t,s);e+=1)this.toolHandler(o,e)}else this.toolHandler(n,s);h=[n,s]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(s.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new l).show().then((e=>{e&&this.maze.copy(s.fromJSON(e))}))},export:()=>{new a(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const n=this.maze.map.get(e,t);return this.config.tileTypes[n].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/exhibit-maze-editor-palette.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.object.from-entries.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/view-html/hold-button.js");e.exports=class{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new s,this.$element.addClass(["maze-editor-palette","exhibit-maze-editor-palette"]),this.$bar1=$('
').appendTo(this.$element),this.tileButtons=this.buildTileButtons(t),this.$bar1.append(Object.values(this.tileButtons)),this.resetMapButton=o({holdTime:2e3}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-reset-map"]).on("hold",(()=>{this.events.emit("action","reset-map")})).appendTo(this.$element).find(".text").html("Reset map").attr({title:"Reset map","data-i18n-text":"editor-palette-button-action-reset-map"})}buildTileButtons(e){return Object.fromEntries(Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return[t,$("
").addClass(["item"]).attr("data-tile-id",t).append($("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:n.color,backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(n.type)))]})))}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let n=null,s=null;function o(){null!==s&&(clearTimeout(s),s=null),t.removeClass("held"),n=null}return $(document).on("pointerup",(e=>{e.pointerId===n&&o()})).on("pointercancel",(e=>{e.pointerId===n&&o()})),t.on("pointerdown",(o=>{null===n&&(o.preventDefault(),n=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==s&&(clearTimeout(s),s=null),t.addClass("held"),s=setTimeout((()=>{s=null,n=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/modal.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('
')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\nconst createHoldButton = require('./hold-button');\n\nclass ExhibitMazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass(['maze-editor-palette', 'exhibit-maze-editor-palette']);\n this.$bar1 = $('
')\n .appendTo(this.$element);\n\n this.tileButtons = this.buildTileButtons(config);\n this.$bar1.append(Object.values(this.tileButtons));\n\n this.resetMapButton = createHoldButton({\n holdTime: 2000,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n 'editor-palette-button-action-reset-map',\n ])\n .on('hold', () => {\n this.events.emit('action', 'reset-map');\n })\n .appendTo(this.$element)\n .find('.text')\n .html('Reset map')\n .attr({\n title: 'Reset map',\n 'data-i18n-text': 'editor-palette-button-action-reset-map',\n });\n }\n\n buildTileButtons(config) {\n return Object.fromEntries(\n Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => [id, $('
')\n .addClass(['item'])\n .attr('data-tile-id', id)\n .append($('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n }))\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${typeCfg.type}`))])\n );\n }\n}\n\nmodule.exports = ExhibitMazeEditorPalette;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('
").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:n,options:s,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),a=$('
').appendTo(i),l=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(n,": ")).append(c).appendTo(a),e.limitLabels){const[n,s]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(a),$("").addClass(["slider-limit","slider-limit-max"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(a)}const u=$('').addClass("form-control-range").attr(s).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(l);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./src/js/model/maze.js");e.exports=class{constructor(e,t,n){let s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(s?n.getAllUserObjects():n.getAllObjects()).map((e=>{let[t,n]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(n)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));s&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=s.fromJSON(e),n=$('').attr({width:t.map.width,height:t.map.height}),o=n[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,n,s]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[s].color||"#000000",o.fillRect(t,n,1,1)})),n}}},"./src/js/view-html/editor/maze-editor.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js"),n("./node_modules/core-js/modules/web.url.to-json.js");const s=n("./src/js/model/maze.js"),o=n("./src/js/view-pixi/maze-view.js"),r=n("./src/js/view-html/editor/modal-load.js"),i=n("./src/js/view-html/editor/modal-save.js"),a=n("./src/js/view-html/editor/modal-export.js"),l=n("./src/js/view-html/editor/modal-import.js"),c=n("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,n,u,d){var p=this;this.$element=e,this.maze=t,this.palette=n,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const n=this.maze.getItem(e,t);n&&n.erasable&&this.maze.removeItem(e,t)},tile:(e,t,n)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===n||this.placedTileIsFixed(t,n)||(this.maze.removeItem(t,n),this.maze.map.set(t,n,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,n,!1))},item:(e,t,n)=>{this.maze.isWalkable(t,n)&&this.maze.placeItem(e,t,n)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[n,s]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,n);o<=Math.max(e,n);o+=1)for(let e=Math.min(t,s);e<=Math.max(t,s);e+=1)this.toolHandler(o,e)}else this.toolHandler(n,s);h=[n,s]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(s.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new l).show().then((e=>{e&&this.maze.copy(s.fromJSON(e))}))},export:()=>{new a(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const n=this.maze.map.get(e,t);return this.config.tileTypes[n].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,n){const s=n("./src/js/view-html/modal.js");e.exports=class extends s{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,n){const s=n("./src/js/view-html/modal.js"),o=n("./src/js/view-html/editor/maze-browser.js");e.exports=class extends s{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/exhibit-maze-editor-palette.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/es.object.from-entries.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");const s=n("./node_modules/events/events.js"),o=n("./src/js/view-html/hold-button.js");e.exports=class{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new s,this.$element.addClass(["maze-editor-palette","exhibit-maze-editor-palette"]),this.$bar1=$('
').appendTo(this.$element),this.tileButtons=this.buildTileButtons(t),this.$bar1.append(Object.values(this.tileButtons)),this.resetMapButton=o({holdTime:2e3}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-reset-map"]).on("hold",(()=>{this.events.emit("action","reset-map")})).appendTo(this.$element).find(".text").html("Reset map").attr({title:"Reset map","data-i18n-text":"editor-palette-button-action-reset-map"})}buildTileButtons(e){return Object.fromEntries(Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,n]=e;return[t,$("
").addClass(["item"]).attr("data-tile-id",t).append($("").attr({type:"button",title:n.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:n.color,backgroundImage:n.editorIcon?"url(".concat(n.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(n.type)))]})))}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let n=null,s=null;function o(){null!==s&&(clearTimeout(s),s=null),t.removeClass("held"),n=null}return $(document).on("pointerup",(e=>{e.pointerId===n&&o()})).on("pointercancel",(e=>{e.pointerId===n&&o()})),t.on("pointerdown",(o=>{null===n&&(o.preventDefault(),n=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==s&&(clearTimeout(s),s=null),t.addClass("held"),s=setTimeout((()=>{s=null,n=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/lang-switcher.js":function(e,t,n){n("./node_modules/core-js/modules/es.array.iterator.js"),n("./node_modules/core-js/modules/web.dom-collections.iterator.js");e.exports=class{constructor(e,t,n){this.menuVisible=!1,this.container=e,this.config=t,this.langChangeCallback=n,this.menuItems={},this.render()}render(){this.element=document.createElement("div"),this.element.classList.add("lang-switcher"),this.trigger=document.createElement("button"),this.trigger.setAttribute("type","button"),this.trigger.classList.add("lang-switcher-trigger"),this.element.appendChild(this.trigger);const e=document.createElement("div");e.classList.add("lang-switcher-menu-mask"),this.element.appendChild(e),this.menu=document.createElement("ul"),this.menu.classList.add("lang-switcher-menu"),e.appendChild(this.menu),Object.entries(this.config.languages).forEach((e=>{let[t,n]=e;const s=document.createElement("li"),o=document.createElement("button");o.setAttribute("type","button"),o.innerText=n,o.addEventListener("pointerdown",(e=>{this.langChangeCallback(t),e.preventDefault()})),s.appendChild(o),this.menu.appendChild(s),this.menuItems[t]=s})),this.container.appendChild(this.element),this.menu.style.bottom="".concat(-1*this.menu.clientHeight-10,"px"),window.document.addEventListener("pointerdown",(()=>{this.menuVisible&&this.hideMenu()})),this.trigger.addEventListener("pointerdown",(e=>{this.menuVisible||(this.showMenu(),e.stopPropagation())}))}showMenu(){this.menuVisible=!0,this.menu.classList.add("visible")}hideMenu(){this.menuVisible=!1,this.menu.classList.remove("visible")}setActiveLanguage(e){Object.entries(this.menuItems).forEach((t=>{let[n,s]=t;n===e?s.classList.add("active"):s.classList.remove("active")}))}}},"./src/js/view-html/modal.js":function(e,t,n){n("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('
')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\nconst createHoldButton = require('./hold-button');\n\nclass ExhibitMazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass(['maze-editor-palette', 'exhibit-maze-editor-palette']);\n this.$bar1 = $('
')\n .appendTo(this.$element);\n\n this.tileButtons = this.buildTileButtons(config);\n this.$bar1.append(Object.values(this.tileButtons));\n\n this.resetMapButton = createHoldButton({\n holdTime: 2000,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n 'editor-palette-button-action-reset-map',\n ])\n .on('hold', () => {\n this.events.emit('action', 'reset-map');\n })\n .appendTo(this.$element)\n .find('.text')\n .html('Reset map')\n .attr({\n title: 'Reset map',\n 'data-i18n-text': 'editor-palette-button-action-reset-map',\n });\n }\n\n buildTileButtons(config) {\n return Object.fromEntries(\n Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => [id, $('
')\n .addClass(['item'])\n .attr('data-tile-id', id)\n .append($('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n }))\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${typeCfg.type}`))])\n );\n }\n}\n\nmodule.exports = ExhibitMazeEditorPalette;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class LangSwitcher {\n constructor(container, config, langChangeCallback) {\n this.menuVisible = false;\n this.container = container;\n this.config = config;\n this.langChangeCallback = langChangeCallback;\n this.menuItems = {};\n\n this.render();\n }\n\n render() {\n this.element = document.createElement('div');\n this.element.classList.add('lang-switcher');\n\n this.trigger = document.createElement('button');\n this.trigger.setAttribute('type', 'button');\n this.trigger.classList.add('lang-switcher-trigger');\n this.element.appendChild(this.trigger);\n\n const mask = document.createElement('div');\n mask.classList.add('lang-switcher-menu-mask');\n this.element.appendChild(mask);\n\n this.menu = document.createElement('ul');\n this.menu.classList.add('lang-switcher-menu');\n mask.appendChild(this.menu);\n\n Object.entries(this.config.languages).forEach(([code, name]) => {\n const item = document.createElement(('li'));\n const link = document.createElement('button');\n link.setAttribute('type', 'button');\n // noinspection JSValidateTypes\n link.innerText = name;\n link.addEventListener('pointerdown', (ev) => {\n this.langChangeCallback(code);\n ev.preventDefault();\n });\n item.appendChild(link);\n this.menu.appendChild(item);\n this.menuItems[code] = item;\n });\n\n this.container.appendChild(this.element);\n\n this.menu.style.bottom = `${this.menu.clientHeight * -1 - 10}px`;\n\n window.document.addEventListener('pointerdown', () => {\n if (this.menuVisible) {\n this.hideMenu();\n }\n });\n this.trigger.addEventListener('pointerdown', (ev) => {\n if (!this.menuVisible) {\n this.showMenu();\n ev.stopPropagation();\n }\n });\n }\n\n showMenu() {\n this.menuVisible = true;\n this.menu.classList.add('visible');\n }\n\n hideMenu() {\n this.menuVisible = false;\n this.menu.classList.remove('visible');\n }\n\n setActiveLanguage(code) {\n Object.entries(this.menuItems).forEach(([langCode, item]) => {\n if (langCode === code) {\n item.classList.add('active');\n } else {\n item.classList.remove('active');\n }\n });\n }\n}\n\nmodule.exports = LangSwitcher;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('
").attr({type:"button",title:e.title,"data-i18n-text":"ai-training-view-button-".concat(e.id)}).addClass(["btn","ai-training-view-button","ai-training-view-button-".concat(e.id)]).html(e.icon?" ":e.title||"").pointerclick();return e.icon&&(t.css({backgroundImage:"url(".concat(e.icon,")")}),t.addClass("round")),"toggle"===e.type?t.on("i.pointerdown",(()=>{t.toggleClass("active"),t.hasClass("active")?t.trigger("active"):t.trigger("inactive")})):(t.on("i.pointerdown",(()=>{t.addClass("active"),t.trigger("active")})),t.on("i.pointerup",(()=>{t.removeClass("active"),t.trigger("inactive")}))),t}buildSlider(e){const{id:t,title:s,options:n,initialValue:o,changeCallback:r}=e,i=$("
").addClass(["slider","ai-training-view-slider","ai-training-view-slider-".concat(t)]),a=$('
').appendTo(i),l=$('
').appendTo(i),c=$("").text(o);if($("").html("".concat(s,": ")).append(c).appendTo(a),e.limitLabels){const[s,n]=e.limitLabels;$("").addClass(["slider-limit","slider-limit-min"]).text(s).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-min")).appendTo(a),$("").addClass(["slider-limit","slider-limit-max"]).text(n).attr("data-i18n-text","ai-training-view-slider-".concat(t,"-limit-max")).appendTo(a)}const u=$('').addClass("form-control-range").attr(n).on("change",(()=>{r(Number(u.val())),c.text(Number(u.val()))})).val(o).appendTo(l);return i}}i.defaultOptions={showViewPolicyButton:!0,useToggleViewPolicyButton:!1,useToggleFFButton:!1},e.exports=i},"./src/js/view-html/editor/maze-browser.js":function(e,t,s){s("./node_modules/core-js/modules/es.array.iterator.js"),s("./node_modules/core-js/modules/web.dom-collections.iterator.js");const n=s("./src/js/model/maze.js");e.exports=class{constructor(e,t,s){let n=arguments.length>3&&void 0!==arguments[3]&&arguments[3];this.$element=e,this.config=t,this.$selectedButton=null,this.selectedData=null,this.$element.addClass("maze-browser");const o=e=>{this.$selectedButton&&this.$selectedButton.removeClass("selected"),this.$selectedButton=$(e),this.$selectedButton.addClass("selected")},r=Object.entries(n?s.getAllUserObjects():s.getAllObjects()).map((e=>{let[t,s]=e;return $("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item").append(this.createPreviewImage(s)).pointerclick().on("i.pointerclick",(e=>{o(e.currentTarget),this.selectedData=t})))}));n&&r.unshift($("
").addClass(["col-6","col-md-2","mb-3"]).append($("").addClass("maze-browser-item-new").on("click",(e=>{o(e.currentTarget),this.selectedData="new"})))),this.$element.append($('
').append(r))}createPreviewImage(e){const t=n.fromJSON(e),s=$('').attr({width:t.map.width,height:t.map.height}),o=s[0].getContext("2d");return t.map.allCells().forEach((e=>{let[t,s,n]=e;o.fillStyle=this.config.tileTypes&&this.config.tileTypes[n].color||"#000000",o.fillRect(t,s,1,1)})),s}}},"./src/js/view-html/editor/maze-editor.js":function(e,t,s){s("./node_modules/core-js/modules/es.array.iterator.js"),s("./node_modules/core-js/modules/web.dom-collections.iterator.js"),s("./node_modules/core-js/modules/web.url.to-json.js");const n=s("./src/js/model/maze.js"),o=s("./src/js/view-pixi/maze-view.js"),r=s("./src/js/view-html/editor/modal-load.js"),i=s("./src/js/view-html/editor/modal-save.js"),a=s("./src/js/view-html/editor/modal-export.js"),l=s("./src/js/view-html/editor/modal-import.js"),c=s("./src/js/helpers-html/object-store.js");e.exports=class{constructor(e,t,s,u,d){var p=this;this.$element=e,this.maze=t,this.palette=s,this.config=u,this.mazeView=new o(t,u,d,!0),this.displayObject=this.mazeView.displayObject;const m={start:(e,t)=>{this.maze.robots.length>0&&this.maze.robots[0].setPosition(e,t)},erase:(e,t)=>{const s=this.maze.getItem(e,t);s&&s.erasable&&this.maze.removeItem(e,t)},tile:(e,t,s)=>{this.maze.startPosition[0]===t&&this.maze.startPosition[1]===s||this.placedTileIsFixed(t,s)||(this.maze.removeItem(t,s),this.maze.map.set(t,s,e),void 0!==this.config.tileTypes[e].item&&this.maze.placeItem(this.config.tileTypes[e].item,t,s,!1))},item:(e,t,s)=>{this.maze.isWalkable(t,s)&&this.maze.placeItem(e,t,s)}};this.toolHandler=null,this.palette.events.on("change",(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;p.toolHandler=null!==t?m[e].bind(p,t):m[e].bind(p)})),this.palette.events.on("action",(e=>{this.actionHandlers[e]&&this.actionHandlers[e]()}));let h=null;this.mazeView.events.on("action",((e,t)=>{let[s,n]=e;if(null!==this.toolHandler){if(h&&t.shiftKey){const[e,t]=h;for(let o=Math.min(e,s);o<=Math.max(e,s);o+=1)for(let e=Math.min(t,n);e<=Math.max(t,n);e+=1)this.toolHandler(o,e)}else this.toolHandler(s,n);h=[s,n]}})),this.objectStore=new c,this.actionHandlers={load:()=>{new r(this.config,this.objectStore).show().then((e=>{const t=e&&this.objectStore.get(e);t&&this.maze.copy(n.fromJSON(t))}))},save:()=>{new i(this.config,this.objectStore).show().then((e=>{e&&this.objectStore.set("new"===e?null:e,this.maze.toJSON())}))},import:()=>{(new l).show().then((e=>{e&&this.maze.copy(n.fromJSON(e))}))},export:()=>{new a(JSON.stringify(this.maze)).show()},reset:()=>{this.maze.reset()}}}placedTileIsFixed(e,t){const s=this.maze.map.get(e,t);return this.config.tileTypes[s].fixed}animate(e){this.mazeView.animate(e)}getRobotView(){return this.mazeView.robotView}}},"./src/js/view-html/editor/modal-export.js":function(e,t,s){const n=s("./src/js/view-html/modal.js");e.exports=class extends n{constructor(e){super({title:"Export maze"}),this.$dataContainer=$('').attr({rows:10}).text(e).appendTo(this.$body),this.$copyButton=$("").addClass(["btn","btn-outline-dark","btn-copy","mt-2"]).text("Copy to clipboard").on("click",(()=>{this.$dataContainer[0].select(),document.execCommand("copy"),this.hide()})).appendTo(this.$footer)}}},"./src/js/view-html/editor/modal-import.js":function(e,t,s){const n=s("./src/js/view-html/modal.js");e.exports=class extends n{constructor(){super({title:"Import maze"}),this.$dataContainer=$('').attr({rows:10,placeholder:"Paste the JSON object here."}).appendTo(this.$body),this.$errorText=$('

').appendTo(this.$footer).hide(),this.$copyButton=$("").addClass(["btn","btn-primary"]).text("Import").on("click",(()=>{try{const e=JSON.parse(this.$dataContainer.val());this.hide(e)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-load.js":function(e,t,s){const n=s("./src/js/view-html/modal.js"),o=s("./src/js/view-html/editor/maze-browser.js");e.exports=class extends n{constructor(e,t){super({title:"Load maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$loadButton=$("").addClass(["btn","btn-primary"]).text("Load").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/editor/modal-save.js":function(e,t,s){const n=s("./src/js/view-html/modal.js"),o=s("./src/js/view-html/editor/maze-browser.js");e.exports=class extends n{constructor(e,t){super({title:"Save maze",size:"lg"}),this.$browserContainer=$("
").appendTo(this.$body),this.browser=new o(this.$browserContainer,e,t,!0),this.$cancelButton=$("").addClass(["btn","btn-secondary"]).text("Cancel").on("click",(()=>{this.hide(null)})).appendTo(this.$footer),this.$saveButton=$("").addClass(["btn","btn-primary"]).text("Save").on("click",(()=>{try{this.hide(this.browser.selectedData)}catch(e){this.showError(e.message)}})).appendTo(this.$footer)}showError(e){this.$errorText.html(e),this.$errorText.show()}}},"./src/js/view-html/exhibit-maze-editor-palette.js":function(e,t,s){s("./node_modules/core-js/modules/es.array.iterator.js"),s("./node_modules/core-js/modules/es.object.from-entries.js"),s("./node_modules/core-js/modules/web.dom-collections.iterator.js");const n=s("./node_modules/events/events.js"),o=s("./src/js/view-html/hold-button.js");e.exports=class{constructor(e,t){this.$container=e,this.$element=$("
").appendTo(this.$container),this.config=t,this.activeButton=null,this.tileId=null,this.events=new n,this.$element.addClass(["maze-editor-palette","exhibit-maze-editor-palette"]),this.$bar1=$('
').appendTo(this.$element),this.tileButtons=this.buildTileButtons(t),this.$bar1.append(Object.values(this.tileButtons)),this.resetMapButton=o({holdTime:2e3}).addClass(["editor-palette-button","editor-palette-button-action","editor-palette-button-action-reset-map"]).on("hold",(()=>{this.events.emit("action","reset-map")})).appendTo(this.$element).find(".text").html("Reset map").attr({title:"Reset map","data-i18n-text":"editor-palette-button-action-reset-map"})}buildTileButtons(e){return Object.fromEntries(Object.entries(e.tileTypes).filter((e=>{let[,t]=e;return!1!==t.inPalette})).map((e=>{let[t,s]=e;return[t,$("
").addClass(["item"]).attr("data-tile-id",t).append($("").attr({type:"button",title:s.name}).addClass(["editor-palette-button","editor-palette-button-tile","editor-palette-button-tile-".concat(t)]).css({backgroundColor:s.color,backgroundImage:s.editorIcon?"url(".concat(s.editorIcon,")"):"none"}).pointerclick().on("i.pointerclick",(e=>{this.activeButton&&this.activeButton.removeClass("active"),this.activeButton=$(e.target),this.activeButton.addClass("active"),this.tileId=Number(t),this.events.emit("change","tile",Number(t))}))).append($("
").addClass("label").attr("data-i18n-text","editor-palette-button-tile-".concat(s.type)))]})))}}},"./src/js/view-html/hold-button.js":function(e){e.exports=function(e){const t=$("").attr({type:"button"}).addClass("hold-button").append($('').css({animationDuration:"".concat(e.holdTime,"ms")})).append($(''));let s=null,n=null;function o(){null!==n&&(clearTimeout(n),n=null),t.removeClass("held"),s=null}return $(document).on("pointerup",(e=>{e.pointerId===s&&o()})).on("pointercancel",(e=>{e.pointerId===s&&o()})),t.on("pointerdown",(o=>{null===s&&(o.preventDefault(),s=o.pointerId,o.delegateTarget.releasePointerCapture(o.pointerId),null!==n&&(clearTimeout(n),n=null),t.addClass("held"),n=setTimeout((()=>{n=null,s=null,t.trigger("hold"),t.removeClass("held")}),e.holdTime))})),t}},"./src/js/view-html/lang-switcher.js":function(e,t,s){s("./node_modules/core-js/modules/es.array.iterator.js"),s("./node_modules/core-js/modules/web.dom-collections.iterator.js");e.exports=class{constructor(e,t,s){this.menuVisible=!1,this.container=e,this.config=t,this.langChangeCallback=s,this.menuItems={},this.render()}render(){this.element=document.createElement("div"),this.element.classList.add("lang-switcher"),this.trigger=document.createElement("button"),this.trigger.setAttribute("type","button"),this.trigger.classList.add("lang-switcher-trigger"),this.element.appendChild(this.trigger);const e=document.createElement("div");e.classList.add("lang-switcher-menu-mask"),this.element.appendChild(e),this.menu=document.createElement("ul"),this.menu.classList.add("lang-switcher-menu"),e.appendChild(this.menu),Object.entries(this.config.languages).forEach((e=>{let[t,s]=e;const n=document.createElement("li"),o=document.createElement("button");o.setAttribute("type","button"),o.innerText=s,o.addEventListener("pointerdown",(e=>{this.langChangeCallback(t),e.preventDefault()})),n.appendChild(o),this.menu.appendChild(n),this.menuItems[t]=n})),this.container.appendChild(this.element),this.menu.style.bottom="".concat(-1*this.menu.clientHeight-10,"px"),window.document.addEventListener("pointerdown",(()=>{this.menuVisible&&this.hideMenu()})),this.trigger.addEventListener("pointerdown",(e=>{this.menuVisible||(this.showMenu(),e.stopPropagation())}))}showMenu(){this.menuVisible=!0,this.menu.classList.add("visible")}hideMenu(){this.menuVisible=!1,this.menu.classList.remove("visible")}setActiveLanguage(e){Object.entries(this.menuItems).forEach((t=>{let[s,n]=t;s===e?n.classList.add("active"):n.classList.remove("active")}))}}},"./src/js/view-html/modal.js":function(e,t,s){s("./node_modules/core-js/modules/es.promise.js");e.exports=class{constructor(e){this.returnValue=null,this.$element=$(''),this.$dialog=$('').appendTo(this.$element),this.$content=$('').appendTo(this.$dialog),this.$header=$('').appendTo(this.$content),this.$body=$('').appendTo(this.$content),this.$footer=$('').appendTo(this.$content),this.$closeButton=$('
')\n .attr({\n type: 'button',\n title: props.title,\n 'data-i18n-text': `ai-training-view-button-${props.id}`,\n })\n .addClass([\n 'btn',\n 'ai-training-view-button',\n `ai-training-view-button-${props.id}`,\n ])\n .html(props.icon ? ' ' : props.title || '')\n .pointerclick();\n\n if (props.icon) {\n button.css({\n backgroundImage: `url(${props.icon})`,\n });\n button.addClass('round');\n }\n\n if (props.type === 'toggle') {\n button.on('i.pointerdown', () => {\n button.toggleClass('active');\n if (button.hasClass('active')) {\n button.trigger('active');\n } else {\n button.trigger('inactive');\n }\n });\n } else {\n // Default type is 'hold'\n button.on('i.pointerdown', () => {\n button.addClass('active');\n button.trigger('active');\n });\n button.on('i.pointerup', () => {\n button.removeClass('active');\n button.trigger('inactive');\n });\n }\n\n return button;\n }\n\n // eslint-disable-next-line class-methods-use-this\n buildSlider(props) {\n const {\n id, title, options, initialValue, changeCallback,\n } = props;\n\n const $element = $('
')\n .addClass([\n 'slider',\n 'ai-training-view-slider',\n `ai-training-view-slider-${id}`,\n ]);\n\n const $text = $('
')\n .appendTo($element);\n const $input = $('
')\n .appendTo($element);\n const $exploreValue = $('')\n .text(initialValue);\n $('')\n .html(`${title}: `)\n .append($exploreValue)\n .appendTo($text);\n\n if (props.limitLabels) {\n const [minLabel, maxLabel] = props.limitLabels;\n $('')\n .addClass(['slider-limit', 'slider-limit-min'])\n .text(minLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-min`)\n .appendTo($text);\n $('')\n .addClass(['slider-limit', 'slider-limit-max'])\n .text(maxLabel)\n .attr('data-i18n-text', `ai-training-view-slider-${id}-limit-max`)\n .appendTo($text);\n }\n\n const $exploreSlider = $('')\n .addClass('form-control-range')\n .attr(options)\n .on('change', () => {\n changeCallback(Number($exploreSlider.val()));\n $exploreValue.text(Number($exploreSlider.val()));\n })\n .val(initialValue)\n .appendTo($input);\n\n return $element;\n }\n}\n\nAITrainingView.defaultOptions = {\n showViewPolicyButton: true,\n useToggleViewPolicyButton: false,\n useToggleFFButton: false,\n};\n\nmodule.exports = AITrainingView;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\n\nclass MazeBrowser {\n constructor($element, config, mazeStore, saveMode = false) {\n this.$element = $element;\n this.config = config;\n this.$selectedButton = null;\n this.selectedData = null;\n\n this.$element.addClass('maze-browser');\n\n const setSelection = (button) => {\n if (this.$selectedButton) {\n this.$selectedButton.removeClass('selected');\n }\n this.$selectedButton = $(button);\n this.$selectedButton.addClass('selected');\n };\n\n const buttons = Object.entries(\n saveMode ? mazeStore.getAllUserObjects() : mazeStore.getAllObjects()\n ).map(([id, mazeJSON]) => $('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append(\n $('')\n .addClass('maze-browser-item')\n .append(this.createPreviewImage(mazeJSON))\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = id;\n })\n ));\n\n if (saveMode) {\n buttons.unshift($('
')\n .addClass(['col-6', 'col-md-2', 'mb-3'])\n .append($('')\n .addClass('maze-browser-item-new')\n .on('click', (ev) => {\n setSelection(ev.currentTarget);\n this.selectedData = 'new';\n })));\n }\n\n this.$element.append($('
').append(buttons));\n }\n\n createPreviewImage(mazeJSON) {\n const maze = Maze.fromJSON(mazeJSON);\n const $canvas = $('')\n .attr({\n width: maze.map.width,\n height: maze.map.height,\n });\n const ctx = $canvas[0].getContext('2d');\n maze.map.allCells().forEach(([i, j, value]) => {\n ctx.fillStyle = (this.config.tileTypes && this.config.tileTypes[value].color) || '#000000';\n ctx.fillRect(i, j, 1, 1);\n });\n\n return $canvas;\n }\n}\n\nmodule.exports = MazeBrowser;\n","// noinspection JSUnresolvedReference\n\nconst Maze = require('../../model/maze');\nconst MazeView = require('../../view-pixi/maze-view');\nconst ModalLoad = require('./modal-load');\nconst ModalSave = require('./modal-save');\nconst ModalExport = require('./modal-export');\nconst ModalImport = require('./modal-import');\nconst ObjectStore = require('../../helpers-html/object-store');\n\nclass MazeEditor {\n constructor($element, maze, palette, config, textures) {\n this.$element = $element;\n this.maze = maze;\n this.palette = palette;\n this.config = config;\n\n this.mazeView = new MazeView(maze, config, textures, true);\n this.displayObject = this.mazeView.displayObject;\n\n const tools = {\n start: (x, y) => {\n if (this.maze.robots.length > 0) {\n this.maze.robots[0].setPosition(x, y);\n }\n },\n erase: (x, y) => {\n const item = this.maze.getItem(x, y);\n if (item && item.erasable) {\n this.maze.removeItem(x, y);\n }\n },\n tile: (tileType, x, y) => {\n if (this.maze.startPosition[0] === x && this.maze.startPosition[1] === y) {\n return;\n }\n if (this.placedTileIsFixed(x, y)) {\n return;\n }\n this.maze.removeItem(x, y);\n this.maze.map.set(x, y, tileType);\n if (this.config.tileTypes[tileType].item !== undefined) {\n this.maze.placeItem(this.config.tileTypes[tileType].item, x, y, false);\n }\n },\n item: (itemType, x, y) => {\n if (this.maze.isWalkable(x, y)) {\n this.maze.placeItem(itemType, x, y);\n }\n },\n };\n\n this.toolHandler = null;\n this.palette.events.on('change', (tool, type = null) => {\n if (type !== null) {\n this.toolHandler = tools[tool].bind(this, type);\n } else {\n this.toolHandler = tools[tool].bind(this);\n }\n });\n\n this.palette.events.on('action', (id) => {\n if (this.actionHandlers[id]) {\n this.actionHandlers[id]();\n }\n });\n\n let lastEdit = null;\n this.mazeView.events.on('action', ([x, y], props) => {\n if (this.toolHandler !== null) {\n if (lastEdit && props.shiftKey) {\n const [lastX, lastY] = lastEdit;\n for (let i = Math.min(lastX, x); i <= Math.max(lastX, x); i += 1) {\n for (let j = Math.min(lastY, y); j <= Math.max(lastY, y); j += 1) {\n this.toolHandler(i, j);\n }\n }\n } else {\n this.toolHandler(x, y);\n }\n lastEdit = [x, y];\n }\n });\n\n this.objectStore = new ObjectStore();\n this.actionHandlers = {\n load: () => {\n const modal = new ModalLoad(this.config, this.objectStore);\n modal.show().then((id) => {\n const jsonMaze = id && this.objectStore.get(id);\n if (jsonMaze) {\n this.maze.copy(Maze.fromJSON(jsonMaze));\n }\n });\n },\n save: () => {\n const modal = new ModalSave(this.config, this.objectStore);\n modal.show().then((id) => {\n if (id) {\n this.objectStore.set(id === 'new' ? null : id, this.maze.toJSON());\n }\n });\n },\n import: () => {\n const modal = new ModalImport();\n modal.show().then((importedData) => {\n if (importedData) {\n this.maze.copy(Maze.fromJSON(importedData));\n }\n });\n },\n export: () => {\n const modal = new ModalExport(JSON.stringify(this.maze));\n // noinspection JSIgnoredPromiseFromCall\n modal.show();\n },\n reset: () => {\n this.maze.reset();\n },\n };\n }\n\n placedTileIsFixed(x, y) {\n const tileType = this.maze.map.get(x, y);\n return this.config.tileTypes[tileType].fixed;\n }\n\n animate(time) {\n this.mazeView.animate(time);\n }\n\n getRobotView() {\n return this.mazeView.robotView;\n }\n}\n\nmodule.exports = MazeEditor;\n","const Modal = require('../modal');\n\nclass ModalExport extends Modal {\n constructor(exportData) {\n super({\n title: 'Export maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n })\n .text(exportData)\n .appendTo(this.$body);\n\n this.$copyButton = $('')\n .addClass(['btn', 'btn-outline-dark', 'btn-copy', 'mt-2'])\n .text('Copy to clipboard')\n .on('click', () => {\n this.$dataContainer[0].select();\n document.execCommand('copy');\n this.hide();\n })\n .appendTo(this.$footer);\n }\n}\n\nmodule.exports = ModalExport;\n","const Modal = require('../modal');\n\nclass ModalImport extends Modal {\n constructor() {\n super({\n title: 'Import maze',\n });\n\n this.$dataContainer = $('')\n .attr({\n rows: 10,\n placeholder: 'Paste the JSON object here.',\n })\n .appendTo(this.$body);\n\n // noinspection JSUnusedGlobalSymbols\n this.$errorText = $('

')\n .appendTo(this.$footer)\n .hide();\n\n // noinspection JSUnusedGlobalSymbols\n this.$copyButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Import')\n .on('click', () => {\n try {\n const imported = JSON.parse(this.$dataContainer.val());\n this.hide(imported);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalImport;\n","const Modal = require('../modal');\nconst CityBrowser = require('./maze-browser');\n\nclass ModalLoad extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Load maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new CityBrowser(this.$browserContainer, config, mazeStore);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$loadButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Load')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalLoad;\n","const Modal = require('../modal');\nconst MazeBrowser = require('./maze-browser');\n\nclass ModalSave extends Modal {\n constructor(config, mazeStore) {\n super({\n title: 'Save maze',\n size: 'lg',\n });\n\n this.$browserContainer = $('
')\n .appendTo(this.$body);\n this.browser = new MazeBrowser(this.$browserContainer, config, mazeStore, true);\n\n // noinspection JSUnusedGlobalSymbols\n this.$cancelButton = $('')\n .addClass(['btn', 'btn-secondary'])\n .text('Cancel')\n .on('click', () => {\n this.hide(null);\n })\n .appendTo(this.$footer);\n\n // noinspection JSUnusedGlobalSymbols\n this.$saveButton = $('')\n .addClass(['btn', 'btn-primary'])\n .text('Save')\n .on('click', () => {\n try {\n this.hide(this.browser.selectedData);\n } catch (err) {\n this.showError(err.message);\n }\n })\n .appendTo(this.$footer);\n }\n\n showError(errorText) {\n this.$errorText.html(errorText);\n this.$errorText.show();\n }\n}\n\nmodule.exports = ModalSave;\n","// noinspection JSUnresolvedReference\n\nconst EventEmitter = require('events');\nconst createHoldButton = require('./hold-button');\n\nclass ExhibitMazeEditorPalette {\n constructor($container, config) {\n this.$container = $container;\n this.$element = $('
').appendTo(this.$container);\n this.config = config;\n this.activeButton = null;\n this.tileId = null;\n this.events = new EventEmitter();\n\n this.$element.addClass(['maze-editor-palette', 'exhibit-maze-editor-palette']);\n this.$bar1 = $('
')\n .appendTo(this.$element);\n\n this.tileButtons = this.buildTileButtons(config);\n this.$bar1.append(Object.values(this.tileButtons));\n\n this.resetMapButton = createHoldButton({\n holdTime: 2000,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-action',\n 'editor-palette-button-action-reset-map',\n ])\n .on('hold', () => {\n this.events.emit('action', 'reset-map');\n })\n .appendTo(this.$element)\n .find('.text')\n .html('Reset map')\n .attr({\n title: 'Reset map',\n 'data-i18n-text': 'editor-palette-button-action-reset-map',\n });\n }\n\n buildTileButtons(config) {\n return Object.fromEntries(\n Object.entries(config.tileTypes)\n .filter(([, tileType]) => tileType.inPalette !== false)\n .map(([id, typeCfg]) => [id, $('
')\n .addClass(['item'])\n .attr('data-tile-id', id)\n .append($('')\n .attr({\n type: 'button',\n title: typeCfg.name,\n })\n .addClass([\n 'editor-palette-button',\n 'editor-palette-button-tile',\n `editor-palette-button-tile-${id}`,\n ])\n .css({\n backgroundColor: typeCfg.color,\n backgroundImage: typeCfg.editorIcon ? `url(${typeCfg.editorIcon})` : 'none',\n })\n .pointerclick()\n .on('i.pointerclick', (ev) => {\n if (this.activeButton) {\n this.activeButton.removeClass('active');\n }\n this.activeButton = $(ev.target);\n this.activeButton.addClass('active');\n this.tileId = Number(id);\n this.events.emit('change', 'tile', Number(id));\n }))\n .append($('
')\n .addClass('label')\n .attr('data-i18n-text', `editor-palette-button-tile-${typeCfg.type}`))])\n );\n }\n}\n\nmodule.exports = ExhibitMazeEditorPalette;\n","/**\n * props:\n * - holdTime: time in ms to hold the button\n */\nfunction createHoldButton(props) {\n const $button = $('')\n .attr({\n type: 'button',\n })\n .addClass('hold-button')\n .append(\n $('')\n .css({\n animationDuration: `${props.holdTime}ms`,\n })\n )\n .append(\n $('')\n );\n\n let trackedPointerId = null;\n let holdTimeout = null;\n\n function startHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n\n $button.addClass('held');\n holdTimeout = setTimeout(() => {\n holdTimeout = null;\n trackedPointerId = null;\n $button.trigger('hold');\n $button.removeClass('held');\n }, props.holdTime);\n }\n\n function abortHolding() {\n if (holdTimeout !== null) {\n clearTimeout(holdTimeout);\n holdTimeout = null;\n }\n $button.removeClass('held');\n trackedPointerId = null;\n }\n\n $(document)\n .on('pointerup', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n })\n .on('pointercancel', (ev) => {\n if (ev.pointerId === trackedPointerId) {\n abortHolding();\n }\n });\n\n $button.on('pointerdown', (ev) => {\n if (trackedPointerId === null) {\n ev.preventDefault();\n trackedPointerId = ev.pointerId;\n // On touch, apparently, the pointer is automatically captured by pointerdown\n ev.delegateTarget.releasePointerCapture(ev.pointerId);\n startHolding();\n }\n });\n\n return $button;\n}\n\nmodule.exports = createHoldButton;\n","class LangSwitcher {\n constructor(container, config, langChangeCallback) {\n this.menuVisible = false;\n this.container = container;\n this.config = config;\n this.langChangeCallback = langChangeCallback;\n this.menuItems = {};\n\n this.render();\n }\n\n render() {\n this.element = document.createElement('div');\n this.element.classList.add('lang-switcher');\n\n this.trigger = document.createElement('button');\n this.trigger.setAttribute('type', 'button');\n this.trigger.classList.add('lang-switcher-trigger');\n this.element.appendChild(this.trigger);\n\n const mask = document.createElement('div');\n mask.classList.add('lang-switcher-menu-mask');\n this.element.appendChild(mask);\n\n this.menu = document.createElement('ul');\n this.menu.classList.add('lang-switcher-menu');\n mask.appendChild(this.menu);\n\n Object.entries(this.config.languages).forEach(([code, name]) => {\n const item = document.createElement(('li'));\n const link = document.createElement('button');\n link.setAttribute('type', 'button');\n // noinspection JSValidateTypes\n link.innerText = name;\n link.addEventListener('pointerdown', (ev) => {\n this.langChangeCallback(code);\n ev.preventDefault();\n });\n item.appendChild(link);\n this.menu.appendChild(item);\n this.menuItems[code] = item;\n });\n\n this.container.appendChild(this.element);\n\n this.menu.style.bottom = `${this.menu.clientHeight * -1 - 10}px`;\n\n window.document.addEventListener('pointerdown', () => {\n if (this.menuVisible) {\n this.hideMenu();\n }\n });\n this.trigger.addEventListener('pointerdown', (ev) => {\n if (!this.menuVisible) {\n this.showMenu();\n ev.stopPropagation();\n }\n });\n }\n\n showMenu() {\n this.menuVisible = true;\n this.menu.classList.add('visible');\n }\n\n hideMenu() {\n this.menuVisible = false;\n this.menu.classList.remove('visible');\n }\n\n setActiveLanguage(code) {\n Object.entries(this.menuItems).forEach(([langCode, item]) => {\n if (langCode === code) {\n item.classList.add('active');\n } else {\n item.classList.remove('active');\n }\n });\n }\n}\n\nmodule.exports = LangSwitcher;\n","class Modal {\n /**\n * @param {object} options\n * Modal dialog options\n * @param {string} options.title\n * Dialog title.\n * @param {string} options.size\n * Modal size (lg or sm).\n * @param {boolean} options.showCloseButton\n * Shows a close button in the dialog if true.\n * @param {boolean} options.showFooter\n * Adds a footer area to the dialog if true.\n */\n constructor(options) {\n this.returnValue = null;\n\n this.$element = $('
');\n this.$dialog = $('
').appendTo(this.$element);\n this.$content = $('
').appendTo(this.$dialog);\n this.$header = $('
').appendTo(this.$content);\n this.$body = $('
').appendTo(this.$content);\n this.$footer = $('
').appendTo(this.$content);\n\n this.$closeButton = $('