Last active
August 29, 2015 13:58
-
-
Save magalhini/9994624 to your computer and use it in GitHub Desktop.
Mars Challenge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Mars Challenge | |
* | |
* @info | |
* | |
* Each setPosition() creates a new robot instance. | |
* Moves can be a one liner or separate calls to go(). | |
* | |
* Robots will ignore an instruction if a previous robot died on the same spot. | |
* They're smart, but I still wouldn't feel too comfortable sending them to outer space | |
* just yet. | |
* | |
* @usage | |
* | |
* mars.generateGrid(5 2); // grid size (5x2) | |
* mars.setPosition('2 1 E'); // set initial position of the robot (E == East) | |
* mars.go("FRLFLR"); // set of instructions (left, forward, right) | |
* | |
* @other | |
* | |
* mars.getCurrentPosition() // returns current grid position | |
* mars.getPosition() // mostly for debug, returns current position value | |
* mars.viewMatrix() // prints the grid | |
*/ | |
(function (doc) { | |
var mars = { | |
grid: [], | |
robots: -1, | |
position: null, | |
direction: null, | |
deathTrail: [], | |
allowedDirections: ['N', 'E', 'S', 'W'], | |
preventForwardMove: false, | |
/** | |
* Adds DOM events. | |
* | |
* @return null | |
*/ | |
initialize: function () { | |
this._message = doc.querySelectorAll('.output')[0]; | |
this._cmdsWrapper = doc.querySelectorAll('.commands')[0]; | |
this._inputGrid = doc.querySelectorAll('.grid-size')[0]; | |
this._startButton = doc.querySelectorAll('.start')[0]; | |
this._inputPosition = doc.querySelectorAll('.initial-pos')[0]; | |
this._inputCmds = doc.querySelectorAll('.instructions')[0]; | |
this._goBtn = doc.querySelectorAll('.go')[0]; | |
this._startButton.addEventListener('click', function (e) { | |
var value = this._inputGrid.value; | |
if (value) { | |
// Valid grid? Display next menu | |
if (this.generateGrid(value)) { | |
this._cmdsWrapper.style.display = 'block'; | |
this._hideStart(); | |
} | |
} else { | |
this.updateMessage("Invalid grid values, human!"); | |
} | |
}.bind(this)); | |
this._goBtn.addEventListener('click', function () { | |
var pos = this._inputPosition.value, | |
cmds = this._inputCmds.value; | |
if (pos && cmds) { | |
this.setPosition(pos); | |
this.go(cmds); | |
} | |
}.bind(this)); | |
}, | |
/** | |
* Generate the planet matrix. | |
* @param {String} matrix A string with upper-right coordinates (eg: '5 3') | |
* @return {Array} | |
*/ | |
generateGrid: function (matrix) { | |
this.grid.length = 0; | |
matrix = this._stripSpaces(matrix); | |
var xLength = parseInt(matrix[0], 10), | |
yLength = parseInt(matrix[1], 10), | |
i = 0, | |
y = 0, | |
col = []; | |
if (xLength > 50 || yLength > 50 || isNaN(xLength) || isNaN(yLength)) { | |
this.updateMessage('Invalid length or invalid grid! Try "5 4"'); | |
return false; | |
} | |
for (i; i < xLength + 1; i += 1) { | |
col.push('x'); | |
for (y; y < yLength + 1; y += 1) { | |
this.grid[y] = col; | |
} | |
} | |
this.grid.reverse(); | |
return this.grid; | |
}, | |
/** | |
* Returns the current position on the grid. | |
* @return {String} The current position. | |
*/ | |
getCurrentPosition: function () { | |
return this.position; | |
}, | |
/** | |
* Returns the value of the current position in the matrix | |
* (useful in debug mode, for a visual feel) | |
* | |
* @param {String} coords The coordinates to get from. | |
* @return {String} The value on the grid. | |
*/ | |
getPosition: function (coords) { | |
var x, y; | |
if (!coords) { | |
x = parseInt(this.position[0], 10); | |
y = parseInt(this.position[1], 10); | |
} else { | |
coords = coords.split(''); | |
x = parseInt(coords[0], 10), | |
y = parseInt(coords[1], 10); | |
} | |
if (this.grid[y] == null || this.grid[x] == null) { | |
return false; | |
} | |
return this.grid[y][x]; | |
}, | |
/** | |
* Gets the current direction. | |
* | |
* @return {String} The direction letter. | |
*/ | |
getDirection: function () { | |
return this.direction; | |
}, | |
/** | |
* Sets initial position and direction of the robot. | |
* @param {String} coords The position and direction (eg: 3 2 E) | |
*/ | |
setPosition: function (coords) { | |
coords = coords.toUpperCase(); | |
coords = this._stripSpaces(coords); | |
this._prepareRobot(); | |
this._validateLength.apply(coords, [3]); | |
this._validateDirection(coords[2]); | |
this.direction = coords[2]; | |
this.position = coords; | |
// Converting faux numbers (strings) to numbers (not the prettiest way) | |
// and leaving direction (actual strings) alone. | |
this.position = this.position.map(function (item) { | |
var val = (!!parseInt(item, 10) || parseInt(item, 10) === 0) ? parseInt(item, 10) : item; | |
return val; | |
}); | |
}, | |
/** | |
* Visually print the current matrix to the console. | |
* @debug | |
*/ | |
viewMatrix: function () { | |
this.grid.reverse().forEach(function (column, i) { | |
console.log(column); | |
}); | |
}, | |
/** | |
* Process a set of instructions. | |
* | |
* @param {String} string The instructions command (eg: 'LRRLFF') | |
* @return {String} The current direction | |
*/ | |
go: function (string) { | |
string = string.toUpperCase(); | |
var i = 0, | |
len = string.length; | |
for (i; i < len; i += 1) { | |
switch (string[i]) { | |
case "R": | |
this.turnRight(); | |
break; | |
case "L": | |
this.turnLeft(); | |
break; | |
case "F": | |
this._move(); | |
break; | |
default: | |
this.updateMessage('Unknown instruction, human!', 'neutral'); | |
break; | |
} | |
} | |
// If it still retains a position in the grid, it survived! | |
if (this.getPosition()) { | |
this.updateMessage('Woohoo, not dead! @ ' + this.getCurrentPosition(), 'happy'); | |
} | |
return this.getDirection(); | |
}, | |
/** | |
* Turns the robot left. | |
* | |
* @return {String} New direction. | |
*/ | |
turnLeft: function () { | |
var cIndex = this.allowedDirections.indexOf(this.getDirection()), | |
newDirection = this.allowedDirections[cIndex - 1] ? this.allowedDirections[cIndex - 1] : this.allowedDirections[this.allowedDirections.length - 1]; | |
this.direction = newDirection; | |
this._updateDirection(); | |
return newDirection; | |
}, | |
/** | |
* Turns the robot right. | |
* | |
* @return {String} New direction. | |
*/ | |
turnRight: function () { | |
var cIndex = this.allowedDirections.indexOf(this.getDirection()), | |
newDirection = this.allowedDirections[cIndex + 1] ? this.allowedDirections[cIndex + 1] : this.allowedDirections[0]; | |
this.direction = newDirection; | |
this._updateDirection(); | |
return newDirection; | |
}, | |
/** | |
* Updates the output for the user. | |
* | |
* @param msg {String} The message | |
* @param mood {String} Mood (face) to display | |
*/ | |
updateMessage: function (msg, mood) { | |
if (msg) { | |
var newMsg = doc.createElement('p'); | |
newMsg.innerHTML = msg; | |
this._message.appendChild(newMsg); | |
} | |
if (mood) { this._showFace(mood); } | |
}, | |
/*************************/ | |
/*** PRIVATE FUNCTIONS ***/ | |
/*************************/ | |
/** | |
* Move forward, soldier! | |
* Moves the robot, checking if it's a deadly move and storing it's last | |
* position, in case it dies. | |
* | |
* @return {String} The current direction, after the move. | |
*/ | |
_move: function () { | |
var dir = this.getDirection(), | |
deathCheck = this._checkIfImDead.bind(this); | |
this._checkForDeadlyMove(dir); | |
// Store last alive position | |
if (!deathCheck() && !this.preventForwardMove) { | |
this._lastPosition = this.getCurrentPosition().slice(0); | |
} else { | |
return false; | |
} | |
if (dir === "W") this.position[0] = this.position[0] - 1; | |
else if (dir === "E") this.position[0] = this.position[0] + 1; | |
else if (dir === "S") this.position[1] = this.position[1] - 1; | |
else if (dir === "N") this.position[1] = this.position[1] + 1; | |
this._checkForDeadlyMove(dir); | |
if (deathCheck()) { | |
this._killRobot(); | |
} | |
return this.getDirection(); | |
}, | |
/** | |
* Prepare another robot. | |
*/ | |
_prepareRobot: function () { | |
this.robots += 1; | |
this._goBtn.innerHTML = "Deploy robot #" + (this.robots+1); | |
this.deathTrail.push({deathAt: null, movedTo: null}); | |
}, | |
/** | |
* Flags if no position is returned, meaning the robot already fell. | |
*/ | |
_checkIfImDead: function () { | |
if (!this.getPosition()) { | |
this.updateMessage('LOST @ ' + this.getCurrentPosition()); | |
return true; | |
} | |
}, | |
/** | |
* Kills the robot and stores its last position in the death trail. | |
* | |
*/ | |
_killRobot: function () { | |
if (!this.deathTrail[this.robots].deathAt) { | |
this.updateMessage('Killing robot #' + this.robots, 'sad'); | |
this.deathTrail[this.robots].deathAt = this._lastPosition; | |
this.deathTrail[this.robots].movedTo = this.getDirection(); | |
} | |
return false; | |
}, | |
/** | |
* Checks to see if any previous robot died at that point. If the move | |
* is the same, then prevent the robot from dying. | |
* | |
* @return null | |
*/ | |
_checkForDeadlyMove: function () { | |
var self = this; | |
this.deathTrail.forEach(function (deaths) { | |
if (deaths.deathAt) { | |
if (self.getCurrentPosition().toString() === deaths.deathAt.toString()) { | |
self.preventForwardMove = true; | |
self.updateMessage('Death spot! Preventing a deadly move...', 'neutral'); | |
} else { | |
self.preventForwardMove = false; | |
} | |
} | |
}); | |
}, | |
/** | |
* Store new direction value. | |
*/ | |
_updateDirection: function () { | |
this.position[2] = this.getDirection(); | |
}, | |
_hideStart: function () { | |
doc.querySelectorAll('.start-grid')[0].style.display = 'none'; | |
}, | |
/** | |
* Shows a robot face. | |
* @param mood {String} The mood to display. | |
*/ | |
_showFace: function (mood) { | |
this._moods = this._moods || doc.querySelectorAll('pre'); | |
Array.prototype.forEach.call(this._moods, function (item) { | |
if (item.classList[0] === mood) { | |
item.style.display = 'block'; | |
} else { | |
item.style.display = 'none'; | |
} | |
}); | |
}, | |
/** | |
* Trim white spaces from instructions. | |
**/ | |
_stripSpaces: function (string) { | |
return string.split(' ').filter(function (str) { | |
return (/\S/).test(str); | |
}); | |
}, | |
_validateLength: function (len) { | |
this.forEach(function (val) { | |
if (val > mars.grid.length) { | |
mars.updateMessage('Invalid grid position, human!'); | |
throw 'Error'; | |
} | |
}); | |
if (this.length !== len) { | |
throw 'Error: number of arguments mismatch:' + len; | |
} | |
}, | |
_validateDirection: function (str) { | |
if (this.allowedDirections.indexOf(str) === -1) { | |
throw "Error: invalid direction argument given"; | |
} | |
} | |
}; | |
console.clear(); | |
mars.initialize(); | |
}(document)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta name="description" content="Mars Challenge" /> | |
<meta charset="utf-8"> | |
<title>Mars Challenge</title> | |
</head> | |
<body> | |
<h2>Mars Challenge</h2> | |
<pre class="neutral"> | |
_______ | |
_/ \_ | |
/ | | \ | |
/ |_^ __| \ | |
|__/((o| |o))\__| | |
| | | | | |
|\ |_| /| | |
| \ / | | |
\| / ___ \ |/ | |
\ | _ | / | |
\_________/ | |
_|_____|_ | |
____|_________|____ | |
/ \ | |
</pre> | |
<pre class="sad"> | |
_______ | |
_/ \_ | |
/ | | \ | |
/ |__ __| \ | |
|__/((x| |x))\__| | |
| | | | | |
|\ |_| /| | |
| \ / | | |
\| / ___ \ |/ | |
\ | / _ \ | / | |
\_________/ | |
_|_____|_ | |
____|_________|____ | |
/ \ | |
</pre> | |
<pre class="happy"> | |
_______ | |
_/ \_ | |
/ | | \ | |
/ |_^ ^_| \ | |
|__/((O| |O))\__| | |
| | | | | |
|\ |_| /| | |
| \ / | | |
\| / \___/ \ |/ | |
\ | _ | / | |
\_________/ | |
_|_____|_ | |
____|_________|____ | |
/ \ | |
</pre> | |
<div class="content"> | |
<div class="start-grid"> | |
<span>Grid size:</span><input class="grid-size" value="" placeholder="Grid size, ex: 5 4" type="text"> | |
<button class="start">Start!</button> | |
</div> | |
<div class="commands"> | |
<span>Initial position:</span><input type="text" placeholder="ex: 2 1 E" class="initial-pos"> | |
<div class="clear"></div> | |
<span>Instructions:</span><input type="text" value="" placeholder="ex: RRFFF" class="instructions"> | |
<button class="go">Deploy robot</button> | |
</div> | |
<div class="clear"></div> | |
<div class="output"> | |
<p></p> | |
</div> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
width: 23em; | |
margin: auto; | |
font-family: sans-serif; | |
font-size: 1.2em; | |
} | |
h2 { | |
text-align: center; | |
} | |
.commands { | |
float: left; | |
clear: both; | |
display: none; | |
} | |
.sad, | |
.happy { | |
display: none; | |
} | |
button { | |
padding: 1em; | |
color: #fff; | |
font-weight: bold; | |
cursor: pointer; | |
height: 3em; | |
border: none; | |
background: rgb(163,100,120); | |
} | |
.go { | |
margin-top: 2em; | |
width: 100%; | |
} | |
input, | |
span { | |
width: 10em; | |
} | |
span { | |
float: left; | |
line-height: 1.6; | |
} | |
.clear {clear: both; margin: 2em;} | |
.output { | |
float: left; | |
font-family: monospace; | |
text-transform: uppercase; | |
} | |
input { | |
float: left; | |
padding: .5em; | |
height: 1.6em; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment