var keyRepeatTimer=0; var keyRepeatIndex=0; var input_throttle_timer=0.0; var lastinput=-100; var dragging=false; var rightdragging=false; var columnAdded=false; function selectText(containerid,e) { e = e || window.event; var myspan = document.getElementById(containerid); if (e&&(e.ctrlKey || e.metaKey)) { if(solving) return; var levelarr = ["console"].concat(myspan.innerHTML.split("
")); var leveldat = levelFromString(state,levelarr); loadLevelFromLevelDat(state,leveldat,null); canvasResize(); } else { if (document.selection) { var range = document.body.createTextRange(); range.moveToElementText(myspan); range.select(); } else if (window.getSelection) { var range = document.createRange(); range.selectNode(myspan); var selection = window.getSelection(); //why removeallranges? https://stackoverflow.com/a/43443101 whatever... selection.removeAllRanges(); selection.addRange(range); } } } function recalcLevelBounds(){ } function arrCopy(from, fromoffset, to, tooffset, len) { while (len--) to[tooffset++] = from[fromoffset]++; } function adjustLevel(level, widthdelta, heightdelta) { backups.push(backupLevel()); var oldlevel = level.clone(); level.width += widthdelta; level.height += heightdelta; level.n_tiles = level.width * level.height; level.objects = new Int32Array(level.n_tiles * STRIDE_OBJ); var bgMask = new BitVec(STRIDE_OBJ); bgMask.ibitset(state.backgroundid); for (var i=0; ihighestbitcount) { highestbitcount=bitcount; highestmask=glyphname; } } } if (highestbitcount>0) { return highestmask; } logErrorNoLine("Wasn't able to approximate a glyph value for some tiles, using '.' as a placeholder.",true); return '.'; } var htmlEntityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/' }; var selectableint = 0; function printLevel() { var glyphMasks = []; for (var glyphName in state.glyphDict) { if (state.glyphDict.hasOwnProperty(glyphName)&&glyphName.length===1) { var glyph = state.glyphDict[glyphName]; var glyphmask=new BitVec(STRIDE_OBJ); for (var i=0;i=0) { glyphmask.ibitset(id); } } var glyphbits = glyphmask.clone(); //register the same - backgroundmask with the same name var bgMask = state.layerMasks[state.backgroundlayer]; glyphmask.iclear(bgMask); glyphMasks.push([glyphName, glyphmask, glyphbits]); } } selectableint++; var tag = 'selectable'+selectableint; var output="Printing level contents:


"; cache_console_messages = false; for (var j=0;j"; } } output+="


" consolePrint(output,true); } function levelEditorClick(event,click) { if (mouseCoordY<=-2) { var ypos = editorRowCount-(-mouseCoordY-2)-1; var newindex=mouseCoordX+(screenwidth-1)*ypos; if (mouseCoordX===-1) { printLevel(); } else if (mouseCoordX>=0&&newindex-1&&mouseCoordY>-1&&mouseCoordX=0) { glyphmask.ibitset(id); } } var backgroundMask = state.layerMasks[state.backgroundlayer]; if (glyphmask.bitsClearInArray(backgroundMask.data)) { // If we don't already have a background layer, mix in // the default one. glyphmask.ibitset(state.backgroundid); } var coordIndex = mouseCoordY + mouseCoordX*level.height; var getcell = level.getCell(coordIndex); if (getcell.equals(glyphmask)) { return; } else { if (anyEditsSinceMouseDown===false) { anyEditsSinceMouseDown=true; backups.push(backupLevel()); } level.setCell(coordIndex, glyphmask); redraw(); } } else if (click) { if (mouseCoordX===-1) { //add a left row to the map addLeftColumn(); canvasResize(); } else if (mouseCoordX===screenwidth-2) { addRightColumn(); canvasResize(); } if (mouseCoordY===-1) { addTopRow(); canvasResize(); } else if (mouseCoordY===screenheight-2-editorRowCount) { addBottomRow(); canvasResize(); } } } function levelEditorRightClick(event,click) { if (mouseCoordY===-2) { if (mouseCoordX<=glyphImages.length) { glyphSelectedIndex=mouseCoordX; redraw(); } } else if (mouseCoordX>-1&&mouseCoordY>-1&&mouseCoordX=0 ? 1 : -1; var ySign = (cellY2-cellY1)>=0 ? 1 : -1; var yTrimmer = cellY1; var tileListX = []; var tileListY = []; for (var i=cellX1; i != cellX2+xSign; i += xSign) { var over = false; for (var j=yTrimmer; j != cellY2+ySign; j += ySign) { if (j >= level.height || j < 0 || i >= level.width || i < 0) { error = "Mouse input loop failed; it:" + i + " " + j + " cell1:" + cellX1 + " " + cellY1 + " cell2:" + cellX2 + " " + cellY2; console.log(error); throw error; } if (isInside(i*2*cellwidth+offsetToCenterTimesTwo, j*2*cellwidth+offsetToCenterTimesTwo)){ tileListX.push(i); tileListY.push(j); // console.log(i + " " + j + " w:" + level.width + " h:" + level.height); over = true; yTrimmer = j; } else if (over) { break; } } } var avoidObstacles = "mouse_obstacle" in state.metadata; var otherTileListX = [cellX1]; var otherTileListY = [cellY1]; while(cellX1 !== cellX2 || cellY1 !== cellY2) { if (cellY1 >= level.height || cellY1 < 0 || cellX1 >= level.width || cellX1 < 0) { error = "Mouse input loop failed; cell1:" + cellX1 + " " + cellY1 + " cell2:" + cellX2 + " " + cellY2; console.log(error); throw error; } hitObstacle = function () { var coordIndex = screenOffsetY+cellY1 + (screenOffsetX+cellX1)*level.height; var tile = level.getCell(coordIndex); if (state.obstacleMask.anyBitsInCommon(tile)) { return true; } else { return false; } } cellCornerXTimesTwo = (cellX1*2)*cellwidth+offsetToCenterTimesTwo + xSign*cellwidth; cellCornerYTimesTwo = (cellY1*2)*cellwidth+offsetToCenterTimesTwo + ySign*cellwidth; fOfPointTimesTwo = dirY*cellCornerXTimesTwo - dirX*cellCornerYTimesTwo; if ((fOfPointTimesTwo > scaledShiftMid*2 == ySign > 0) != (xSign > 0)) { cellX1 += xSign; if (avoidObstacles && hitObstacle()) { cellX1 -= xSign; cellY1 += ySign; } } else { cellY1 += ySign; if (avoidObstacles && hitObstacle()) { cellY1 -= ySign; cellX1 += xSign; } } if (!(avoidObstacles && hitObstacle())) { otherTileListX.push(cellX1); otherTileListY.push(cellY1); } else { break; } } if (avoidObstacles) { tileListX = otherTileListX; tileListY = otherTileListY; } else for (var i=0; i 12) { hoverSelection = -1; } else { hoverSelection = mouseCoordY; } return; } if (titleScreen) { if (quittingTitleScreen || titleSelected) { return; } if (titleMode===0) { titleButtonSelected(); } else if (titleMode===1) { if (mouseCoordY===5 && titleSelectOptions >= 1) { titleSelection=0; titleButtonSelected(); } else if (mouseCoordY===6 && titleSelectOptions >= 3) { titleSelection=1; titleButtonSelected(); } else if (mouseCoordY===7 && titleSelectOptions >= 2) { if (titleSelectOptions === 2) { titleSelection = 1; } else { titleSelection = 2; } titleButtonSelected(); } else if (mouseCoordY===8 && titleSelectOptions >= 4) { titleSelection=3; titleButtonSelected(); } } else if (titleMode===2) { if (quittingTitleScreen || titleSelected) { return; } //console.log(mouseCoordY); if (mouseCoordY===0) { titleSelection = 0; goToTitleScreen(); tryPlayTitleSound(); canvasResize(); } else if (mouseCoordY===2) { if (levelSelectScrollPos != 0) { levelSelectScroll(-3) } } else if (mouseCoordY===12) { if (titleSelectOptions - amountOfLevelsOnScreen > levelSelectScrollPos) { levelSelectScroll(3) } } else { var clickedLevel = -1; switch (mouseCoordY) { case 3: clickedLevel = 0; break; case 4: clickedLevel = 1; break; case 5: clickedLevel = 2; break; case 6: clickedLevel = 3; break; case 7: clickedLevel = 4; break; case 8: clickedLevel = 5; break; case 9: clickedLevel = 6; break; case 10: clickedLevel = 7; break; case 11: clickedLevel = 8; break; } if (clickedLevel != -1) { clickedLevel += levelSelectScrollPos; if (clickedLevel < titleSelectOptions) { //console.log("Clicked level "+clickedLevel); titleSelection = clickedLevel; titleSelected=true; messageselected=false; timer=0; quittingTitleScreen=true; generateLevelSelectScreen(); } } } } } else if (messageselected===false && state.levels[curlevel].message) { messageselected=true; timer=0; quittingMessageScreen=true; tryPlayCloseMessageSound(); titleScreen=false; drawMessageScreen(); } } else { if (winning) {return;} x1 = x2; y1 = y2; x2 = mousePixelX; y2 = mousePixelY; // clamp mouse target pixel (for moving) x2 = Math.max(0, Math.min(cellwidth*screenwidth-1, mousePixelX)); y2 = Math.max(0, Math.min(cellheight*screenheight-1, mousePixelY)); if (!click) { var tileLists = getTilesTraversingPoints(x1, y1, x2, y2); var tileListX = tileLists.tileListX; var tileListY = tileLists.tileListY; for (var i=0; i=0 && mouseCoordY>=0 && mouseCoordX -1)) { if (event&&(event.ctrlKey || event.metaKey)){ } else { prevent(event); } } if ((!IDE) && event.keyCode===77){//m toggleMute(); } if (keybuffer.indexOf(event.keyCode)>=0) { return; } if(lastDownTarget === canvas || (window.Mobile && (lastDownTarget === window.Mobile.focusIndicator) ) ){ if (keybuffer.indexOf(event.keyCode)===-1) { if (event&&(event.ctrlKey || event.metaKey)){ } else { keybuffer.splice(keyRepeatIndex,0,event.keyCode); keyRepeatTimer=0; checkKey(event,!event.repeat); } } } if (canDump===true) { if (event.keyCode===74 && (event.ctrlKey||event.metaKey)) {//ctrl+j dumpTestCase(); prevent(event); } else if (event.keyCode===75 && (event.ctrlKey||event.metaKey)) {//ctrl+k makeGIF(); prevent(event); } else if (event.keyCode===83 && (event.ctrlKey||event.metaKey)) {//ctrl+s saveClick(); prevent(event); } else if (event.keyCode===66 && (event.ctrlKey||event.metaKey)) {//ctrl+b rebuildClick(); prevent(event); event.target.blur(); canvas.focus(); } else if (event.keyCode===82 && (event.ctrlKey||event.metaKey)) {//ctrl+r runClick(); prevent(event); event.target.blur(); canvas.focus(); } else if (event.keyCode===120) { //f9 prevent(event); solve(); } else if (event.keyCode===119) { //f8 prevent(event); stopSolving(); } else if (event.keyCode===13 && (event.ctrlKey||event.metaKey)){//ctrl+enter canvas.focus(); editor.display.input.blur(); if (event.shiftKey){ runClick(); } else { rebuildClick(); } prevent(event); } } } function relMouseCoords(event){ var totalOffsetX = 0; var totalOffsetY = 0; var canvasX = 0; var canvasY = 0; var currentElement = this; do{ totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft; totalOffsetY += currentElement.offsetTop - currentElement.scrollTop; } while(currentElement = currentElement.offsetParent) if (event.touches==null){ canvasX = event.pageX - totalOffsetX; canvasY = event.pageY - totalOffsetY; } else { canvasX = event.touches[0].pageX - totalOffsetX; canvasY = event.touches[0].pageY - totalOffsetY; } return {x:canvasX, y:canvasY} } HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords; function onKeyUp(event) { event = event || window.event; var index=keybuffer.indexOf(event.keyCode); if (index>=0){ keybuffer.splice(index,1); if (keyRepeatIndex>=index){ keyRepeatIndex--; } } } function onMyFocus(event) { keybuffer=[]; keyRepeatIndex = 0; keyRepeatTimer = 0; } function onMyBlur(event) { keybuffer=[]; keyRepeatIndex = 0; keyRepeatTimer = 0; } var mouseCoordX=0; var mouseCoordY=0; var mousePixelX=0; var mousePixelY=0; function setMouseCoord(e){ var coords = canvas.relMouseCoords(e); if (isNaN(coords.x) || isNaN(coords.y)) { console.warn("[SetMouseCoord] Did not recieve valid mouse coords from event. Ignoring it (since I'm assuming this is a faked keypress that was generated on mobile).") } mousePixelX=coords.x-xoffset; mousePixelY=coords.y-yoffset; mouseCoordX=Math.floor(mousePixelX/cellwidth); mouseCoordY=Math.floor(mousePixelY/cellheight); } function mouseMove(event) { if (event.handled){ return; } if (levelEditorOpened) { setMouseCoord(event); if (dragging) { levelEditorClick(event,false); } else if (rightdragging){ levelEditorRightClick(event,false); } redraw(); } else if (titleScreen && IsMouseGameInputEnabled()) { var prevHoverSelection = hoverSelection; setMouseCoord(event); mouseAction(event,false,null); if (prevHoverSelection != hoverSelection) { if (titleMode == 1) { generateTitleScreen(); redraw(); } else if (titleMode == 2) { generateLevelSelectScreen(); redraw(); } } } else if (dragging && "mouse_drag" in state.metadata) { setMouseCoord(event); mouseAction(event,false,state.dragID); redraw(); } else if (rightdragging && "mouse_rdrag" in state.metadata) { setMouseCoord(event); mouseAction(event,false,state.rdragID); redraw(); } event.handled=true; //window.console.log("showcoord ("+ canvas.width+","+canvas.height+") ("+x+","+y+")"); } mouseInCanvas = false; function onMouseIn() { mouseInCanvas = true; //console.log("Cursor moved into canvas") } function onMouseOut() { mouseInCanvas = false; //console.log("Cursor moved out of canvas") } document.addEventListener('touchstart', onTouchDown, {passive: false}); document.addEventListener('touchmove', mouseMove, false); document.addEventListener('touchend', onTouchUp, {passive: false}); function onTouchDown(event) { onMouseDown(event, true) } function onTouchUp(event) { onMouseUp(event, true) } document.addEventListener('mousedown', onMouseDown, false); document.addEventListener('mouseup', onMouseUp, false); document.addEventListener('mousemove', mouseMove, false); document.addEventListener('contextmenu', rightClickCanvas, false); document.addEventListener('keydown', onKeyDown, false); document.addEventListener('keyup', onKeyUp, false); document.addEventListener('wheel', onMouseWheel, {passive: false}) window.addEventListener('focus', onMyFocus, false); window.addEventListener('blur', onMyBlur, false); canvas.addEventListener('mouseenter', onMouseIn, false); canvas.addEventListener('mouseleave', onMouseOut, false) function onMouseWheel(event) { if (!mouseInCanvas || event.ctrlKey) {return;} //console.log("Scroll "+event.deltaY); normalizedDelta = Math.sign(event.deltaY); if (titleScreen && titleMode == 2 && (IsMouseGameInputEnabled())) { levelSelectScroll(normalizedDelta); redraw(); prevent(event) } if (levelEditorOpened) { glyphSelectedIndex = clamp(glyphSelectedIndex + normalizedDelta, 0, glyphCount() - 1); redraw(); prevent(event) } } function levelSelectScroll(direction) { levelSelectScrollPos = clamp(levelSelectScrollPos + direction, 0, Math.max(state.sections.length - amountOfLevelsOnScreen, 0)); titleSelection = clamp(titleSelection + direction, 0, state.sections.length - 1); //console.log(levelSelectScrollPos + " " + titleSelection); generateLevelSelectScreen(); } function clamp(number, min, max) { return Math.min(Math.max(number, min), max); } function prevent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); if (e.stopPropagation) e.stopPropagation(); e.returnValue=false; return false; } function titleButtonSelected() { if (titleSelected===false) { tryPlayStartGameSound(); titleSelected=true; messageselected=false; timer=0; quittingTitleScreen=true; generateTitleScreen(); canvasResize(); document.dispatchEvent(new Event("psplusGameStarted")); } } var gamepadKeys = []; // used to store keys held at previous frame function pollGamepads() { function buttonCheck(buttons, i) { if(buttons.length < i) { return false; } if(typeof(buttons[i]) == "object") { return buttons[i].pressed; } return buttons[i] == 1.0; } function axisCheck(axes, i, dir) { if(axes.length < i) { return false; } return axes[i] * dir > 0.5; } var newGamepadKeys = []; function keyPressed(keycode) { if(keybuffer.indexOf(keycode) === -1) { keybuffer.splice(keyRepeatIndex, 0, keycode); keyRepeatTimer = 0; checkKey({keyCode: keycode}, true); } newGamepadKeys.splice(0, 0, keycode); } // clear any keys previously pressed but no longer held: function clear() { for(var k = 0; k < gamepadKeys.length; k++) { if(newGamepadKeys.indexOf(gamepadKeys[k]) >= 0) { continue; } var index = keybuffer.indexOf(gamepadKeys[k]); if(index >= 0) { keybuffer.splice(index, 1); if(keyRepeatIndex >= index) { keyRepeatIndex--; } } } gamepadKeys = newGamepadKeys; } var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []); if(gamepads == null || gamepads.length == 0) { clear(); return; } for(var i = 0; i < gamepads.length; i++) { var gamepad = gamepads[i]; if(gamepad == null || !gamepad.connected) { continue; } if(buttonCheck(gamepad.buttons, 3) // Y || buttonCheck(gamepad.buttons, 4)) { // LB keyPressed(82); // restart } if(buttonCheck(gamepad.buttons, 1) // B || axisCheck(gamepad.axes, 2, 1)) { // LT keyPressed(90); // undo } if(buttonCheck(gamepad.buttons, 2) // X || buttonCheck(gamepad.buttons, 0) // A || buttonCheck(gamepad.buttons, 5) // RB || axisCheck(gamepad.axes, 1, 1)) { // RT keyPressed(88); // action } if(buttonCheck(gamepad.buttons, 7)) { // menu button keyPressed(27); // exit } if(buttonCheck(gamepad.buttons, 6)) { // change view button keyPressed(69); // edit } if(axisCheck(gamepad.axes, 0, 1) // right || axisCheck(gamepad.axes, 6, 1)) { // D-pad right keyPressed(39); // right } if(axisCheck(gamepad.axes, 0, -1) // left || axisCheck(gamepad.axes, 6, -1)) { // D-pad left keyPressed(37); // left } if(axisCheck(gamepad.axes, 1, -1) // up || axisCheck(gamepad.axes, 7, -1)) { // D-pad up keyPressed(38); // up } if(axisCheck(gamepad.axes, 1, 1) // down || axisCheck(gamepad.axes, 7, 1)) { // D-pad down keyPressed(40); // down } } clear(); } function checkKey(e,justPressed) { ULBS(); if (winning) { return; } if (e&&(e.ctrlKey || e.metaKey|| e.altKey)){ return; } var inputdir=-1; switch(e.keyCode) { case 65://a case 37: //left { // window.console.log("LEFT"); inputdir=1; break; } case 38: //up case 87: //w { // window.console.log("UP"); inputdir=0; break; } case 68://d case 39: //right { // window.console.log("RIGHT"); inputdir=3; break; } case 83://s case 40: //down { // window.console.log("DOWN"); inputdir=2; break; } case 80://p { printLevel(); break; } case 13://enter case 32://space case 67://c case 88://x { // window.console.log("ACTION"); if (justPressed && ignoreNotJustPressedAction){ ignoreNotJustPressedAction=false; } if (justPressed===false && ignoreNotJustPressedAction){ return; } if (norepeat_action===false || justPressed) { inputdir=4; } else { return; } break; } case 85://u case 90://z { //undo if (textMode===false) { pushInput("undo"); DoUndo(false,true); canvasResize(); // calls redraw return prevent(e); } break; } case 82://r { if (textMode===false) { if (justPressed) { pushInput("restart"); DoRestart(); canvasResize(); // calls redraw return prevent(e); } } break; } case 27://escape { if(solving) { stopSolving(); break; } if (titleScreen===false || titleMode > 1) { if ((timer/1000>0.5 || titleMode > 1) && !quittingTitleScreen) { titleSelection = 0; timer = 0; if(titleScreen === false && state.metadata["level_select"] !== undefined) { gotoLevelSelectScreen(); } else { goToTitleScreen(); } tryPlayTitleSound(); canvasResize(); } return prevent(e) } break; } case 69: {//e if (!solving && canOpenEditor) { if (justPressed) { if (titleScreen){ if (state.title==="EMPTY GAME"){ compile(["loadFirstNonMessageLevel"]); } else { nextLevel(); } } levelEditorOpened=!levelEditorOpened; if (levelEditorOpened===false){ printLevel(); } restartTarget=backupLevel(); canvasResize(); } return prevent(e); } break; } case 48://0 case 49://1 case 50://2 case 51://3 case 52://4 case 53://5 case 54://6 case 55://7 case 56://8 case 57://9 { if (levelEditorOpened&&justPressed) { var num=9; if (e.keyCode>=49) { num = e.keyCode-49; } if (num0) { glyphSelectedIndex--; canvasResize(); return prevent(e); } } break; } case 187://+ { if (levelEditorOpened&&justPressed) { if (glyphSelectedIndex+1=0&&inputdir<=3) { if (lastinput==inputdir && input_throttle_timer= titleSelectOptions) { titleSelection -= titleSelectOptions; } else if (titleSelection < 0) { titleSelection += titleSelectOptions; } if(titleMode == 1) { generateTitleScreen(); } else if(titleMode == 2) { generateLevelSelectScreen(); } redraw(); } } } else { if (inputdir==4&&justPressed) { if (unitTesting) { nextLevel(); return; } else if (messageselected===false) { messageselected=true; timer=0; quittingMessageScreen=true; tryPlayCloseMessageSound(); titleScreen=false; drawMessageScreen(); } } } } else { if (!againing && inputdir>=0) { if (inputdir===4 && ('noaction' in state.metadata)) { } else { pushInput(inputdir); if (processInput(inputdir)) { redraw(); } } return prevent(e); } } } function update() { var draw = false; timer+=deltatime; tweentimer+=deltatime; input_throttle_timer+=deltatime; if (quittingTitleScreen) { if (timer/1000>0.3) { quittingTitleScreen=false; if(titleMode <= 1) { nextLevel(); } else if(titleMode == 2) { gotoLevel(titleSelection); } } } if (againing) { if (timer>againinterval&&messagetext.length==0) { if (processInput(-1)) { draw = true; keyRepeatTimer=0; autotick=0; } } } if (quittingMessageScreen) { if (timer/1000>0.15) { quittingMessageScreen=false; if (messagetext==="") { nextLevel(); } else { messagetext=""; textMode=false; titleScreen=false; titleMode=(curlevel>0||curlevelTarget!==null)?1:0; titleSelected=false; ignoreNotJustPressedAction=true; titleSelection=0; canvasResize(); checkWin(); } } } if (winning) { if (timer/1000>0.5) { winning=false; nextLevel(); } } pollGamepads(); if (keybuffer.length>0) { keyRepeatTimer+=deltatime; var ticklength = throttle_movement ? repeatinterval : repeatinterval/(Math.sqrt(keybuffer.length)); if (keyRepeatTimer>ticklength) { keyRepeatTimer=0; keyRepeatIndex = (keyRepeatIndex+1)%keybuffer.length; var key = keybuffer[keyRepeatIndex]; checkKey({keyCode:key},false); } } if (autotickinterval>0&&!textMode&&!levelEditorOpened&&!againing&&!winning) { autotick+=deltatime; if (autotick>autotickinterval) { autotick=0; pushInput("tick"); if (processInput(-1)) { draw = true; } } } if (draw || (typeof state !== "undefined" && (state.metadata.smoothscreen !== undefined || state.metadata.tween_length !== undefined))) { redraw(); } } var looping=false; // Lights, camera…function! var loop = function(){ looping=true; try { update(); } catch (e) { //Something went wrong, but it's more important that the loop doesn't crash during errors console.error(e); } if (document.visibilityState==='hidden'){ looping=false; return; }; setTimeout(loop,deltatime); } document.addEventListener('visibilitychange', function logData() { if (document.visibilityState === 'visible') { if (looping===false){ loop(); } } }); loop();