Skip to content

Instantly share code, notes, and snippets.

@magalhini
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save magalhini/9994624 to your computer and use it in GitHub Desktop.
Save magalhini/9994624 to your computer and use it in GitHub Desktop.
Mars Challenge
/**
* 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));
<!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>
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