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 ; i < MENU . buttons . length ; i ++ ) {
if ( MENU . buttons [ i ] . img === ID _PLAY ) {
MENU . buttons [ i ] = PAUSE _BUTTON
break ;
}
}
}
gameState = RUNNING
for ( var i = 0 ; i < MENU . buttons . length ; i ++ ) {
if ( MENU . buttons [ i ] . img === ID _PLAY ) {
MENU . buttons [ i ] = PAUSE _BUTTON
break ;
}
}
}
function endARound ( ) {
for ( var i = 0 ; i < MENU . buttons . length ; i ++ ) {
if ( MENU . buttons [ i ] . img === ID _PAUSE ) {
MENU . buttons [ i ] = PLAY _BUTTON
break ;
}
}
}
function pauseGameSimulation ( ) {
if ( ! ISTESTING ) {
gameState = PAUSED
endARound ( )
}
}
function resetSimulation ( ) {
BOT _STATE = ALIVE
gameState = WAITING
BOT _X = gameStage . start . x
BOT _Y = gameStage . start . y
BOT _PREVX = - 100
BOT _PREVY = - 100
BOT _DIR = RIGHT
endARound ( )
}
function toggleDeleteMode ( ) {
DELETEMODE = ! DELETEMODE
if ( DELETEMODE ) {
ITEM _SELECTED = undefined
setMoveMode ( false )
document . body . style . cursor = "url('delete_cursor.png') 8 8,auto"
} else {
document . body . style . cursor = "url('cursor.png') 8 8,auto"
}
}
function rotateClockwise ( ) {
ITEM _DIRECTION = getClockwiseDirection ( ITEM _DIRECTION )
}
function rotateCounterClockwise ( ) {
ITEM _DIRECTION = getCounterClockwiseDirection ( ITEM _DIRECTION )
}
function getClockwiseDirection ( dir ) {
return ( dir + 1 ) % 4
}
function getCounterClockwiseDirection ( dir ) {
var newdir = dir ;
newdir = ( newdir - 1 ) ;
if ( newdir < 0 ) { newdir = 3 }
return newdir ;
}
var lastGameUpdate = 0 ;
var gameSpeed = 1000 / 1 ;
var gameState = WAITING ;
var gameStage = 0 ;
var LEVEL0 = [
[ { } , { } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] , ]
var LEVEL1 = [
[ { } , { } , { ... BELTDOWN } , { ... BELTLEFT } , { ... BELTLEFT } , ] ,
[ { } , { } , { } , { } , { ... BELTUP } , ] ,
[ { } , { ... BELTDOWN } , { } , { } , { ... BELTUP } , ] ,
[ { } , { ... BELTRIGHT } , { ... BELTRIGHT } , { ... BELTRIGHT } , { ... BELTUP } , ] ,
[ { } , { } , { } , { } , { } , ] , ]
var LEVEL2 = [
[ { } , { } , { } , { } , { } , ] ,
[ { } , { ... BELTRIGHT } , { } , { } , { } , ] , //BLUE
[ { } , { ... BRANCHRIGHT } , { } , { } , { } , ] ,
[ { } , { ... BELTRIGHT } , { } , { } , { } , ] , //RED
[ { } , { } , { } , { } , { } , ] , ]
var LEVEL3 = [
[ { } , { } , { } , { } , { } , ] ,
[ { } , { ... WRITERUP , overwrite : true } , { } , { } , { } , ] ,
[ { } , { ... WRITERDOWN } , { } , { } , { } , ] ,
[ { } , { ... BELTRIGHT } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] , ]
var LEVEL4 = [
[ { } , { } , { } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] ,
[ { } , { ... BELTRIGHT , direction2 : DOWN } , { } , { } , { } , ] ,
[ { } , { } , { ... BELTUP , direction2 : LEFT } , { } , { } , ] ,
[ { } , { } , { } , { } , { } , ] , ]
var STAGE1 = {
name : "The First Stage!" ,
objective : "Accept all bots" ,
level : createGrid ( 5 , 5 , 4 , 2 ) ,
start : { x : 0 , y : 2 } ,
accept : ( tape ) => true }
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 < tape . length ; i ++ ) {
if ( tape [ i ] !== "B" ) {
return false ;
}
}
return true ;
}
}
var STAGE3 = {
name : "Balance" ,
objective : "Accept bots with an equal number of R and B markers in total." ,
level : createGrid ( 15 , 15 , 14 , 14 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var reds = 0 ;
var blues = 0 ;
for ( var i = 0 ; i < tape . length ; i ++ ) {
switch ( tape [ i ] ) {
case RED : {
reds ++ ;
} break ;
case BLUE : {
blues ++ ;
} break ;
}
}
return blues === reds ;
}
}
var BSTAGE1 = {
name : "Bloody Red" ,
objective : ` Output just the ${ RED } markers from a bot. ` ,
level : createGrid ( 5 , 5 , 4 , 4 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var finalTape = ""
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === RED ) {
finalTape += RED
}
}
return finalTape
} ,
//generator:BLANK_TEST,
//display:STRING
}
var BSTAGE2 = {
name : "Crazy Conversion" ,
objective : ` Given that ${ YELLOW } = ${ RED } , ${ GREEN } = ${ BLUE } ${ BLUE } ${ RED } , ${ PURPLE } = ${ BLUE } ${ RED } , ${ PINK } = ${ RED } ${ RED } ${ RED } , ${ BLACK } = ${ RED } ${ BLUE } , ${ GRAY } = ${ BLUE } , convert all the bots to the correct ${ RED } and ${ BLUE } values. ` ,
level : createGrid ( 11 , 11 , 10 , 0 ) ,
start : { x : 0 , y : 10 } ,
accept : ( tape ) => {
var finalTape = ""
for ( var i = 0 ; i < tape . length ; i ++ ) {
switch ( tape [ i ] ) {
case YELLOW : {
finalTape += ` ${ RED } `
} break ;
case GREEN : {
finalTape += ` ${ BLUE } ${ BLUE } ${ RED } `
} break ;
case PURPLE : {
finalTape += ` ${ BLUE } ${ RED } `
} break ;
case PINK : {
finalTape += ` ${ RED } ${ RED } ${ RED } `
} break ;
case BLACK : {
finalTape += ` ${ RED } ${ BLUE } `
} break ;
case GRAY : {
finalTape += ` ${ BLUE } `
} break ;
}
}
return finalTape
} ,
generator : BSTAGE2 _TEST ,
//display:STRING
}
var BSTAGE3 = {
name : "Double Trouble" ,
objective : ` For every dot on the tape, double the output. Ex. ${ RED } ${ RED } ${ RED } ${ BLUE } should output ${ RED } ${ RED } ${ RED } ${ RED } ${ RED } ${ RED } ${ BLUE } ${ BLUE } . ` ,
level : createGrid ( 5 , 5 , 4 , 4 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var finalTape = ""
for ( var i = 0 ; i < tape . length ; i ++ ) {
finalTape += tape [ i ] + tape [ i ]
}
return finalTape
} ,
//generator:BLANK_TEST,
//display:STRING
}
var BSTAGE4 = {
name : "Back of the line" ,
objective : ` Replace the last marker on the tape with a ${ YELLOW } marker. ` ,
level : createGrid ( 5 , 5 , 4 , 4 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
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 < tape . length ; i ++ ) {
if ( tape [ i ] === lastCol ) {
return false
} else {
lastCol = tape [ i ]
}
}
return true
} ,
//generator:BLANK_TEST,
//display:STRING
}
var BSTAGE6 = {
name : "#olcCodeJam2020" ,
objective : ` Output the string "#olcCodeJam2020", using Binary ASCII characters ( ${ RED } =0 ${ BLUE } =1) and using ${ YELLOW } markers as a separator. Ex: "A"=65= ${ BLUE } ${ RED } ${ RED } ${ RED } ${ RED } ${ RED } ${ BLUE } , "b"=98= ${ RED } ${ BLUE } ${ RED } ${ RED } ${ RED } ${ BLUE } ${ BLUE } ` ,
level : createGrid ( 12 , 12 , 11 , 11 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
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 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === BLUE ) {
blues += BLUE
} else
if ( tape [ i ] === RED ) {
reds += RED
}
}
return ( blues . length > reds . 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 < tape . length ; i ++ ) {
if ( tape [ i ] === RED ) {
reds ++
}
}
return binaryToTape ( reds )
} ,
//generator:BINARY_TEST,
display : BINARY
}
var ISTAGE1 = {
name : "Blue In Blue Out" ,
objective : ` Output the tape with all the ${ BLUE } s moved to the front. ` ,
level : createGrid ( 7 , 7 , 6 , 6 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var blues = ""
var reds = ""
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === BLUE ) {
blues += BLUE
} else
if ( tape [ i ] === RED ) {
reds += RED
}
}
return blues + reds
} ,
//generator:BINARY_TEST,
//display:BINARY
}
var ISTAGE5 = {
name : "NoLemonsNoMelon" ,
objective : ` Accept bots that are a palindrome. Ex: ${ BLUE } ${ RED } ${ RED } ${ BLUE } is a palindrome, ${ BLUE } ${ RED } ${ BLUE } is a palindrome, ${ BLUE } ${ RED } ${ BLUE } ${ RED } is not a palindrome. ` ,
level : createGrid ( 7 , 7 , 6 , 6 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
for ( var i = 0 ; i < tape . length / 2 ; i ++ ) {
if ( tape [ i ] !== tape [ tape . length - 1 - i ] ) {
return false ;
}
}
return true ;
} ,
//generator:ODD_LENGTH_TEST,
//display:BINARY
}
var ISTAGE6 = {
name : "Is Equal To" ,
objective : ` Given 2 binary numbers ( ${ RED } =0 ${ BLUE } =1) separated by a ${ GREEN } marker, accept bots where the 2 numbers are equal. Ex. ${ RED } ${ BLUE } ${ RED } ${ GREEN } ${ RED } ${ BLUE } ${ RED } (2/2) should be accepted while ${ BLUE } ${ RED } ${ GREEN } ${ RED } ${ BLUE } (2/1) should not be. ` ,
level : createGrid ( 11 , 11 , 10 , 10 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var temp = ""
var number1 = - 1
var number2 = - 1
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === GREEN ) {
if ( number1 === - 1 ) {
number1 = tapeToBinary ( temp )
}
temp = ""
} else {
temp += tape [ i ]
}
}
number2 = tapeToBinary ( temp )
return number1 === number2
} ,
generator : TWO _NUMBERS _TEST ,
display : BINARY
}
var ISTAGE7 = {
name : "Substring" ,
objective : ` Given a tape with 2 ${ YELLOW } markers, return all the contents between the 2 ${ YELLOW } markers. Ex: ${ BLUE } ${ RED } ${ YELLOW } ${ RED } ${ RED } ${ BLUE } ${ YELLOW } ${ RED } ${ RED } ${ RED } should output ${ RED } ${ RED } ${ BLUE } ` ,
level : createGrid ( 7 , 7 , 6 , 6 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var found = ""
var start = false
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === YELLOW ) {
if ( ! start ) {
start = true
} else {
break ;
}
} else {
if ( start ) {
found += tape [ i ]
}
}
}
return found ;
} ,
generator : SUBSTRING _TEST ,
//display:BINARY
}
var ISTAGE8 = {
name : "Reversal" ,
objective : ` Reverse the input string. Ex: ${ RED } ${ RED } ${ RED } ${ BLUE } ${ BLUE } ${ RED } ${ BLUE } ${ BLUE } -> ${ 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 < chars . length ; i ++ ) {
chars [ i ] = binaryToTape ( tapeToBinary ( chars [ i ] ) + 32 )
}
return chars . join ( YELLOW )
} ,
generator : UPPERCASE _CHARACTER _TEST ,
display : STRING
}
var ASTAGE3 = {
name : "Addition" ,
objective : ` Given 2 binary numbers (R=0 B=1) separated by a green dot, output the addition of those two numbers. ` ,
level : createGrid ( 15 , 15 , 14 , 14 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
var temp = ""
var number1 = - 1
var number2 = - 1
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === GREEN ) {
if ( number1 === - 1 ) {
number1 = tapeToBinary ( temp )
}
temp = ""
} else {
temp += tape [ i ]
}
}
number2 = tapeToBinary ( temp )
return binaryToTape ( number1 + number2 )
} ,
generator : TWO _NUMBERS _TEST ,
display : BINARY
}
var ASTAGE4 = {
name : "The Great Conversion" ,
objective : ` Convert every other ${ RED } to ${ BLUE } ${ GREEN } , then for every found ${ BLUE } ${ RED } sequence, turn them into ${ RED } ${ RED } and run the checks again. Ex: ${ RED } ${ RED } ${ BLUE } ${ BLUE } ${ RED } ${ BLUE } ${ BLUE } ${ BLUE } ${ RED } -> ${ 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 < tape . length ; i ++ ) {
if ( tape [ i ] === RED ) {
if ( first ) {
found = true
newString += BLUE + GREEN
first = false
} else {
newString += RED
first = true
}
} else {
newString += tape [ i ]
}
}
tape = newString
newString = ""
//BR -> RR
var last = ""
for ( var i = 0 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === BLUE ) {
if ( i + 1 < tape . length && tape [ i + 1 ] === RED ) {
newString += RED + RED
i += 1
} else {
newString += tape [ i ]
}
} else
{
newString += tape [ i ]
}
}
tape = newString
newString = ""
}
return tape
} ,
//generator:TWO_NUMBERS_TEST,
//display:BINARY
}
var ASTAGE5 = {
name : "Compare" ,
objective : ` Given 2 distinct binary values ( ${ RED } =0 ${ BLUE } =1) separated by a green dot, output the greater of the two numbers Ex: ${ BLUE } ${ BLUE } ${ BLUE } ${ RED } ${ BLUE } ${ GREEN } ${ BLUE } ${ BLUE } ${ RED } ${ RED } ${ RED } (29/24) outputs as ${ BLUE } ${ BLUE } ${ BLUE } ${ RED } ${ BLUE } (29>24) ` ,
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 ; i < tape . length ; i ++ ) {
if ( tape [ i ] === GREEN ) {
if ( number1 === - 1 ) {
number1 = tapeToBinary ( temp )
}
temp = ""
} else {
temp += tape [ i ]
}
}
number2 = tapeToBinary ( temp )
return binaryToTape ( ( number1 > number2 ) ? 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 < tape . length ; i ++ ) {
newTape += YELLOW
}
return newTape
}
}
var SANDBOX = {
name : "The Sandbox" ,
objective : "You may experiment freely in this very large room." ,
level : createGrid ( 50 , 50 , 24 , 24 ) ,
start : { x : 0 , y : 0 } ,
accept : ( tape ) => {
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 ; i < width ; i ++ ) {
var row = [ ]
for ( var j = 0 ; j < height ; j ++ ) {
row . push ( { } )
}
grid . push ( row )
}
grid [ exitY ] [ exitX ] = { type : "EXIT" }
return grid
}
function resetGame ( ) {
gameGrid = [ ]
gameState = WAITING
BOT _X = - 1
BOT _Y = - 1
BOT _DIR = RIGHT
BOT _STATE = ALIVE
BOT _TAPE = "RB"
BOT _QUEUE = [ ]
lastGameUpdate = 0
completedStages = { }
MENU . visible = false
}
function convertStringToTape ( str ) {
var finalTape = ""
for ( var i = 0 ; i < str . length ; i ++ ) {
if ( finalTape !== "" ) {
finalTape += YELLOW
}
finalTape += binaryToTape ( str . charCodeAt ( i ) )
}
return finalTape
}
function decideIfWrongBot ( isSupposedToBeAccepted , tape ) {
var result ;
var wrongBot = false ;
switch ( typeof ( isSupposedToBeAccepted ) ) {
case "object" : {
if ( isSupposedToBeAccepted . string !== undefined ) {
if ( getSimulatedBotResult ( tape ) ) {
result = BOT _TAPE
if ( result === convertStringToTape ( isSupposedToBeAccepted . string ) ) {
wrongBot = false
} else {
wrongBot = true
}
isSupposedToBeAccepted = isSupposedToBeAccepted . string
} else {
if ( BOT _QUEUE . length === 0 ) {
BOT _DID _NOT _REACH _EXIT = true
}
wrongBot = true ;
}
}
} break ;
case "string" : {
if ( getSimulatedBotResult ( tape ) ) {
result = BOT _TAPE
if ( result === isSupposedToBeAccepted ) {
wrongBot = false ;
} else {
wrongBot = true ;
}
} else {
if ( BOT _QUEUE . length === 0 ) {
BOT _DID _NOT _REACH _EXIT = true
}
wrongBot = true ;
}
} break ;
case "boolean" : {
result = getSimulatedBotResult ( tape )
if ( result === isSupposedToBeAccepted ) {
wrongBot = false ;
} else {
wrongBot = true ;
}
} break ;
}
if ( wrongBot ) {
if ( BOT _QUEUE . length === 0 ) {
EXPECTED = isSupposedToBeAccepted ;
}
RESULT = false
BOT _QUEUE . push ( tape )
}
}
function generateBotQueue ( ) {
BOT _QUEUE = [ ]
RESULT = true
TESTSTEPS = 0
BOT _DID _NOT _REACH _EXIT = false
if ( gameState === TESTING ) {
//Iterate up to...15 RED/BLUE combinations.
var MAX _VALUE = 1000
var startingValue = 0
var tests = [ ]
var generator
if ( gameStage . generator !== undefined ) {
generator = gameStage . generator
} else {
generator = NORMAL _TEST
}
switch ( generator ) {
case BSTAGE2 _TEST : {
for ( var i = 0 ; i < MAX _VALUE ; i ++ ) {
var newTape = ""
var numb = i ;
while ( numb > 0 ) {
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 < length ; i ++ ) {
word += String . fromCharCode ( Math . floor ( Math . random ( ) * 26 ) + 65 )
}
tests . push ( convertStringToTape ( word ) )
}
} break ;
case TWO _UNEQUAL _NUMBERS _TEST : {
while ( tests . length < 2000 ) {
var numb1 = Math . floor ( Math . random ( ) * 512 )
var numb2 = Math . floor ( Math . random ( ) * 512 )
var newTape = binaryToTape ( numb1 )
var newTape2 = binaryToTape ( numb2 )
if ( newTape [ newTape . length - 1 ] !== RED && newTape2 [ newTape2 . length - 1 ] !== RED && numb1 !== numb2 ) {
tests . push ( newTape + GREEN + newTape2 )
}
}
} break ;
case ONE _TEST : {
tests = [ "RRR" ]
} break ;
case BLANK _TEST : {
tests = [ "" ]
} break ;
default : {
for ( var i = 0 ; i < MAX _VALUE ; i ++ ) {
tests . push ( binaryToTape ( startingValue ++ ) )
tests . push ( binaryToTape ( startingValue ) . split ( "" ) . reverse ( ) . join ( "" ) )
}
}
}
TEST _RANDOM _TAPE = tests [ Math . floor ( Math . random ( ) * tests . length ) ]
for ( var i = 0 ; i < tests . length ; i ++ ) {
//Set to true if a bot that's supposed to pass fails, or a bot that's supposed to fail passes.
var isSupposedToBeAccepted = gameStage . accept ( tests [ i ] )
decideIfWrongBot ( isSupposedToBeAccepted , tests [ i ] )
if ( BOT _QUEUE . length >= 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 ( iterations < MAX _ITERATIONS ) {
runBot ( true )
TESTSTEPS ++
if ( gameState === REVIEWING ) {
return false
}
if ( gameState === FINISH ) {
return true
}
iterations ++
}
if ( BOT _QUEUE . length === 0 ) {
BOT _DID _NOT _REACH _EXIT = true
RESULT = false
BOT _QUEUE . push ( tape )
}
if ( iterations === MAX _ITERATIONS ) {
TESTSTEPS = - 1
}
return false
}
function binaryToTape ( val ) {
var remainingVal = val
var tape = ""
while ( remainingVal > 0 ) {
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 < new Date ( ) . getTime ( ) || testing ) && gameSpeed !== - 1 ) {
GHOST _BOT _TAPE = BOT _TAPE
lastGameUpdate = new Date ( ) . getTime ( ) + gameSpeed
var nextSquare = { }
switch ( BOT _DIR ) {
case UP : {
nextSquare = setNextSquare ( 0 , - 1 )
if ( nextSquare !== undefined ) {
BOT _DIR = ( nextSquare . direction2 !== undefined &&
( nextSquare . direction2 === UP || nextSquare . direction2 === DOWN ) ) ? nextSquare . direction2 : nextSquare . direction
}
} break ;
case LEFT : {
nextSquare = setNextSquare ( - 1 , 0 )
if ( nextSquare !== undefined ) {
BOT _DIR = ( nextSquare . direction2 !== undefined &&
( nextSquare . direction2 === RIGHT || nextSquare . direction2 === LEFT ) ) ? nextSquare . direction2 : nextSquare . direction
}
} break ;
case RIGHT : {
nextSquare = setNextSquare ( 1 , 0 ) ;
if ( nextSquare !== undefined ) {
BOT _DIR = ( nextSquare . direction2 !== undefined &&
( nextSquare . direction2 === RIGHT || nextSquare . direction2 === LEFT ) ) ? nextSquare . direction2 : nextSquare . direction
}
} break ;
case DOWN : {
nextSquare = setNextSquare ( 0 , 1 )
if ( nextSquare !== undefined ) {
BOT _DIR = ( nextSquare . direction2 !== undefined &&
( nextSquare . direction2 === UP || nextSquare . direction2 === DOWN ) ) ? nextSquare . direction2 : nextSquare . direction
}
} break ;
}
if ( nextSquare !== undefined && ( nextSquare . direction !== undefined || nextSquare . type === "EXIT" ) ) {
switch ( nextSquare . type ) {
case "BRANCH" : {
if ( BOT _TAPE [ 0 ] === nextSquare . color1 ) {
//Move towards left side of the branch.
BOT _DIR = LeftOf ( nextSquare . direction )
ConsumeTape ( )
} else
if ( BOT _TAPE [ 0 ] === nextSquare . color2 ) {
//Move towards left side of the branch.
BOT _DIR = RightOf ( nextSquare . direction )
ConsumeTape ( )
}
} break ;
case "WRITER" : {
if ( nextSquare . overwrite ) {
OverwriteTape ( nextSquare . color1 )
} else {
AppendTape ( nextSquare . color1 )
}
BOT _DIR = nextSquare . direction
} break ;
case "EXIT" : {
gameState = FINISH
BOT _STATE = DONE
endARound ( )
} break ;
}
} else {
gameState = REVIEWING
BOT _STATE = DEAD
endARound ( )
}
}
}
function resetBot ( x , y , state , tape ) {
gameState = state
BOT _STATE = ALIVE
BOT _DIR = RIGHT
BOT _TAPE = tape
placeBot ( x , y )
}
function placeBot ( x , y ) {
BOT _X = x
BOT _Y = y
}
function setupGame ( ) {
canvas = document . createElement ( "canvas" ) ;
canvas . width = 568
canvas . height = 320
canvas . style . height = "100%"
document . getElementById ( "game" ) . appendChild ( canvas )
canvas . addEventListener ( "mousemove" , updateMouse )
canvas . addEventListener ( "mousedown" , clickEvent )
canvas . addEventListener ( "mouseup" , releaseEvent )
canvas . addEventListener ( "touchmove" , updateMouse )
canvas . addEventListener ( "touchstart" , clickEvent )
canvas . addEventListener ( "touchend" , releaseEvent )
document . addEventListener ( "keydown" , keydownEvent )
document . addEventListener ( "keyup" , keyupEvent )
try {
completedStages = JSON . parse ( localStorage . getItem ( "game" ) )
if ( ! completedStages ) {
completedStages = { }
} else {
FIRSTBOOT = false
}
} catch { }
//loadStage(TUTORIAL4)
//gameState=MAINMENU
gameState = STARTUP
}
function setupTitleScreen ( ) {
gameState = TITLE
CURRENTTIMELINE = [ ... TITLETIMELINE ]
TITLESCREENTIMELINE = new Date ( ) . getTime ( ) ;
}
function CheckKeys ( e , keys ) {
for ( var key of keys ) {
if ( key === e . key ) {
return true
}
}
return false
}
function keydownEvent ( e ) {
if ( CheckKeys ( e , KEY _ROTATION _RIGHT ) ) {
ITEM _DIRECTION = RIGHT
}
if ( CheckKeys ( e , KEY _ROTATION _LEFT ) ) {
ITEM _DIRECTION = LEFT
}
if ( CheckKeys ( e , KEY _ROTATION _UP ) ) {
ITEM _DIRECTION = UP
}
if ( CheckKeys ( e , KEY _ROTATION _DOWN ) ) {
ITEM _DIRECTION = DOWN
}
if ( CheckKeys ( e , KEY _BRIDGED _BELT ) ) {
BRIDGEDBELT = true
}
}
function keyupEvent ( e ) {
if ( CheckKeys ( e , KEY _BRIDGED _BELT ) ) {
BRIDGEDBELT = false
}
}
function mouseOverButton ( canvas , e , button ) {
return ( getMousePos ( e ) . x >= 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 . y < gameGrid . length && coords . x < gameGrid [ coords . y ] . length
}
function modifyBoard ( clickCoords , item ) {
if ( DELETEMODE ) {
deleteObject ( clickCoords )
} else {
placeObject ( clickCoords , item )
}
}
function notAForbiddenObject ( coords ) {
return ( gameStage . start . x !== coords . x || gameStage . start . y !== coords . y ) && ( gameGrid [ coords . y ] [ coords . x ] . type === undefined || ( gameGrid [ coords . y ] [ coords . x ] . type && gameGrid [ coords . y ] [ coords . x ] . type !== "EXIT" ) )
}
function deleteObject ( coords , def ) {
if ( notAForbiddenObject ( coords ) ) {
gameGrid [ coords . y ] [ coords . x ] = { }
gameState = WAITING
endARound ( )
}
}
function placeObject ( coords , def ) {
if ( notAForbiddenObject ( coords ) ) {
if ( gameGrid [ coords . y ] [ coords . x ] . direction !== undefined && gameGrid [ coords . y ] [ coords . x ] . type && gameGrid [ coords . y ] [ coords . x ] . type === "BELT"
&& ( gameGrid [ coords . y ] [ coords . x ] . direction === getClockwiseDirection ( ITEM _DIRECTION ) || gameGrid [ coords . y ] [ coords . x ] . direction === getCounterClockwiseDirection ( ITEM _DIRECTION ) )
&& BRIDGEDBELT ) {
gameGrid [ coords . y ] [ coords . x ] = { ... gameGrid [ coords . y ] [ coords . x ] , direction2 : ITEM _DIRECTION }
} else {
var newObj = { ... def , direction : ITEM _DIRECTION }
gameGrid [ coords . y ] [ coords . x ] = newObj
}
gameState = WAITING
endARound ( )
}
}
function getGridCoords ( pos ) {
if ( pos . x < canvas . width * 0.75 && pos . y < canvas . height * 0.8 ) {
var x = Math . round ( ( pos . x - GRID _X ) / GRID _W - 1 )
var y = Math . round ( ( pos . y - GRID _Y ) / GRID _H - 1 )
return { x : x , y : y }
} else {
return { x : - 1 , y : - 1 }
}
}
function releaseEvent ( e ) {
if ( window . TouchEvent && e instanceof TouchEvent ) {
e . preventDefault ( )
}
if ( gridModeIsAvailable ( ) && ITEM _SELECTED === undefined ) {
setMoveMode ( false )
}
if ( ! MOBILE && e . button !== 0 ) {
e . preventDefault ( )
if ( gridModeIsAvailable ( ) ) {
setMoveMode ( false )
}
}
ALLOWEDTOCLICK = true
if ( MOVEMODE ) {
STARTDRAG = undefined
}
var mousepos = getMousePos ( e )
LAST _MOUSE _X = mousepos . x
LAST _MOUSE _Y = mousepos . y
if ( SUBMENU . visible ) {
for ( var button of SUBMENU . buttons ) {
if ( mouseOverButton ( canvas , e , button ) ) {
ITEM _SELECTED = button . def
BUTTON _SELECTED . lastselected = button . def
SUBMENU . visible = false
return
}
}
SUBMENU . visible = false
}
DRAGGING = false
MOUSEDOWN = false
}
function loadLevel ( level , botx , boty ) {
placeBot ( botx , boty )
gameGrid = deepCopy ( level )
MENU . visible = true
}
function loadStage ( stage ) {
//gameGrid=deepCopy(stage.level)
ITEM _SELECTED = undefined
loadLevel ( stage . level , stage . start . x , stage . start . y )
gameStage = stage
if ( completedStages [ stage . name ] === undefined ) {
completedStages [ stage . name ] = { }
} else {
if ( completedStages [ stage . name ] . data !== undefined ) {
gameGrid = deepCopy ( completedStages [ stage . name ] . data )
}
}
}
function deepCopy ( arr ) {
var newarr = [ ]
for ( var i = 0 ; i < arr . length ; i ++ ) {
newarr [ i ] = [ ]
for ( var j = 0 ; j < arr [ i ] . length ; j ++ ) {
newarr [ i ] [ j ] = { ... arr [ i ] [ j ] }
}
}
return newarr
}
function step ( ) {
dashOffset += 0.1 * Math . max ( ( 1000 / gameSpeed ) , 1 )
switch ( gameState ) {
case RUNNING : {
runBot ( )
} break ;
}
}
function updateMouse ( e ) {
e . preventDefault ( )
var mousepos = getMousePos ( e )
LAST _MOUSE _X = mousepos . x
LAST _MOUSE _Y = mousepos . y
if ( MOVEMODE && STARTDRAG !== undefined ) {
GRID _X += mousepos . x - STARTDRAG . x
GRID _Y += mousepos . y - STARTDRAG . y
STARTDRAG = mousepos
}
if ( DRAGGING ) {
var clickCoords = getGridCoords ( getMousePos ( e ) )
if ( coordsInBounds ( clickCoords ) ) {
if ( DRAG _X !== clickCoords . x || DRAG _Y !== clickCoords . y ) {
modifyBoard ( clickCoords , ITEM _SELECTED )
DRAG _X = clickCoords . x
DRAG _Y = clickCoords . y
}
}
}
}
function getMousePos ( e ) {
var rect = canvas . getBoundingClientRect ( ) ;
var scale = canvas . height / canvas . clientHeight
if ( e . changedTouches ) {
return {
x : ( e . changedTouches [ 0 ] . clientX - rect . left ) * scale ,
y : ( e . changedTouches [ 0 ] . clientY - rect . top ) * scale
} ;
} else {
return {
x : ( e . clientX - rect . left ) * scale ,
y : ( e . clientY - rect . top ) * scale
} ;
}
}
function renderGame ( ctx ) {
ctx . lineWidth = 1
ctx . strokeStyle = "#000000"
ctx . setLineDash ( [ ] )
for ( var y = 0 ; y <= gameGrid . length + 1 ; y ++ ) {
ctx . moveTo ( GRID _X , GRID _Y + GRID _H * y ) ;
ctx . lineTo ( GRID _X + GRID _W * ( gameGrid . length + 1 ) , GRID _Y + GRID _H * y ) ;
}
for ( var x = 0 ; x <= gameGrid . length + 1 ; x ++ ) {
ctx . moveTo ( GRID _X + GRID _W * x , GRID _Y ) ;
ctx . lineTo ( GRID _X + GRID _W * x , GRID _Y + GRID _H * ( gameGrid . length + 1 ) ) ;
}
ctx . stroke ( ) ;
for ( var y = 0 ; y < gameGrid . length ; y ++ ) {
for ( var x = 0 ; x < gameGrid [ y ] . length ; x ++ ) {
if ( gameGrid [ y ] [ x ] . img !== undefined ) {
RenderIcon ( GRID _X + GRID _W * x + 16 , GRID _Y + GRID _H * y + 16 , ctx , gameGrid [ y ] [ x ] , gameGrid [ y ] [ x ] . direction , undefined , { x : x , y : y } )
}
if ( gameGrid [ y ] [ x ] . type === "EXIT" ) {
drawImage ( GRID _X + GRID _W * x + 32 , GRID _Y + GRID _H * y + 32 , ID _EXIT , ctx , 0 , 1 )
}
}
}
drawImage ( GRID _X + GRID _W * gameStage . start . x + 16 + GRID _W / 2 ,
GRID _Y + GRID _H * gameStage . start . y + 16 + GRID _H / 2 ,
ID _ENTRANCE , ctx , 0 )
if ( BOT _X !== undefined && ( gameState === RUNNING || gameState === PAUSED || gameState == REVIEWING || gameState == FINISH ) ) {
var movedDiff = { x : BOT _X - BOT _PREVX , y : BOT _Y - BOT _PREVY }
movedDiff . x *= Math . min ( ( new Date ( ) . getTime ( ) - LASTPOSITIONUPDATE ) , gameSpeed ) / gameSpeed
movedDiff . y *= Math . min ( ( new Date ( ) . getTime ( ) - LASTPOSITIONUPDATE ) , gameSpeed ) / gameSpeed
var interpolatedX = GRID _X + GRID _W * ( BOT _PREVX + movedDiff . x ) + 16 + GRID _W / 2
var interpolatedY = GRID _Y + GRID _H * ( BOT _PREVY + movedDiff . y ) + 16 + GRID _H / 2
if ( gridModeIsAvailable ( ) && gameState === RUNNING && BOT _START _TAPE !== undefined ) {
GRID _X = - ( interpolatedX - GRID _X ) + canvas . width / 2 - 20 - GRID _W
GRID _Y = - ( interpolatedY - GRID _Y ) + canvas . height / 2 - 20 - GRID _H
}
drawImage (
interpolatedX ,
interpolatedY ,
ID _BOT , ctx , 0 * 90 )
}
}
function colorToHex ( r , g , b ) {
function hex ( col ) {
function toHex ( val ) {
switch ( Math . floor ( val ) ) {
case 10 : {
return "a" ;
} break ;
case 11 : {
return "b" ;
} break ;
case 12 : {
return "c" ;
} break ;
case 13 : {
return "d" ;
} break ;
case 14 : {
return "e" ;
} break ;
case 15 : {
return "f" ;
} break ;
default : {
return String ( Math . floor ( val ) ) ;
}
}
}
var placeValue1 = col % 16 ;
var placeValue2 = col / 16 ;
return toHex ( placeValue2 ) + toHex ( placeValue1 )
}
return "#" + hex ( r ) + hex ( g ) + hex ( b ) ;
}
function drawImage ( x , y , img , ctx , degrees , scale = 1 ) {
//context.clearRect(0,0,canvas.width,canvas.height);
ctx . save ( ) ;
ctx . translate ( x , y ) ;
ctx . rotate ( degrees * Math . PI / 180 ) ;
ctx . scale ( scale , scale )
ctx . drawImage ( IMAGE _DATA [ img ] , ( IMAGE _DATA [ img ] . width ) ? - IMAGE _DATA [ img ] . width / 2 : 0 , ( IMAGE _DATA [ img ] . height ) ? - IMAGE _DATA [ img ] . height / 2 : 0 ) ;
ctx . restore ( ) ;
}
function runEvents ( ctx ) {
var elapsedTime = new Date ( ) . getTime ( ) - TITLESCREENTIMELINE
if ( CURRENTTIMELINE . length > 0 ) {
var currentEvent = CURRENTTIMELINE [ 0 ]
if ( currentEvent . time < elapsedTime ) {
currentEvent . cb ( ctx )
CURRENTTIMELINE . shift ( )
}
}
}
function draw ( ) {
var ctx = canvas . getContext ( "2d" )
if ( Math . random ( ) < 0.01 ) {
ctx . fillStyle = "rgba(63, 73, 71, 0.5)"
ctx . globalAlpha = 0.5
ctx . fillRect ( 0 , 0 , canvas . width , canvas . height )
} else {
ctx . fillStyle = "#b5c4c1"
ctx . globalAlpha = 1.0
ctx . fillRect ( 0 , 0 , canvas . width , canvas . height )
switch ( gameState ) {
case TITLE : {
ctx . save ( ) ;
ctx . scale ( 1 , 1 )
ctx . translate ( 260 + 320 , 100 + 320 )
ctx . rotate ( ( dashOffset ) * ( Math . PI / 180 ) )
ctx . translate ( - 320 , - 320 )
ctx . drawImage ( IMAGE _GEAR , 0 , 0 )
ctx . restore ( )
runEvents ( ctx )
ctx . fillStyle = SCENEBACKGROUND
ctx . globalAlpha = SCENEALPHA
ctx . fillRect ( 0 , 0 , canvas . width , canvas . height )
SCENE _DRAW ( ctx )
} break ;
case MAINMENU : {
ctx . fillStyle = "rgb(180,180,180)"
ctx . strokeStyle = "black"
ctx . beginPath ( )
ctx . ellipse ( 24 , 24 , 20 , 20 , 0 , 0 , 2 * Math . PI )
ctx . fill ( )
ctx . stroke ( )
ctx . beginPath ( )
ctx . ellipse ( 24 , 64 , 20 , 20 , 0 , 0 , 2 * Math . PI )
ctx . fill ( )
ctx . stroke ( )
ctx . drawImage ( ( currentSound . muted ) ? IMAGE _NOAUDIO _BUTTON : IMAGE _AUDIO _BUTTON , 8 , 8 )
ctx . drawImage ( IMAGE _INFO _BUTTON , 8 , 48 )
DisplayMenu ( canvas . width / 2 , 8 , TUTORIALMENU , ctx )
DisplayMenu ( ( canvas . width * 0.33 ) * 0 + ( canvas . width * 0.15 ) + ( canvas . width * 0.01 ) , 108 , EASYMENU , ctx )
DisplayMenu ( ( canvas . width * 0.33 ) * 1 + ( canvas . width * 0.15 ) + ( canvas . width * 0.02 ) , 108 , MEDIUMMENU , ctx )
DisplayMenu ( ( canvas . width * 0.33 ) * 2 + ( canvas . width * 0.15 ) + ( canvas . width * 0.03 ) , 108 , HARDMENU , ctx )
DisplayMenu ( ( canvas . width * 0.33 ) * 2 + ( canvas . width * 0.15 ) + ( canvas . width * 0.03 ) , 275 , SANDBOXMENU , ctx )
} break ;
case INFO : {
ctx . fillStyle = "rgb(180,180,180)"
ctx . strokeStyle = "black"
ctx . beginPath ( )
ctx . ellipse ( 24 , 24 , 20 , 20 , 0 , 0 , 2 * Math . PI )
ctx . fill ( )
ctx . stroke ( )
ctx . drawImage ( IMAGE _DATA [ ID _HOME ] , 12 , 8 )
var split = CREDITS . split ( "\n" )
for ( var i = 0 ; i < split . length ; i ++ ) {
var string = split [ i ]
if ( split [ i ] [ 0 ] === ">" ) {
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 ; i < arr . length ; i ++ ) {
var tempText = ( finalText [ finalText . length - 1 ] !== undefined ) ? finalText [ finalText . length - 1 ] + " " + arr [ i ] : arr [ i ]
var newWidth = ctx . measureText ( tempText ) . width
if ( newWidth > w ) {
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 < finalText . length ; i ++ ) {
if ( topToBottom ) {
ctx . fillText ( finalText [ i ] , x , y + i * fontHeight + 4 )
} else {
ctx . fillText ( finalText [ finalText . length - 1 - i ] , x , y - i * fontHeight + 4 )
}
}
}
function LevelIsBeat ( levelName ) {
return completedStages [ levelName ] !== undefined && completedStages [ levelName ] . complete
}
function RenderGameInfo ( ctx ) {
if ( MENU . visible ) {
ctx . fillStyle = "#20424a"
ctx . fillRect ( canvas . width * 0.75 , 0 , canvas . width , canvas . height * 0.8 )
RenderSpeedbar ( canvas . width * 0.75 + ( canvas . width * 0.25 ) / 2 - 32 , 8 , 64 , ctx )
if ( LevelIsBeat ( gameStage . name ) ) {
drawImage ( canvas . width - 18 , 14 , ID _COMPLETE _STAR , ctx , 0 , 0.75 )
ctx . font = "11px 'Profont','Courier New', serif"
ctx . fillStyle = "white"
ctx . textAlign = "center"
ctx . fillText ( "Best Score: " + completedStages [ gameStage . name ] . score , canvas . width * 0.75 + ( canvas . width * 0.25 ) / 3 + 2 , 22 )
} else {
drawImage ( canvas . width - 18 , 14 , ID _INCOMPLETE _STAR , ctx , 0 , 0.75 )
}
if ( BOT _START _TAPE !== undefined && ( gameState === REVIEWING || gameState === FINISH ) ) {
ctx . fillStyle = "#20424a"
ctx . globalAlpha = 0.6
ctx . fillRect ( canvas . width * 0.75 - canvas . width * 0.25 , 4 , canvas . width * 0.25 , 5 * 12 + 56 )
ctx . globalAlpha = 1
ctx . font = "bold 16px 'Zilla Slab', serif"
ctx . textAlign = "left"
ctx . strokeStyle = "rgb(90, 148, 118)"
ctx . strokeText ( "Original Tape" , canvas . width * 0.75 - canvas . width * 0.25 , 20 )
ctx . fillStyle = "black"
ctx . fillText ( "Original Tape" , canvas . width * 0.75 - canvas . width * 0.25 , 20 )
RenderTape ( canvas . width * 0.75 - canvas . width * 0.25 + 8 , 16 , canvas . width * 0.25 - 16 , ctx , BOT _START _TAPE )
ctx . font = "12px 'Profont','Courier New', serif"
ctx . fillStyle = "white"
ctx . textAlign = "left"
ctx . fillText ( "Efficiency Score:" , canvas . width * 0.75 - ( canvas . width * 0.25 ) + 4 , 4 + 5 * 12 + 36 )
ctx . textAlign = "right"
ctx . fillText ( TESTSTEPS , canvas . width * 0.75 - 8 , 4 + 5 * 12 + 36 + 16 )
}
if ( BOT _START _TAPE !== undefined ) {
RenderTape ( canvas . width * 0.75 + 8 , 16 , canvas . width * 0.25 - 16 , ctx , GHOST _BOT _TAPE )
}
if ( gameState === RUNNING || gameState === REVIEWING || gameState === FINISH || gameState === PAUSED ) {
ctx . font = "16px 'Zilla Slab', serif"
ctx . fillStyle = "white"
ctx . textAlign = "center"
switch ( typeof ( EXPECTED ) ) {
case "string" : {
ctx . fillText ( "Expected Result: " , canvas . width - canvas . width * 0.25 / 2 , canvas . height / 2 - 52 )
ctx . fillStyle = "rgb(52, 235, 140)"
RenderTape ( canvas . width * 0.75 + 8 , canvas . height / 2 - 52 , canvas . width * 0.25 - 16 , ctx , EXPECTED )
if ( RESULT ) {
ctx . fillStyle = "rgb(52, 235, 140)"
ctx . fillText ( "PASSED" , canvas . width - canvas . width * 0.25 / 2 , canvas . height / 2 + 24 )
} else {
if ( BOT _DID _NOT _REACH _EXIT ) {
ctx . fillStyle = "rgb(235, 98, 52)"
ctx . fillText ( "DID NOT EXIT" , canvas . width - canvas . width * 0.25 / 2 , canvas . height / 2 + 24 )
} else {
ctx . fillStyle = "rgb(235, 98, 52)"
ctx . fillText ( "FAILED" , canvas . width - canvas . width * 0.25 / 2 , canvas . height / 2 + 24 )
}
}
} break ;
case "boolean" : {
ctx . fillText ( "Expected Result: " , canvas . width - canvas . width * 0.25 / 2 , canvas . height - 120 - 40 )
if ( EXPECTED ) {
ctx . fillStyle = "rgb(52, 235, 140)"
ctx . fillText ( " ACCEPT" , canvas . width - canvas . width * 0.25 / 2 , canvas . height - 100 - 40 )
} else {
ctx . fillStyle = "rgb(235, 98, 52)"
ctx . fillText ( " REJECT" , canvas . width - canvas . width * 0.25 / 2 , canvas . height - 100 - 40 )
}
} break ;
}
}
if ( ISTESTING ) {
ctx . font = "32px 'Zilla Slab', serif"
ctx . fillStyle = "white"
ctx . textAlign = "center"
ctx . fillText ( "Testing..." , ( canvas . width * 0.75 ) / 2 , canvas . height * 0.75 )
ctx . lineWidth = 2
ctx . strokeStyle = "Black"
ctx . strokeText ( "Testing..." , ( canvas . width * 0.75 ) / 2 , canvas . height * 0.75 )
}
var currentTime = new Date ( ) . getTime ( ) ;
if ( currentTime < MESSAGETIMER ) {
ctx . font = "bold 24px 'Zilla Slab', serif"
ctx . fillStyle = ( RESULT ) ? "rgb(32, 59, 33)" : "rgb(144, 12, 63)"
ctx . textAlign = "center"
DrawWrapText ( ( RESULT ) ? GOODQUOTES [ MESSAGETIMER % GOODQUOTES . length ] : BADQUOTES [ MESSAGETIMER % BADQUOTES . length ] , ( canvas . width * 0.75 ) / 2 , canvas . height * 0.75 , ( canvas . width * 0.75 ) * 0.9 , 24 , ctx )
}
var levelDescriptionOffsetY = - 16
ctx . font = "bold 20px 'Zilla Slab', serif"
ctx . fillStyle = "rgb(52, 227, 160)"
ctx . textAlign = "center"
ctx . fillText ( gameStage . name , ( canvas . width * 0.75 ) + ( canvas . width * 0.25 ) / 2 , canvas . height - 100 + levelDescriptionOffsetY )
ctx . strokeStyle = "white"
ctx . lineWidth = 1
ctx . beginPath ( )
ctx . moveTo ( ( canvas . width * 0.75 ) + ( canvas . width * 0.25 ) / 4 , canvas . height - 94 + levelDescriptionOffsetY )
ctx . lineTo ( ( canvas . width * 0.75 ) + ( ( canvas . width * 0.25 ) ) * 0.75 , canvas . height - 94 + levelDescriptionOffsetY )
ctx . stroke ( )
ctx . font = "12px 'Zilla Slab', serif"
ctx . fillStyle = "white"
ctx . textAlign = "left"
DrawWrapText ( gameStage . objective , ( canvas . width * 0.75 ) + 8 , canvas . height - 84 + levelDescriptionOffsetY , ( canvas . width * 0.25 ) * 0.9 , 12 , ctx , true )
if ( MOBILE && gameState !== RUNNING ) {
drawImage ( canvas . width * 0.75 - 48 , canvas . height * 0.8 - 48 ,
ID _OUTLINE , ctx , 0 )
if ( ITEM _SELECTED && ! MOVEMODE ) {
if ( ITEM _SELECTED . img === ID _CONVEYOR ) {
RenderIcon ( canvas . width * 0.75 - 48 - 32 , canvas . height * 0.8 - 48 - 32 , ctx , ITEM _SELECTED , ITEM _DIRECTION , undefined , undefined , 2 )
} else {
RenderIcon ( canvas . width * 0.75 - 48 - 16 , canvas . height * 0.8 - 48 - 16 , ctx , ITEM _SELECTED , ITEM _DIRECTION , undefined , undefined , 2 )
}
} else {
if ( DELETEMODE ) {
drawImage ( canvas . width * 0.75 - 48 , canvas . height * 0.8 - 48 , ID _DELETE _CURSOR , ctx , 0 , 2 )
} else {
drawImage ( canvas . width * 0.75 - 48 , canvas . height * 0.8 - 48 , ID _CURSOR , ctx , 0 , 2 )
}
}
}
}
}
var DARKCOLORS = [ PURPLE , GRAY , BLACK ]
function drawTape ( color , ctx , x , y , ySpacing , first = false , last = false , firstInRow = false ) {
ctx . font = "bold 12px 'Profont','Courier New', serif"
if ( first ) {
ctx . fillStyle = "rgb(222, 210, 158)"
ctx . fillRect ( x - 8 , y - 8 , 16 , 16 )
}
if ( DARKCOLORS . includes ( color ) ) {
ctx . fillStyle = "rgb(210,210,210)"
} else {
ctx . fillStyle = "black"
}
ctx . textAlign = "center"
switch ( color ) {
case RED : {
drawImage ( x , y , ID _DOT _R , ctx , 0 )
} break ;
case BLUE : {
drawImage ( x , y , ID _DOT _B , ctx , 0 )
} break ;
case GREEN : {
drawImage ( x , y , ID _DOT _G , ctx , 0 )
} break ;
case YELLOW : {
drawImage ( x , y , ID _DOT _Y , ctx , 0 )
} break ;
case PURPLE : {
drawImage ( x , y , ID _DOT _P , ctx , 0 )
} break ;
case PINK : {
drawImage ( x , y , ID _DOT _PI , ctx , 0 )
} break ;
case BLACK : {
drawImage ( x , y , ID _DOT _BL , ctx , 0 )
} break ;
case GRAY : {
drawImage ( x , y , ID _DOT _GR , ctx , 0 )
} break ;
}
if ( last ) {
ctx . strokeStyle = "white"
ctx . lineWidth = 1
ctx . beginPath ( )
ctx . moveTo ( x - 16 , y )
ctx . lineTo ( x - 8 , y )
ctx . moveTo ( x - 12 , y - 4 )
ctx . lineTo ( x - 8 , y )
ctx . moveTo ( x - 12 , y + 4 )
ctx . lineTo ( x - 8 , y )
ctx . stroke ( )
}
if ( firstInRow ) {
ctx . strokeStyle = "white"
ctx . lineWidth = 1
ctx . beginPath ( )
ctx . moveTo ( x , y )
ctx . lineTo ( x , y - 6 )
ctx . lineTo ( x - 112 , y - 6 )
ctx . lineTo ( x - 112 , y - ySpacing )
ctx . stroke ( )
}
ctx . fillText ( color , x , y + 4 )
}
function tapeToBinary ( tape ) {
var numb = 0 ;
for ( var i = tape . length - 1 ; 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 ; i < Math . min ( tape . length , 1024 ) ; i ++ ) {
drawTape ( tape [ i ] , ctx , x + xOffset + 16 , y + yOffset + 16 , ySpacing * ySpacingMult , i === 0 , i !== Math . min ( tape . length , 1024 ) - 1 , i % 5 === 0 )
xOffset -= 24 ;
if ( xOffset < 0 ) {
xOffset = width - 24 ;
yOffset += ySpacing * ySpacingMult ;
}
}
xOffset = width - 24
yOffset = 0
for ( var i = 0 ; i < Math . min ( tape . length , 5 ) ; i ++ ) {
drawTape ( tape [ i ] , ctx , x + xOffset + 16 , y + yOffset + 16 , ySpacing * ySpacingMult , i === 0 , i !== Math . min ( tape . length , 1024 ) - 1 , false )
xOffset -= 24 ;
if ( xOffset < 0 ) {
xOffset = width - 24 ;
yOffset += ySpacing * ySpacingMult ;
}
}
if ( gameStage . display !== undefined ) {
switch ( gameStage . display ) {
case BINARY : {
ctx . fillStyle = "gray"
ctx . globalAlpha = 0.8
ctx . fillRect ( x , y + 60 , width , 14 )
ctx . globalAlpha = 1
ctx . fillStyle = "white"
if ( tape . includes ( GREEN ) ) {
var numbs = tape . split ( GREEN )
ctx . fillText ( tapeToBinary ( numbs [ 0 ] ) + "," + tapeToBinary ( numbs [ 1 ] ) , x + width / 2 , y + 70 )
} else {
ctx . fillText ( tapeToBinary ( tape ) , x + width / 2 , y + 70 )
}
} break ;
case STRING : {
ctx . fillStyle = "gray"
ctx . globalAlpha = 0.8
ctx . fillRect ( x , y + 60 , width , 14 )
ctx . globalAlpha = 1
ctx . fillStyle = "white"
var splitString = tape . split ( YELLOW )
var finalString = "\""
for ( var i = 0 ; i < splitString . length ; i ++ ) {
var binary = tapeToBinary ( splitString [ i ] )
finalString += String . fromCharCode ( binary )
}
finalString += "\""
ctx . fillText ( finalString , x + width / 2 , y + 70 )
} break ;
}
}
}
function createVerticalGradient ( x , y , up , ctx , scale ) {
var gradient = ctx . createLinearGradient ( x , y + 32 * scale * ( ( up ) ? 1 : 0 ) , x , y + 32 * scale * ( ( up ) ? 0 : 1 ) ) ;
gradient . addColorStop ( 0 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.31 , "black" )
gradient . addColorStop ( 0.6 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.61 , "black" )
gradient . addColorStop ( 0.9 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.91 , "white" )
gradient . addColorStop ( 1 , "white" )
return gradient
}
function createHorizontalGradient ( x , y , right , ctx , scale ) {
var gradient = ctx . createLinearGradient ( x + 32 * scale * ( ( right ) ? 0 : 1 ) , y , x + 32 * scale * ( ( right ) ? 1 : 0 ) , y ) ;
gradient . addColorStop ( 0 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.31 , "black" )
gradient . addColorStop ( 0.6 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.61 , "black" )
gradient . addColorStop ( 0.9 , "rgb(124,162,157)" )
gradient . addColorStop ( 0.91 , "white" )
gradient . addColorStop ( 1 , "white" )
return gradient
}
function DrawSingleConveyor ( x , y , dir , ctx , scale , ghost = false ) {
ctx . lineWidth = 16 * scale ;
ctx . lineCap = "square"
if ( ghost ) {
ctx . globalAlpha = 0.6
ctx . strokeStyle = "rgb(0,255,0)"
} else {
ctx . globalAlpha = 1
ctx . strokeStyle = "rgb(124,162,157)"
}
ctx . setLineDash ( [ ] )
if ( dir === LEFT || dir === RIGHT ) {
ctx . beginPath ( )
ctx . moveTo ( x + 8 * scale , y + 16 * scale )
ctx . lineTo ( x + 24 * scale , y + 16 * scale )
ctx . stroke ( )
ctx . setLineDash ( [ 5 * scale , 5 * scale ] )
ctx . lineDashOffset = - dashOffset * ( ( dir === LEFT ) ? - 1 * scale : 1 * scale ) ;
ctx . strokeStyle = createHorizontalGradient ( x , y , dir === RIGHT , ctx , scale )
ctx . lineWidth = 3.5 * scale ;
ctx . beginPath ( )
ctx . moveTo ( x + 2 * scale , y + 16 * scale )
ctx . lineTo ( x + 30 * scale , y + 16 * scale )
ctx . stroke ( )
} else {
ctx . beginPath ( )
ctx . moveTo ( x + 16 * scale , y + 8 * scale )
ctx . lineTo ( x + 16 * scale , y + 24 * scale )
ctx . stroke ( )
ctx . strokeStyle = "rgb(34,62,57)"
ctx . setLineDash ( [ 5 * scale , 5 * scale ] )
ctx . lineDashOffset = - dashOffset * ( ( dir === DOWN ) ? 1 : - 1 ) ;
ctx . strokeStyle = createVerticalGradient ( x , y , dir === UP , ctx , scale )
ctx . lineWidth = 3.5 * scale ;
ctx . beginPath ( )
ctx . moveTo ( x + 16 * scale , y + 2 * scale )
ctx . lineTo ( x + 16 * scale , y + 30 * scale )
ctx . stroke ( )
}
ctx . globalAlpha = 1
}
function GetOppositeDirection ( dir ) {
switch ( dir ) {
case UP : { return DOWN }
case DOWN : { return UP }
case LEFT : { return RIGHT }
case RIGHT : { return LEFT }
}
}
function DrawConnectedConveyor ( x , y , ctx , connections , dir , scale , pass = 0 ) {
ctx . lineWidth = 15 * scale ;
ctx . lineCap = "round"
ctx . strokeStyle = "rgb(124,162,157)"
switch ( Object . keys ( connections ) . length ) {
case 0 : {
DrawSingleConveyor ( x * scale , y * scale , dir , ctx , scale )
} break ;
default : {
ctx . lineWidth = 15 * scale ;
ctx . lineCap = "square"
ctx . strokeStyle = "rgb(124,162,157)"
ctx . setLineDash ( [ ] )
ctx . beginPath ( )
ctx . moveTo ( x + 16 * scale , y + 16 * scale )
var endingPoint = { x : 0 , y : 0 }
switch ( dir ) {
case UP : { startingPoint = { x : 0 , y : - 12 * scale } ; endingPoint = { x : 0 , y : - 14 * scale } } break ;
case DOWN : { startingPoint = { x : 0 , y : 12 * scale } ; endingPoint = { x : 0 , y : 14 * scale } } break ;
case RIGHT : { startingPoint = { x : 12 * scale , y : 0 } ; endingPoint = { x : 14 * scale , y : 0 } } break ;
case LEFT : { startingPoint = { x : - 12 * scale , y : 0 } ; endingPoint = { x : - 14 * scale , y : 0 } } break ;
}
ctx . moveTo ( x + 16 * scale + startingPoint . x , y + 16 * scale + startingPoint . y )
ctx . lineTo ( x + 16 * scale + endingPoint . x , y + 16 * scale + endingPoint . y )
if ( pass === 0 ) { ctx . stroke ( ) ; }
for ( var connection of Object . keys ( connections ) ) {
var startingPoint = { x : x , y : y }
switch ( Number ( connection ) ) {
case UP : { startingPoint = { x : x + 16 * scale , y : y + 8 * scale } } break ;
case DOWN : { startingPoint = { x : x + 16 * scale , y : y + 24 * scale } } break ;
case RIGHT : { startingPoint = { x : x + 24 * scale , y : y + 16 * scale } } break ;
case LEFT : { startingPoint = { x : x + 8 * scale , y : y + 16 * scale } } break ;
}
ctx . lineWidth = 16 * scale ;
ctx . lineCap = "square"
ctx . strokeStyle = "rgb(124,162,157)"
ctx . setLineDash ( [ ] )
ctx . beginPath ( )
ctx . moveTo ( startingPoint . x , startingPoint . y )
ctx . lineTo ( x + 16 * scale , y + 16 * scale )
if ( pass === 0 ) { ctx . stroke ( ) }
ctx . setLineDash ( [ 5 * scale , 5 * scale ] )
ctx . lineDashOffset = - dashOffset * 1 ;
if ( Number ( connection ) === RIGHT || Number ( connection ) === LEFT ) {
ctx . strokeStyle = createHorizontalGradient ( x , y , Number ( connection ) === LEFT , ctx , scale )
} else {
ctx . strokeStyle = createVerticalGradient ( x , y , Number ( connection ) === UP , ctx , scale )
}
ctx . lineWidth = 3.5 * scale ;
ctx . beginPath ( )
startingPoint = { x : x , y : y }
switch ( Number ( connection ) ) {
case UP : { startingPoint = { x : x + 16 * scale , y : y + 2 * scale } } break ;
case DOWN : { startingPoint = { x : x + 16 * scale , y : y + 30 * scale } } break ;
case RIGHT : { startingPoint = { x : x + 30 * scale , y : y + 16 * scale } } break ;
case LEFT : { startingPoint = { x : x + 2 * scale , y : y + 16 * scale } } break ;
}
ctx . moveTo ( startingPoint . x , startingPoint . y )
ctx . lineTo ( x + 16 * scale , y + 16 * scale )
if ( pass === 1 ) { ctx . stroke ( ) }
}
if ( dir === RIGHT || dir === LEFT ) {
ctx . strokeStyle = createHorizontalGradient ( x , y , dir === LEFT , ctx , scale )
} else {
ctx . strokeStyle = createVerticalGradient ( x , y , dir === UP , ctx , scale )
}
ctx . setLineDash ( [ 5 * scale , 5 * scale ] )
ctx . lineWidth = 3.5 * scale ;
ctx . beginPath ( )
switch ( dir ) {
case UP : { startingPoint = { x : 0 , y : - 14 * scale } } break ;
case DOWN : { startingPoint = { x : 0 , y : 14 * scale } } break ;
case RIGHT : { startingPoint = { x : 14 * scale , y : 0 } } break ;
case LEFT : { startingPoint = { x : - 14 * scale , y : 0 } } break ;
}
ctx . moveTo ( x + 16 * scale , y + 16 * scale )
ctx . lineTo ( x + 16 * scale + startingPoint . x , y + 16 * scale + startingPoint . y )
if ( pass === 1 ) { ctx . stroke ( ) ; }
} break ;
}
}
function HasRelativeConnection ( x , y , dir ) {
return ( gameGrid [ y ] [ x ] . direction === dir ||
( gameGrid [ y ] [ x ] . direction2 !== undefined && gameGrid [ y ] [ x ] . direction2 === dir ) ||
(
gameGrid [ y ] [ x ] . type === "BRANCH" &&
( gameGrid [ y ] [ x ] . direction === getClockwiseDirection ( dir ) || gameGrid [ y ] [ x ] . direction === getCounterClockwiseDirection ( dir ) ||
( gameGrid [ y ] [ x ] . direction2 !== undefined && gameGrid [ y ] [ x ] . direction2 === getClockwiseDirection ( dir ) ) ||
( gameGrid [ y ] [ x ] . direction2 !== undefined && gameGrid [ y ] [ x ] . direction2 === getCounterClockwiseDirection ( dir ) ) )
) ||
(
x === gameStage . start . x && y === gameStage . start . y
)
)
}
function RenderConveyor ( x , y , ctx , icon _definition , dir = 0 , background = undefined , grid = undefined , scale ) {
if ( grid === undefined ) {
//This is the button version.
if ( BRIDGEDBELT ) {
DrawSingleConveyor ( x , y , dir + 1 , ctx , scale , true )
DrawSingleConveyor ( x , y , dir , ctx , scale )
} else {
DrawSingleConveyor ( x , y , dir , ctx , scale )
}
} else
if ( gameGrid [ grid . y ] [ grid . x ] . direction2 !== undefined ) {
DrawSingleConveyor ( x , y , dir , ctx , scale )
DrawSingleConveyor ( x , y , gameGrid [ grid . y ] [ grid . x ] . direction2 , ctx , scale )
} else
{
var connections = { }
if ( grid . x > 0 ) { if ( HasRelativeConnection ( grid . x - 1 , grid . y + 0 , RIGHT ) ) { connections [ LEFT ] = true } }
if ( grid . x < gameGrid [ grid . y ] . length - 1 ) { if ( HasRelativeConnection ( grid . x + 1 , grid . y + 0 , LEFT ) ) { connections [ RIGHT ] = true } }
if ( grid . y > 0 ) { if ( HasRelativeConnection ( grid . x + 0 , grid . y - 1 , DOWN ) ) { connections [ UP ] = true } }
if ( grid . y < gameGrid . length - 1 ) { if ( HasRelativeConnection ( grid . x + 0 , grid . y + 1 , UP ) ) { connections [ DOWN ] = true } }
DrawConnectedConveyor ( x , y , ctx , connections , dir , scale )
DrawConnectedConveyor ( x , y , ctx , connections , dir , scale , 1 )
}
}
function DrawDirectionalColorText ( x , y , color1 , color2 , dir , ctx ) {
ctx . font = "bold 12px 'Profont','Courier New', serif"
ctx . fillStyle = "black"
ctx . textAlign = "center"
switch ( dir ) {
case UP : {
ctx . fillText ( color1 , x + 12 , y - 8 )
ctx . fillText ( color2 , x - 12 , y - 8 )
} break ;
case DOWN : {
ctx . fillText ( color1 , x - 12 , y + 14 )
ctx . fillText ( color2 , x + 12 , y + 14 )
} break ;
case RIGHT : {
ctx . fillText ( color1 , x + 12 , y + 4 + 12 )
ctx . fillText ( color2 , x + 12 , y + 4 - 12 )
} break ;
case LEFT : {
ctx . fillText ( color1 , x - 10 , y + 4 - 12 )
ctx . fillText ( color2 , x - 10 , y + 4 + 12 )
} break ;
}
}
function RenderIcon ( x , y , ctx , icon _definition , dir = 0 , background = undefined , renderToGrid = undefined , scale = 1 ) {
if ( background !== undefined ) {
ctx . fillStyle = background
ctx . fillRect ( x , y , 32 , 32 )
}
if ( icon _definition . img === ID _CONVEYOR ) {
RenderConveyor ( x , y , ctx , icon _definition , dir , background , renderToGrid , scale )
} else {
if ( icon _definition . img === ID _BRANCH ) {
drawImage (
x + 16 ,
y + 16 ,
icon _definition . img , ctx , dir * 90 , scale )
} else {
drawImage (
x + 16 ,
y + 16 ,
icon _definition . img , ctx , dir * 90 - 90 , scale )
}
switch ( icon _definition . img ) {
case ID _BRANCH : {
drawImage (
x + 16 ,
y + 16 ,
GetArrowImage ( icon _definition . color1 ) , ctx , dir * 90 + 0 , scale )
drawImage (
x + 16 ,
y + 16 ,
GetArrowImage ( icon _definition . color2 ) , ctx , dir * 90 + 180 , scale )
DrawDirectionalColorText ( x + 16 , y + 16 , icon _definition . color1 , icon _definition . color2 , dir , ctx )
} break ;
case ID _WRITER : {
drawImage (
x + 16 ,
y + 16 ,
GetDotImage ( icon _definition . color1 ) , ctx , dir * 90 - 90 , scale )
ctx . font = "bold 12px 'Profont','Courier New', serif"
var darkcolors = [ PURPLE , GRAY , BLACK ]
if ( darkcolors . includes ( icon _definition . color1 ) ) {
ctx . fillStyle = "rgb(210,210,210)"
} else {
ctx . fillStyle = "black"
}
ctx . textAlign = "center"
ctx . fillText ( icon _definition . color1 , x + 16 , y + 16 + 4 )
} break ;
}
}
}
function DisplayMenu ( x , y , menu , ctx ) {
ctx . fillStyle = "#20424a"
var totalRowHeight = Math . floor ( menu . levels . length / menu . cols ) * 24 + 16 + 24
ctx . fillRect ( x - menu . width / 2 , y , menu . width , totalRowHeight )
ctx . font = "bold 16px 'Zilla Slab', serif"
ctx . fillStyle = "white"
ctx . textAlign = "center"
ctx . fillText ( menu . title , x , y + 16 )
ctx . beginPath ( )
ctx . lineWidth = 1 ;
ctx . strokeStyle = "gray"
ctx . moveTo ( x - menu . width / 2 + 4 , y + 18 )
ctx . lineTo ( x + menu . width / 2 - 4 , y + 18 )
ctx . stroke ( )
for ( var i = 0 ; i < menu . levels . length ; i ++ ) {
ctx . font = "16px 'Zilla Slab', serif"
ctx . fillStyle = "white"
ctx . textAlign = "left"
var col = i % menu . cols
var row = Math . floor ( i / menu . cols )
var colWidth = menu . width / menu . cols
var levelBoxBounds = { x : x + col * colWidth - ( ( menu . cols !== 1 ) ? colWidth : colWidth / 2 ) , y : y + 24 + 24 * row , w : colWidth , h : 16 }
if ( MouseOverBounds ( levelBoxBounds ) ) {
if ( MOUSEDOWN && ALLOWEDTOCLICK ) {
MOUSEDOWN = false
ALLOWEDTOCLICK = false
loadStage ( menu . levels [ i ] )
gameState = WAITING
}
ctx . fillStyle = "rgb(132, 186, 133)"
ctx . fillRect ( levelBoxBounds . x , levelBoxBounds . y , levelBoxBounds . w , levelBoxBounds . h )
}
ctx . fillStyle = "white"
if ( LevelIsBeat ( menu . levels [ i ] . name ) ) {
drawImage ( x + col * colWidth + 16 - ( ( menu . cols !== 1 ) ? colWidth : colWidth / 2 ) - 8 , y + 32 + 24 * row , ID _COMPLETE _STAR , ctx , 0 , 0.5 )
}
ctx . fillText ( menu . levels [ i ] . name , x + col * colWidth - ( ( menu . cols !== 1 ) ? colWidth / 2 : 0 ) + 20 - colWidth / 2 , y + 16 + 24 + 24 * row )
if ( LevelIsBeat ( menu . levels [ i ] . name ) ) {
ctx . font = "12px 'Profont','Courier New', serif"
ctx . fillStyle = "white"
ctx . textAlign = "center"
ctx . fillText ( completedStages [ menu . levels [ i ] . name ] . score , x + col * colWidth - ( ( menu . cols !== 1 ) ? colWidth / 2 : 0 ) + colWidth / 2 - 16 , y + 16 + 24 + 24 * row )
}
}
}
function MouseOverBounds ( bounds ) {
return ( LAST _MOUSE _X >= 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 < tooltipSplit . length ; i ++ ) {
if ( i === 0 ) {
ctx . font = "bold 12px 'Zilla Slab', serif"
} else {
ctx . font = "12px 'Zilla Slab', serif"
}
ctx . fillText ( tooltipSplit [ i ] , tooltipBounds . x + 8 , tooltipBounds . y + i * 16 + 16 )
}
ctx . globalAlpha = 1
}
function RenderMenu ( ctx ) {
if ( MENU . visible ) {
ctx . fillStyle = "#20424a"
ctx . fillRect ( 0 , canvas . height * 0.8 , canvas . width , canvas . height * 0.2 )
var buttonX = 16
var buttonY = canvas . height * 0.8 + 16
var mouseOverButton = undefined
var mousedOver = false
for ( var button of MENU . buttons ) {
button . x = buttonX
button . y = buttonY
button . w = 32
button . h = 32
if ( ButtonIsUnlocked ( button ) ) {
if ( mouseOverButton === undefined && isMouseOverButton ( button ) ) {
mousedOver = true
if ( MOUSEOVERTIME === - 1 ) {
MOUSEOVERTIME = new Date ( ) . getTime ( )
} else
if ( MOBILE && new Date ( ) . getTime ( ) - MOUSEOVERTIME >= 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
}