var canvas; var currentSound = new Audio(); const GOODQUOTES=[ "Let the conversion begin...", "All the bots, come to ME", "Let's convert them ALL", "What a fantastic machine you have built", "I like it!",] const BADQUOTES=[ "I smell a funny one here...", "I have found a flaw!", "How could you build me so disfunctionally?", "???", "You seemed to miss a step...",] var TITLESCREENTIMELINE=new Date().getTime() const TITLETIMELINE = [ {time:0,cb: (ctx)=>{ SCENE_DRAW=(ctx)=>{ ctx.globalAlpha=Math.min((new Date().getTime()-TITLESCREENTIMELINE)/3000,1) ctx.drawImage(IMAGE_SIG,canvas.width/2-243,canvas.height/2-22) } } }, {time:4000,cb: (ctx)=>{ SCENE_DRAW=(ctx)=>{ ctx.globalAlpha=Math.max( (7000-(new Date().getTime()-TITLESCREENTIMELINE))/3000 ,0) ctx.drawImage(IMAGE_SIG,canvas.width/2-243,canvas.height/2-22) } } }, {time:4000,cb: (ctx)=>{ currentSound.src="Super 8 Old Movie Projector - Gaming Sound Effect.mp3" currentSound.play() } }, {time:7000,cb: (ctx)=>{ SCENE_DRAW=(ctx)=>{ SCENEALPHA=0 if (Math.random()<=0.02) { ctx.globalAlpha=0.5 ctx.drawImage(IMAGE_TITLE,canvas.width/2+Math.random()*640260,canvas.height/2+Math.random()*64-70) } else { ctx.globalAlpha=0.75+Math.random()*0.25 ctx.drawImage(IMAGE_TITLE,canvas.width/2-260,canvas.height/2-70) } } } }, {time:15000,cb: (ctx)=>{ currentSound.src="Shostakovich_ Symphony No. 9.mp3" currentSound.loop=true currentSound.play() SCENE_DRAW=(ctx)=>{ ctx.globalAlpha=0.9 ctx.drawImage(IMAGE_TITLE,canvas.width/2-260,canvas.height/2-70) ctx.font="bold 48px 'Zilla Slab', serif" ctx.fillStyle="black" ctx.strokeStyle="white" ctx.textAlign = "center" ctx.fillText("- Click to Play -",canvas.width/2,canvas.height*0.9) ctx.strokeText("- Click to Play -",canvas.width/2,canvas.height*0.9) } } }, ] var SCENEBACKGROUND = "black" var SCENEALPHA = 1.0 var CURRENTTIMELINE = [] var SCENE_DRAW = ()=>{} var FIRSTBOOT = true const WAITING = 0; const RUNNING = 1; const REVIEWING = 2; const TESTING = 3; const FINISH = 4; const PAUSED = 5; const MAINMENU = 6; const TITLE = 7; const STARTUP = 8; const INFO = 9; var WAITINGTOFINISH=false const ONE_TEST = -1; const BLANK_TEST = -2; const NORMAL_TEST = 0; const EVEN_LENGTH_TEST = 1; //Only generate even length tapes. const ODD_LENGTH_TEST = 3; //Only generates odd length tapes. const BINARY_TEST = 4; //Generates valid binary numbers (never has a trailing R) const BSTAGE2_TEST = 2; const TWO_NUMBERS_TEST = 5; //Generates two binary numbers separated by a green dot. const SUBSTRING_TEST = 6; //Generates two yellow dots to indicate a substring to cut out. const UPPERCASE_CHARACTER_TEST = 7; //Generates uppercase letters separated by yellow. const TWO_UNEQUAL_NUMBERS_TEST = 8; //Two unequal numbers will be generated separated by a green. const NONE = 0; const BINARY = 1; const STRING = 2; var ISTESTING = false; var MOUSEOVERTIME = -1 const UP = 0; const RIGHT = 1; const DOWN = 2; const LEFT = 3; const RED = "R"; const BLUE = "B"; const GREEN = "G"; const YELLOW = "Y"; const PURPLE = "P"; const PINK = "p"; const BLACK = "b"; const GRAY = "g"; const ALIVE = 0; const DEAD = 1; const DONE = 2; var BOT_X = -1 var BOT_Y = -1 var BOT_DIR = RIGHT var BOT_STATE = ALIVE var BOT_TAPE = "RB" var GHOST_BOT_TAPE = "RB" var BOT_START_TAPE = BOT_TAPE var BOT_QUEUE = [] var DELETEMODE = false var DRAGGING = false var MOUSEDOWN = false var LAST_MOUSE_X=0; var LAST_MOUSE_Y=0; var DRAG_X = -1 var DRAG_Y = -1 var BOT_DID_NOT_REACH_EXIT = false var TOOLTIPDISPLAYED = undefined var MOVEMODE = false var STARTDRAG = undefined var TEST_RANDOM_TAPE = "" var MESSAGETIMER = -1 var EXPECTED = true //True means the bot was supposed to accepted, false means the bot was supposed to be rejected. var RESULT = true //True means you won. False means you lost. var TESTSTEPS = 0 //How long it takes the tests to run. var BOT_PREVX = BOT_X var BOT_PREVY = BOT_Y var LASTPOSITIONUPDATE = 0 var BRIDGEDBELT = false var ALLOWEDTOCLICK = true var MOBILE = false var BELTDOWN = {type:"BELT",direction:DOWN/*,direction2 - defines a secondary direction. For two belts at once.*/} var BELTRIGHT = {type:"BELT",direction:RIGHT} var BELTUP = {type:"BELT",direction:UP} var BELTLEFT = {type:"BELT",direction:LEFT} var BRANCHDOWN = {type:"BRANCH",direction:DOWN,color1:RED,color2:BLUE} //color 1 points clockwise(right), color 2 points counter-clockwise(left) var BRANCHLEFT = {type:"BRANCH",direction:LEFT,color1:RED,color2:BLUE} var BRANCHRIGHT = {type:"BRANCH",direction:RIGHT,color1:RED,color2:BLUE} var BRANCHUP = {type:"BRANCH",direction:UP,color1:RED,color2:BLUE} var WRITERDOWN = {type:"WRITER",direction:DOWN,color1:RED/*overwrite - if turned on, the writer overwrites the current tape position instead of appending.*/} var WRITERLEFT = {type:"WRITER",direction:LEFT,color1:RED} var WRITERRIGHT = {type:"WRITER",direction:RIGHT,color1:RED} var WRITERUP = {type:"WRITER",direction:UP,color1:RED} var DEF_BRANCHUP_RB = {img:ID_BRANCH,color1:RED,color2:BLUE,type:"BRANCH"} var DEF_BRANCHUP_BR = {img:ID_BRANCH,color1:BLUE,color2:RED,type:"BRANCH"} var DEF_BRANCHUP_GY = {img:ID_BRANCH,color1:GREEN,color2:YELLOW,type:"BRANCH"} var DEF_BRANCHUP_YG = {img:ID_BRANCH,color1:YELLOW,color2:GREEN,type:"BRANCH"} var DEF_BRANCHUP_PPI = {img:ID_BRANCH,color1:PURPLE,color2:PINK,type:"BRANCH"} var DEF_BRANCHUP_PIP = {img:ID_BRANCH,color1:PINK,color2:PURPLE,type:"BRANCH"} var DEF_BRANCHUP_BLGR = {img:ID_BRANCH,color1:BLACK,color2:GRAY,type:"BRANCH"} var DEF_BRANCHUP_GRBL = {img:ID_BRANCH,color1:GRAY,color2:BLACK,type:"BRANCH"} var DEF_WRITERRIGHT_R = {img:ID_WRITER,color1:RED,type:"WRITER"} var DEF_WRITERRIGHT_B = {img:ID_WRITER,color1:BLUE,type:"WRITER"} var DEF_WRITERRIGHT_G = {img:ID_WRITER,color1:GREEN,type:"WRITER"} var DEF_WRITERRIGHT_Y = {img:ID_WRITER,color1:YELLOW,type:"WRITER"} var DEF_WRITERRIGHT_P = {img:ID_WRITER,color1:PURPLE,type:"WRITER"} var DEF_WRITERRIGHT_PI = {img:ID_WRITER,color1:PINK,type:"WRITER"} var DEF_WRITERRIGHT_BL = {img:ID_WRITER,color1:BLACK,type:"WRITER"} var DEF_WRITERRIGHT_GR = {img:ID_WRITER,color1:GRAY,type:"WRITER"} var DEF_CONVEYOR = {img:ID_CONVEYOR,type:"BELT"} var GRID_W = 32 var GRID_H = 32 var GRID_X = 20 var GRID_Y = 20 var ITEM_DIRECTION=RIGHT; var dashOffset=0 var SUBMENU = { visible:false, width:0, height:0, buttons:[] } var BUTTON_SELECTED = undefined var ITEM_SELECTED = undefined var KEY_ROTATION_RIGHT = ["D","L","d","l","6","ArrowRight"] var KEY_ROTATION_LEFT = ["A","G","a","h","4","ArrowLeft"] var KEY_ROTATION_UP = ["W","K","w","k","8","ArrowUp"] var KEY_ROTATION_DOWN = ["S","J","s","j","2","ArrowDown"] var KEY_BRIDGED_BELT = ["Shift"] var CONVEYOR_BUILD_BUTTON = {img:ID_CONVEYOR,x:-1,y:-1,w:-1,h:-1,lastselected:DEF_CONVEYOR,tooltip:"Conveyor Belt\nMoves bots in a direction.\n\nHold (Shift) to bridge over other belts",mobileTooltip:"Conveyor Belt\nMoves bots in a direction.\n\nHold button down to toggle bridge mode.\nUsed to bridge over other belts"} var BRANCH_BUILD_BUTTON = {img:ID_BRANCH,x:-1,y:-1,w:-1,h:-1,submenu_buttons:[DEF_BRANCHUP_RB,DEF_BRANCHUP_BR,DEF_BRANCHUP_GY,DEF_BRANCHUP_YG,DEF_BRANCHUP_PPI,DEF_BRANCHUP_PIP,DEF_BRANCHUP_BLGR,DEF_BRANCHUP_GRBL],lastselected:undefined,default:DEF_BRANCHUP_RB,tooltip:"Branch\nReads next tape and moves bot in the\nmatching color direction.\n\nMoves bot forward and does not consume\ntape if no match found."} var WRITER_BUILD_BUTTON = {img:ID_WRITER,x:-1,y:-1,w:-1,h:-1,submenu_buttons:[DEF_WRITERRIGHT_R,DEF_WRITERRIGHT_B,DEF_WRITERRIGHT_G,DEF_WRITERRIGHT_Y,DEF_WRITERRIGHT_P,DEF_WRITERRIGHT_PI,DEF_WRITERRIGHT_BL,DEF_WRITERRIGHT_GR],lastselected:undefined,default:DEF_WRITERRIGHT_R,tooltip:"Writer\nWrites a color to the end of the tape."} var ROTATE_CLOCKWISE_BUTTON = {img:ID_ROTATE_CLOCKWISE,x:-1,y:-1,w:-1,h:-1,cb:rotateClockwise,tooltip:"Rotate selection clockwise.\nYou can use WASD, HJKL, Arrow Keys\nto rotate quickly.",mobileTooltip:"Rotate selection clockwise." } var ROTATE_COUNTERCLOCKWISE_BUTTON = {img:ID_ROTATE_COUNTERCLOCKWISE,x:-1,y:-1,w:-1,h:-1,cb:rotateCounterClockwise,tooltip:"Rotate selection counter-clockwise.\nYou can use WASD, HJKL, Arrow Keys\nto rotate quickly.",mobileTooltip:"Rotate selection counter-clockwise." } var PLAY_BUTTON = {img:ID_PLAY,x:-1,y:-1,w:-1,h:-1,cb:runGameSimulation,tooltip:"Test your great machine.\n\nConvert all the bots!" } var PAUSE_BUTTON = {img:ID_PAUSE,x:-1,y:-1,w:-1,h:-1,cb:pauseGameSimulation,tooltip:"Pause the great machine." } var RESET_BUTTON = {img:ID_RESET,x:-1,y:-1,w:-1,h:-1,cb:resetSimulation,tooltip:"Reset the great machine." } var DELETE_BUTTON = {img:ID_DELETE,x:-1,y:-1,w:-1,h:-1,cb:toggleDeleteMode,tooltip:"Remove a piece on the field." } var HOME_BUTTON = {img:ID_HOME,x:-1,y:-1,w:-1,h:-1,cb:goHome,tooltip:"Go back to the level selection menu.\n\nYour progress will be saved automatically." } var MOVE_BUTTON = {img:ID_MOVE_BUTTON,x:2,y:2,w:32,h:32,cb:toggleMoveMode,tooltip:"Scroll the view area." } var AUDIO_BUTTON = { bounds:{x:4,y:4,w:40,h:40}, cb:()=>{ currentSound.muted=!currentSound.muted } } var INFO_BUTTON = { bounds:{x:4,y:44,w:40,h:40}, cb:()=>{ gameState=INFO } } var INFO_HOME_BUTTON = { bounds:{x:4,y:4,w:40,h:40}, cb:()=>{ goHome(false) } } var CREDITS=">CREDITS\n\n" +"First, thanks to Javidx9 for putting on a wonderful coding jam!\n" +"I had a lot of fun working on this game.\n" +"\n" +"This game is heavily inspired by Manufactoria, and thus\n" +"it is appropriate to give credit to the developer of Manufactoria\n" +">(@pleasingfungus) Nicholas Feinberg - Creator of Manufactoria\n" +"\n" +">Music is from an excerpt from the Shostakovich - Symphony No.9 Performance\n" +"\n" +"All graphics were designed by myself.\n" +">(@sigonasr2) Joshua Sigona - Creator of The Great Conversion\n" +"\n" +"Font licenses are included in attached License files." var MENU = { visible:false, buttons:[CONVEYOR_BUILD_BUTTON,BRANCH_BUILD_BUTTON,WRITER_BUILD_BUTTON,ROTATE_COUNTERCLOCKWISE_BUTTON,ROTATE_CLOCKWISE_BUTTON,DELETE_BUTTON,PLAY_BUTTON,RESET_BUTTON,HOME_BUTTON] } function toggleMoveMode() { setMoveMode(!MOVEMODE) } function saveLevelData() { completedStages[gameStage.name].data=deepCopy(gameGrid) localStorage.setItem("game",JSON.stringify(completedStages)) } function goHome(save=true) { if (save) { saveLevelData() } MENU.visible=false BRIDGEDBELT=false setMoveMode(false) BOT_TAPE="" GHOST_BOT_TAPE="" GRID_X=20 GRID_Y=20 ITEM_SELECTED=undefined MOUSEDOWN=false ALLOWEDTOCLICK=false endARound() for (var button of MENU.buttons) { if (button.submenu_buttons) { button.lastselected=button.default } } gameGrid=[] gameState=MAINMENU } function runGameSimulation(){ if (gameState!==PAUSED) { gameState=TESTING ISTESTING=true BOT_PREVX=-100 BOT_PREVY=-100 BOT_TAPE="" GHOST_BOT_TAPE="" ITEM_SELECTED=undefined BOT_START_TAPE=undefined if (gameSpeed===-1) { gameSpeed=1000/1 } setMoveMode(false) generateBotQueue() ISTESTING=false if (BOT_QUEUE.length>0) { BOT_TAPE=BOT_QUEUE[0] } else { BOT_TAPE=TEST_RANDOM_TAPE EXPECTED=gameStage.accept(BOT_TAPE) } BOT_START_TAPE=BOT_TAPE BOT_X=gameStage.start.x BOT_Y=gameStage.start.y BOT_PREVX=BOT_X BOT_PREVY=BOT_Y BOT_STATE=ALIVE gameState=WAITING BOT_DIR=RIGHT gameState=RUNNING gameState=RUNNING MESSAGETIMER=new Date().getTime()+3000 for (var i=0;itrue} var STAGE2 = { name:"Blue Blue", objective:"Accept only bots that have all B markers.", level:createGrid(5,5,4,2), start:{x:0,y:2}, accept:(tape)=>{ if (tape.length===0) { return false; } for (var i=0;i{ var reds=0; var blues=0; for (var i=0;i{ var finalTape="" for (var i=0;i{ var finalTape="" for (var i=0;i{ var finalTape="" for (var i=0;i{ var finalTape = tape.substring(0,tape.length-1) finalTape+=YELLOW return finalTape }, //generator:BLANK_TEST, //display:STRING } var BSTAGE5 = { name:"Back and Forth", objective:`Accept robots that have alternating colors.`, level:createGrid(7,7,6,6), start:{x:0,y:0}, accept:(tape)=>{ var finalTape="" var lastCol="" for (var i=0;i{ return {string:"#olcCodeJam2020"} }, generator:BLANK_TEST, display:STRING } var BSTAGE7 = { name:"End-to-End", objective:`Accept robots that have the same first and last colors on their tapes. Ex: ${RED}${BLUE}${RED} is accepted, ${RED}${BLUE}${BLUE} is not.`, level:createGrid(7,7,6,6), start:{x:0,y:0}, accept:(tape)=>{ var first="" var last="" if (tape.length>0) { first=tape[0] last=tape[tape.length-1] } return first===last }, //generator:BLANK_TEST, //display:STRING } var BSTAGE8 = { name:"Greatest Count", objective:`Given an odd number of ${BLUE}s and ${RED}s, determine if there are more ${BLUE}s or more ${RED}s and only return those markers. Ex: ${BLUE}${BLUE}${BLUE}${RED}${RED} should output as ${BLUE}${BLUE}${BLUE} (3>2)`, level:createGrid(9,9,8,8), start:{x:0,y:0}, accept:(tape)=>{ var blues = "" var reds = "" for (var i=0;ireds.length)?blues:reds }, generator:ODD_LENGTH_TEST, //display:STRING } var ISTAGE2 = { name:"Oddly Even", objective:`Accepts bots that have an even length of tape.`, level:createGrid(7,7,6,6), start:{x:0,y:0}, accept:(tape)=>{ return tape.length%2===0 }, //generator:BLANK_TEST, //display:STRING } var ISTAGE3 = { name:"Add One", objective:`Given a tape representing binary (${RED}=0 ${BLUE}=1), add 1 to the number. Ex: ${BLUE}${RED}${BLUE}(5) should output ${BLUE}${BLUE}${RED}(6)`, level:createGrid(9,9,8,8), start:{x:0,y:0}, accept:(tape)=>{ var answer = tapeToBinary(tape)+1 return binaryToTape(answer) }, generator:BINARY_TEST, display:BINARY } var ISTAGE4 = { name:"String Length", objective:`Return the number of ${RED}s from a bot as a binary number (${RED}=0 ${BLUE}=1). Ex: ${RED}${RED}${RED}${BLUE}${RED} should output as ${BLUE}${RED}${RED} (4 ${RED}s)`, level:createGrid(13,13,12,12), start:{x:0,y:0}, accept:(tape)=>{ var reds = 0 for (var i=0;i{ var blues = "" var reds = "" for (var i=0;i{ for (var i=0;i{ var temp = "" var number1 = -1 var number2 = -1 for (var i=0;i{ var found = "" var start = false for (var i=0;i ${BLUE}${BLUE}${RED}${BLUE}${BLUE}${RED}${RED}${RED}`, level:createGrid(11,11,10,10), start:{x:0,y:0}, accept:(tape)=>{ return tape.split("").reverse().join("") }, //generator:SUBSTRING_TEST, //display:BINARY } var ASTAGE1 = { name:"Right in the Center", objective:`Given an even length tape, add a ${YELLOW} marker in the middle of it.`, level:createGrid(10,10,9,9), start:{x:0,y:0}, accept:(tape)=>{ return tape.substring(0,tape.length/2)+"Y"+tape.substring(tape.length/2,tape.length) }, generator:EVEN_LENGTH_TEST, //display:STRING } var ASTAGE2 = { name:"toLowerCase", objective:`Given a string of uppercase ASCII binary characters (${RED}=0 ${BLUE}=1) separated by ${YELLOW}, convert all uppercase to lowercase. Ex: "HI"= ${BLUE}${RED}${RED}${BLUE}${RED}${RED}${RED}Y${BLUE}${RED}${RED}${BLUE}${RED}${RED}${BLUE} output as "hi"= ${BLUE}${BLUE}${RED}${BLUE}${RED}${RED}${RED}Y${BLUE}${BLUE}${RED}${BLUE}${RED}${RED}${BLUE}`, level:createGrid(25,25,24,24), start:{x:0,y:0}, accept:(tape)=>{ var chars=tape.split(YELLOW) var finalString = "" for (var i=0;i{ var temp = "" var number1 = -1 var number2 = -1 for (var i=0;i ${RED}${BLUE}${GREEN}${BLUE}${BLUE}${RED}${BLUE}${BLUE}${BLUE}${BLUE}${GREEN} -> ${RED}${BLUE}${GREEN}${BLUE}${RED}${RED}${BLUE}${BLUE}${BLUE}${BLUE}${GREEN} -> ${RED}${BLUE}${GREEN}${BLUE}${BLUE}${GREEN}${RED}${BLUE}${BLUE}${BLUE}${BLUE}${GREEN}`, level:createGrid(13,13,6,6), start:{x:0,y:6}, accept:(tape)=>{ var first=false; var newString="" var found=true var limit=2 while (found&&limit-->0) { found=false first=false //Red->Blue-Green for (var i=0;i RR var last = "" for (var i=0;i24)`, level:createGrid(17,17,16,16), start:{x:0,y:0}, accept:(tape)=>{ var temp = "" var number1 = -1 var number2 = -1 for (var i=0;inumber2)?number1:number2) }, generator:TWO_UNEQUAL_NUMBERS_TEST, display:BINARY } var TUTORIAL1 = { name:"Conveyors!", objective:"To convert your robots, you must send them from the entrance to the exit. Select a belt and send robots to where they truly belong. Press the Play button to test your machine!", level:createGrid(5,5,4,2), start:{x:0,y:2}, locked:[BRANCH_BUILD_BUTTON,WRITER_BUILD_BUTTON], tutorial:true, accept:(tape)=>{ return true; }, //display:BINARY } var TUTORIAL2 = { name:"Branches", objective:"We have to make sure we are sending robots that meet our needs! Use the branch to filter out robots that start with a R signal. Send only those to the exit!", level:createGrid(5,5,4,2), start:{x:0,y:2}, locked:[WRITER_BUILD_BUTTON], tutorial:true, accept:(tape)=>{ if (tape[0]===RED) { return true; } else { return false; } }, //generator:BLANK_TEST, //display:STRING } var TUTORIAL3 = { name:"Writers", objective:"CONVERSION! It's time to convert robots to what they should be. Add 3 B signals to every bot that comes through. We shall convert them all!", level:createGrid(5,5,4,2), start:{x:0,y:2}, tutorial:true, accept:(tape)=>{ return tape+BLUE+BLUE+BLUE } } var TUTORIAL4 = { name:"More Colors", objective:"You may be required to use different colors, either for your purposes or mine. For this robot cycle, convert all B and R signals to Y signals.", level:createGrid(5,5,4,2), start:{x:0,y:2}, accept:(tape)=>{ var newTape = "" for (var i=0;i{ return true } } var TUTORIALMENU={ title:"Introduction to Conversion", levels:[TUTORIAL1,TUTORIAL2,TUTORIAL3,TUTORIAL4], cols:2, width:568*0.8 } var EASYMENU={ title:"Beginner Stages", levels:[BSTAGE1,BSTAGE2,BSTAGE3,BSTAGE4,BSTAGE5,BSTAGE6,BSTAGE7,BSTAGE8], cols:1, width:568*0.33 } var MEDIUMMENU={ title:"Intermediate Stages", levels:[ISTAGE1,ISTAGE2,ISTAGE3,ISTAGE4,ISTAGE5,ISTAGE6,ISTAGE7,ISTAGE8], cols:1, width:568*0.33 } var HARDMENU={ title:"Advanced Stages", levels:[ASTAGE1,ASTAGE2,ASTAGE3,ASTAGE4,ASTAGE5], cols:1, width:568*0.33 } var SANDBOXMENU={ title:"Sandbox", levels:[SANDBOX], cols:1, width:568*0.33 } var gameGrid = [] var completedStages = undefined //Example completed structure. function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires="+d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";"; } function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } function createGrid(width=5,height=5,exitX=4,exitY=2) { var grid = [] for (var i=0;i0) { var mask = numb&7 switch (mask) { case 0:{ newTape+=GREEN }break; case 1:{ newTape+=YELLOW }break; case 2:{ newTape+=PURPLE }break; case 3:{ newTape+=PINK }break; case 4:{ newTape+=BLACK }break; case 5:{ newTape+=GRAY }break; default:{ newTape+=GREEN } } numb=numb>>>3 } tests.push(newTape) } }break; case EVEN_LENGTH_TEST:{ while (tests.length<2000) { var newTape = binaryToTape(startingValue++) if (newTape.length%2===0) { tests.push(newTape) tests.push(newTape.split("").reverse().join("")) } } }break; case ODD_LENGTH_TEST:{ while (tests.length<2000) { var newTape = binaryToTape(startingValue++) if (newTape.length%2===1) { tests.push(newTape) tests.push(newTape.split("").reverse().join("")) } } }break; case BINARY_TEST:{ while (tests.length<2000) { var newTape = binaryToTape(startingValue++) if (newTape[newTape.length-1]!==RED) { tests.push(newTape) } } }break; case TWO_NUMBERS_TEST:{ while (tests.length<2000) { var newTape = binaryToTape(startingValue++) var newTape2 = binaryToTape(startingValue++) if (Math.random()<=0.5) { newTape2=newTape } if (newTape[newTape.length-1]!==RED&&newTape2[newTape2.length-1]!==RED) { tests.push(newTape+GREEN+newTape2) } } }break; case SUBSTRING_TEST:{ startingValue=1000 while (tests.length<2000) { var tape = binaryToTape(startingValue) var pos1 = Math.floor(Math.random()*(tape.length/2)) var pos2 = (tape.length/2)+Math.floor(Math.random()*(tape.length/2)) tests.push(tape.substring(0,pos1)+YELLOW+tape.substring(pos1,pos2)+YELLOW+tape.substring(pos2,tape.length-1)) startingValue+=141 } }break; case UPPERCASE_CHARACTER_TEST:{ while (tests.length<2000) { var length=Math.floor(Math.random()*15) var word = "" for (var i=0;i=3) { break; } } if (RESULT) { completedStages[gameStage.name].complete=true completedStages[gameStage.name].score=TESTSTEPS } } } function getSimulatedBotResult(tape) { var simulatedBoard = deepCopy(gameGrid) resetBot(gameStage.start.x,gameStage.start.y,TESTING,tape) const MAX_ITERATIONS=10000 var iterations=0 while (iterations0) { var mask = remainingVal&1 if (mask===1) { tape=tape+BLUE } else { tape=tape+RED } remainingVal=remainingVal>>>1 } return tape; } function setNextSquare(offsetX,offsetY) { if (gameGrid[BOT_Y+offsetY]!==undefined) { var nextSquare = gameGrid[BOT_Y+offsetY][BOT_X+offsetX]; if (!ISTESTING) { BOT_PREVX=BOT_X BOT_PREVY=BOT_Y } LASTPOSITIONUPDATE=new Date().getTime() BOT_X+=offsetX BOT_Y+=offsetY return nextSquare } else { gameState = REVIEWING BOT_STATE = DEAD endARound() return undefined } } function runBot(testing) { if ((lastGameUpdate=button.x && getMousePos(e).x<=button.x+button.w && getMousePos(e).y>=button.y && getMousePos(e).y<=button.y+button.h) } function setMoveMode(mode) { MOVEMODE=mode if (MOVEMODE) { document.body.style.cursor="move" } else { if (DELETEMODE) { document.body.style.cursor="url('delete_cursor.png') 8 8,auto" } else { document.body.style.cursor="url('cursor.png') 8 8,auto" } } } function clickEvent(e) { if (window.TouchEvent && e instanceof TouchEvent) { MOBILE=true e.preventDefault() } else { MOBILE=false } var mousepos = getMousePos(e) LAST_MOUSE_X=mousepos.x LAST_MOUSE_Y=mousepos.y MOUSEOVERTIME=-1 MOUSEDOWN=true if (gameState===STARTUP) { currentSound.play() setupTitleScreen() MOUSEDOWN=false } if (gameState===TITLE) { MOUSEDOWN=false if (new Date().getTime()-TITLESCREENTIMELINE>=15000) { if (FIRSTBOOT) { loadStage(TUTORIAL1) gameState=WAITING } else { gameState=MAINMENU } } } if (gameState===MAINMENU) { if (MouseOverBounds(AUDIO_BUTTON.bounds)) {AUDIO_BUTTON.cb();return;} if (MouseOverBounds(INFO_BUTTON.bounds)) {INFO_BUTTON.cb();return;} } if (gameState===INFO) { if (MouseOverBounds(INFO_HOME_BUTTON.bounds)) {INFO_HOME_BUTTON.cb();return;} } if (MENU.visible) { for (var button of MENU.buttons) { if (ButtonIsUnlocked(button)) { if (mouseOverButton(canvas,e,button)) { if (MOBILE&&button===CONVEYOR_BUILD_BUTTON) { setTimeout(()=>{ if (MOUSEDOWN&& LAST_MOUSE_X>=CONVEYOR_BUILD_BUTTON.x&& LAST_MOUSE_X<=CONVEYOR_BUILD_BUTTON.x+CONVEYOR_BUILD_BUTTON.w&& LAST_MOUSE_Y>=CONVEYOR_BUILD_BUTTON.y&& LAST_MOUSE_Y<=CONVEYOR_BUILD_BUTTON.y+CONVEYOR_BUILD_BUTTON.h) { BRIDGEDBELT=!BRIDGEDBELT } },500) } if (button.cb!==undefined) { button.cb() return; } else { DELETEMODE=false document.body.style.cursor="url('cursor.png') 8 8,auto" if (button.submenu_buttons&&button.submenu_buttons.length>0) { BUTTON_SELECTED=button; SUBMENU.visible=true; SUBMENU.buttons=[] var index = 0; for (var button2 of BUTTON_SELECTED.submenu_buttons) { var buttonX = ((index%3)*48)+16 var buttonY = canvas.height*0.8-(Math.floor(index/3)*48)-40 SUBMENU.buttons.push({def:button2,img:button2.img,x:buttonX,y:buttonY,w:32,h:32}) index++; } } if (ITEM_SELECTED===button.lastselected&&!MOVEMODE) { if (!MOBILE) { ITEM_SELECTED=undefined } } else { ITEM_SELECTED=button.lastselected } setMoveMode(false) return } } } } if (mouseOverButton(canvas,e,MOVE_BUTTON)&&!DELETEMODE) { MOVE_BUTTON.cb() return } } if (gridModeIsAvailable()&&ITEM_SELECTED===undefined) { setMoveMode(true) } if (!MOBILE&&e.button!==0) { e.preventDefault() if (gridModeIsAvailable()) { setMoveMode(true) } else { setMoveMode(false) } } if (MOVEMODE) { //Cannot handle building until out of move mode. STARTDRAG=getMousePos(e) return; } if ((ITEM_SELECTED!==undefined||DELETEMODE)&&!MOVEMODE) { var clickCoords = getGridCoords(getMousePos(e)) if (coordsInBounds(clickCoords)) { modifyBoard(clickCoords,ITEM_SELECTED) DRAGGING = true DRAG_X = clickCoords.x DRAG_Y = clickCoords.y } } } function coordsInBounds(coords) { return coords.x>=0&&coords.y>=0&&coords.y0) { var currentEvent = CURRENTTIMELINE[0] if (currentEvent.time") { string=split[i].substring(1) ctx.font="bold 16px 'Zilla Slab', serif" } else { ctx.font="16px 'Zilla Slab', serif" } ctx.textAlign = "center" ctx.fillStyle="black" ctx.fillText(string,canvas.width/2,i*20+36) } }break; case STARTUP:{ ctx.fillStyle="black" ctx.fillRect(0,0,canvas.width,canvas.height) ctx.font="16px 'Zilla Slab', serif" ctx.fillStyle="white" ctx.textAlign = "left" ctx.fillText("Booting...",0,24) ctx.fillText("Click to begin",0,48) }break; default:{ renderGame(ctx) if (ITEM_SELECTED&&!MOBILE) { RenderIcon(LAST_MOUSE_X-16,LAST_MOUSE_Y-16,ctx,ITEM_SELECTED,ITEM_DIRECTION) } //drawImage(0,0,ID_CONVEYOR,ctx,0) //drawImage(LAST_MOUSE_X,LAST_MOUSE_Y,ID_ARROW,ctx,0) RenderSubmenu(ctx) RenderMenu(ctx) RenderGameInfo(ctx) } } } } function RenderSpeedbar(x,y,w,ctx) { var gradient = ctx.createLinearGradient(x, 32, x+w, 32); gradient.addColorStop(0,"rgb(105, 55, 51)") gradient.addColorStop(1,"rgb(54, 77, 255)") ctx.lineWidth = 6; ctx.lineCap = "round" ctx.strokeStyle="white" ctx.setLineDash([]) ctx.beginPath() ctx.moveTo(x,y) ctx.lineTo(x+w,y) ctx.stroke(); ctx.lineWidth = 4; ctx.lineCap = "round" ctx.strokeStyle=gradient ctx.setLineDash([]) ctx.beginPath() ctx.moveTo(x,y) ctx.lineTo(x+w,y) ctx.stroke(); ctx.lineWidth = 2; ctx.lineCap = "round" ctx.strokeStyle="yellow" ctx.setLineDash([]) var cursorX = 0 switch (gameSpeed) { case 1000/1:{ cursorX=8; }break; case 1000/2:{ cursorX=16; }break; case 1000/3:{ cursorX=24; }break; case 1000/4:{ cursorX=32; }break; case 1000/6:{ cursorX=40; }break; case 1000/8:{ cursorX=48; }break; case 1000/16:{ cursorX=56; }break; case 1000/32:{ cursorX=64; }break; } if (MOUSEDOWN && LAST_MOUSE_X>=x&&LAST_MOUSE_X<=x+w && LAST_MOUSE_Y>=y-6&&LAST_MOUSE_Y<=y+6) { var mouseCursorPos = Math.round((LAST_MOUSE_X-x)/8)*8 switch (mouseCursorPos) { case 0:{ gameSpeed=-1; }break; case 8:{ gameSpeed=1000/1; }break; case 16:{ gameSpeed=1000/2; }break; case 24:{ gameSpeed=1000/3; }break; case 32:{ gameSpeed=1000/4; }break; case 40:{ gameSpeed=1000/6; }break; case 48:{ gameSpeed=1000/8; }break; case 56:{ gameSpeed=1000/16; }break; case 64:{ gameSpeed=1000/32; }break; } } ctx.beginPath() ctx.moveTo(x+cursorX,y-4) ctx.lineTo(x+cursorX,y+4) ctx.stroke(); } function DrawWrapText(text,x,y,w,fontHeight,ctx,topToBottom=false/*Set to true to draw from the position downward. Defaults to drawing upwards.*/) { var arr = text.split(" ") var finalText = [] for (var i=0;iw) { finalText.push(arr[i]) } else { if (finalText.length===0) { finalText.push(arr[i]) } else { finalText[finalText.length-1]+=" "+arr[i] } } } for (var i=0;i=0;i--) { if (tape[i]===RED) { numb=numb<<1; numb=numb|0 } else if (tape[i]===BLUE) { numb=numb<<1; numb=numb|1 } } return numb } function RenderTape(x,y,width,ctx,tape) { var xOffset=width-24 var yOffset=0 var ySpacingMult=1 var ySpacing=12 if (tape.length>5*5) { ySpacingMult=(ySpacing*5)/(Math.ceil(tape.length/5)*ySpacing) } yOffset=ySpacing*ySpacingMult for (var i=5;i0) {if (HasRelativeConnection(grid.x-1,grid.y+0,RIGHT)){connections[LEFT]=true}} if (grid.x0) {if (HasRelativeConnection(grid.x+0,grid.y-1,DOWN)){connections[UP]=true}} if (grid.y=bounds.x&& LAST_MOUSE_X<=bounds.x+bounds.w&& LAST_MOUSE_Y>=bounds.y&& LAST_MOUSE_Y<=bounds.y+bounds.h) } function GetArrowImage(col) { switch (col) { case RED:{ return ID_ARROW_R }break; case BLUE:{ return ID_ARROW_B }break; case GREEN:{ return ID_ARROW_G }break; case YELLOW:{ return ID_ARROW_Y }break; case PURPLE:{ return ID_ARROW_P }break; case PINK:{ return ID_ARROW_PI }break; case BLACK:{ return ID_ARROW_BL }break; case GRAY:{ return ID_ARROW_GR }break; } } function GetDotImage(col) { switch (col) { case RED:{ return ID_DOT_R }break; case BLUE:{ return ID_DOT_B }break; case GREEN:{ return ID_DOT_G }break; case YELLOW:{ return ID_DOT_Y }break; case PURPLE:{ return ID_DOT_P }break; case PINK:{ return ID_DOT_PI }break; case BLACK:{ return ID_DOT_BL }break; case GRAY:{ return ID_DOT_GR }break; } } function RenderSubmenu(ctx) { if (SUBMENU.visible) { ctx.fillStyle="#76abab" var submenuRows = BUTTON_SELECTED.submenu_buttons.length/3 var submenuCols = 3 ctx.fillRect(0,canvas.height*0.8-submenuRows*48-16,submenuCols*48+16,submenuRows*48+16) var index = 0; for (var button of BUTTON_SELECTED.submenu_buttons) { if (SubmenuButtonIsUnlocked(button)) { var buttonX = ((index%3)*48)+16 var buttonY = canvas.height*0.8-(Math.floor(index/3)*48)-40 RenderIcon(buttonX,buttonY,ctx,button,ITEM_DIRECTION,(LAST_MOUSE_X>=buttonX&&LAST_MOUSE_X<=buttonX+32&&LAST_MOUSE_Y>=buttonY&&LAST_MOUSE_Y<=buttonY+32)?"#555555":"#b5b5b5") index++; } } } } function SubmenuButtonIsUnlocked(button) { return gameStage.tutorial===undefined||!gameStage.tutorial||button.color1===RED||button.color1===BLUE } function ButtonIsUnlocked(button) { if (button===HOME_BUTTON&&gameStage.tutorial&&!LevelIsBeat(gameStage.name)) { return false } return gameStage.locked===undefined||(!gameStage.locked.includes(button)) } function isMouseOverButton(button) { return LAST_MOUSE_X>=button.x&& LAST_MOUSE_X<=button.x+button.w&& LAST_MOUSE_Y>=button.y&& LAST_MOUSE_Y<=button.y+button.h } function DisplayTooltip(button,ctx) { //240x(20*lines) (16pt font) var tooltipSplit=[] if (MOBILE&&button.mobileTooltip!==undefined){ tooltipSplit=button.mobileTooltip.split("\n") } else { tooltipSplit=button.tooltip.split("\n") } var tooltipBounds = { x:Math.min(Math.max(LAST_MOUSE_X-120,0),canvas.width*0.75-240), y:LAST_MOUSE_Y-tooltipSplit.length*20-4, w:240, h:tooltipSplit.length*16+8 } ctx.globalAlpha=0.9 ctx.fillStyle="#20424a" ctx.fillRect(tooltipBounds.x,tooltipBounds.y,tooltipBounds.w,tooltipBounds.h) ctx.strokeStyle="white" ctx.lineWidth=1 ctx.setLineDash([]) ctx.strokeRect(tooltipBounds.x,tooltipBounds.y,tooltipBounds.w,tooltipBounds.h) ctx.fillStyle="white" ctx.textAlign = "left" for (var i=0;i=4000 || !MOBILE&&new Date().getTime()-MOUSEOVERTIME>=1000) { mouseOverButton = button } } if (button.lastselected) { RenderIcon(buttonX,buttonY,ctx,button.lastselected,(button.cb===undefined)?ITEM_DIRECTION:0,"#b5b5b5") } else { AddButton(button.img,buttonX,buttonY,ctx,button,(button.cb===undefined)?ITEM_DIRECTION:0) } } buttonX+=47 } if (mouseOverButton!==undefined) { DisplayTooltip(mouseOverButton,ctx) } if (!mousedOver) { MOUSEOVERTIME=-1 } if (gridModeIsAvailable()) { AddButton(MOVE_BUTTON.img,MOVE_BUTTON.x,MOVE_BUTTON.y,ctx,MOVE_BUTTON,MOVE_BUTTON.cb) } } } function gridModeIsAvailable() { return (gameGrid.length>5||(gameGrid.length>0&&gameGrid[0].length>5))&&!DELETEMODE } function AddButton(img,x,y,ctx,button,dir=0) { ctx.fillStyle="#b5b5b5" ctx.fillRect(x,y,32,32) if (img===ID_WRITER) { drawImage(x+16,y+16,img,ctx,dir*90-90) } else { drawImage(x+16,y+16,img,ctx,dir*90) } } function ConsumeTape() { BOT_TAPE=BOT_TAPE.substring(1) } function AppendTape(col) { BOT_TAPE+=col } function OverwriteTape(col) { BOT_TAPE=col+BOT_TAPE.substring(1) } function LeftOf(dir) { return (dir+1)%4 } function RightOf(dir) { if (dir===0) {dir=4} return (dir-1)%4 }