Skip to content

Instantly share code, notes, and snippets.

@stujo
Last active August 29, 2015 14:07
Show Gist options
  • Save stujo/6e25eac36e7ebaf3ad12 to your computer and use it in GitHub Desktop.
Save stujo/6e25eac36e7ebaf3ad12 to your computer and use it in GitHub Desktop.
JavaScript Racing Game
function App(playerCount, numSpaces) {
this._boardView = new BoardView('#board_container');
this._historyView = new HistoryView('#history_container');
this._graphView = new GraphView('#distribution_container');
this._playerCount = playerCount;
this._numSpaces = numSpaces;
this._history = [];
}
App.prototype = {
run: function() {
this.bindStarterButton();
//this.startNewRace();
},
getStarter: function() {
return document.querySelector("#start_button");
},
bindStarterButton: function() {
this.getStarter().addEventListener('click', this.startNewRace.bind(this));
},
startNewRace: function() {
this.model = new RaceModel(this._playerCount, this._numSpaces);
this._boardView.buildBoard(this.model);
this.resetPositions();
this.updateDisplay();
this.setTurnTimeout();
},
setTurnTimeout: function() {
setTimeout(this.takeTurn.bind(this), 300);
},
takeTurn: function() {
this.model.incrementPositions();
if (!this.isComplete()) {
this.setTurnTimeout();
} else {
this.addToHistory(this.model);
this._historyView.update(this._history);
this._graphView.update(this._history);
}
this.updateDisplay();
},
updateDisplay: function() {
this._boardView.updateDisplay(this.model);
},
isComplete: function() {
return this.model.isComplete();
},
resetPositions: function() {
this.model.resetPositions();
},
addToHistory: function(model) {
this._history.push(model);
}
};
function BoardView(board_selector) {
this.board_selector = board_selector;
this._colorFactory = d3.scale.category20(); //builtin range of colors
}
BoardView.prototype = {
getBoard: function() {
return document.querySelector(this.board_selector);
},
buildBoard: function(model) {
var board = this.getBoard();
var placeholder = board.querySelector('#board_placeholder');
var playerCount = model.getPlayerCount();
var numSpaces = model.getNumSpaces();
placeholder.innerHTML = "";
var table = document.createElement("table");
table.setAttribute("id", "racer_table");
for (var i = 0; i < playerCount; i++) {
var tr = document.createElement("tr");
tr.setAttribute("id", "player" + i + "_strip");
for (var j = -1; j < numSpaces; j++) {
var td = document.createElement("td");
td.appendChild(document.createTextNode(''));
tr.appendChild(td);
}
table.appendChild(tr);
}
placeholder.appendChild(table);
},
ensureBoard: function() {
var board = this.getBoard();
return board;
},
setStyleBasesOnToggle: function(node, toggle, className) {
var list = node.classList;
var hasStyleClass = list.contains(className);
if (toggle && !hasStyleClass) {
list.add(className);
} else if (!toggle && hasStyleClass) {
list.remove(className);
}
},
displayTurnCounter: function(model) {
var turnDisplay = document.querySelector("#turn_display");
turnDisplay.innerHTML = "Turn #" + model.getCurrentTurn();
},
updateStatusBar: function(model) {
var statusMessage = "Running...";
var statusBar = document.querySelector("#status_bar");
if (model.isComplete()) {
if (model.isTie()) {
statusMessage = "It's a draw!!";
} else {
statusMessage = "The winner is player " + model.getWinningPlayerId();
}
}
statusBar.innerHTML = statusMessage;
},
getPlayerColor : function(playerIndex)
{
return this._colorFactory(playerIndex + 2).toString();
},
updateDisplay: function(model) {
var board = this.ensureBoard();
var playerCount = model.getPlayerCount();
var tieColor = this.getPlayerColor(0);
for (var iPlayer = 0; iPlayer < playerCount; iPlayer++) {
var playerPosition = model.getPlayerPosition(iPlayer);
var player_strip = board.querySelector("#player" + iPlayer + "_strip");
var spaces = player_strip.querySelectorAll("td");
for (var iSpace = 0; iSpace < spaces.length; iSpace++) {
var space = spaces[iSpace];
var active = (iSpace == playerPosition);
if(iSpace <= playerPosition)
{
space.style.backgroundColor = this.getPlayerColor(iPlayer);
}
this.setStyleBasesOnToggle(space, active, 'active');
}
}
this.setStyleBasesOnToggle(board, model.isComplete(), 'complete');
this.setStyleBasesOnToggle(board, !model.isComplete(), 'running');
this.displayTurnCounter(model);
this.updateStatusBar(model);
}
};
function GraphView(graph_selector) {
this._graph_selector = graph_selector;
}
GraphView.prototype = {
graphData: function(historyArray) {
var model = null,
hasData = false,
i = 0;
var summary = [];
if (historyArray.length > 0) {
model = historyArray[0];
for (i = 0; i < model.getPlayerCount(); i++) {
summary.push({
"label": "Player" + i,
"value": 0
});
}
for (i = 0; i < historyArray.length; i++) {
model = historyArray[i];
if (model.isTie()) {
} else {
summary[model.getWinningPlayerId()].value++;
hasData = true;
}
}
}
if (!hasData) {
summary = [];
}
return summary;
},
update: function(historyArray) {
var historyGraph = d3.select(this._graph_selector).select(".pie_chart");
var graphDataArray = this.graphData(historyArray);
if (graphDataArray.length > 0) {
var color = d3.scale.category20(); //builtin range of colors
color(0);
var r = 100;
var textOffset = 14;
// arc definition
var arc = d3.svg.arc().outerRadius(r).innerRadius(r / 2);
//this will create pie data for us given a list of values
var pieData = d3.layout.pie().value(function(d) {
return d.value;
}).sort(null)(graphDataArray);
var paths = historyGraph.selectAll("path").data(pieData);
paths.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("transform", "translate(" + r + ", " + r + ")")
.attr("d", arc)
.each(function(d) {
this._current = d;
});
paths.transition()
.attrTween("d", arcTween);
paths.exit().remove();
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
}
}
};
function HistoryView(history_selector) {
this._history_selector = history_selector;
}
HistoryView.prototype = {
update: function(historyArray) {
var historyList = document.querySelector(this._history_selector);
historyList.innerHTML = "";
if (historyArray.length > 0) {
for (var i = historyArray.length - 1; i >= 0; i--) {
var div = document.createElement("div");
div.appendChild(document.createTextNode(this.gameSummary(historyArray[i])));
historyList.appendChild(div);
}
}
},
gameSummary: function(model) {
var message = "";
if (model.isComplete()) {
if (model.isTie()) {
message = "Tie after";
} else {
message = "Won by Player " + model.getWinningPlayerId() + " after";
}
} else {
message = "In Progress... at";
}
return message + " turn #" + model.getCurrentTurn();
}
}
body {
font-family: sans-serif;
background-color: #D62728;
background-image: url(http://clients.stujophoto.com/photos/i-6JGC9Pj/3/L/i-6JGC9Pj-L.jpg);
background-size: 100%;
}
.heading {
font-size: 1.5em;
border-bottom: 1px solid #333;
}
.container {
margin: 20px;
padding: 10px;
background-color: #fff;
opacity: 0.9;
border-radius: 10px;
}
#racer_table {
border: 2px solid black;
margin: 10px auto;
padding: 10px;
width: 90%;
}
#board_container.complete #racer_table {
border: 2px solid lightgreen;
opacity: 0.8;
}
#racer_table tr {
height: 1em;
}
#racer_table td {
border: 1px solid #999;
opacity: 0.5;
}
#racer_table td.active {
opacity: 1.0;
border: 1px solid #333;
}
#player2_strip td.active {
background-color: green;
}
#player3_strip td.active {
background-color: yellow;
}
#board_placeholder {
min-height: 100px;
}
#board_controls {
width: 100%;
margin: 10px;
}
#board_container #start_button {
position: absolute;
left: 100px;
top: 90px;
}
#board_container.running #start_button {
display: none;
}
#board_controls button{
font-size: 2em;
border-radius: 5px;
}
#history_controls {
width: 100%;
}
#history_controls td {
vertical-align: top;
margin: 0px;
padding: 0px;
width: 50%;
}
.pie_chart {
margin: 10px;
height: 200px;
width: 200px;
}
#history_container {
min-height: 225px;
}
#history_container div {
padding-top: 10px;
}
#distribution_container {
min-height: 225px;
text-align: center;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="jsracer.css">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<div class="container">
<div id="board_container">
<div class="heading">JavaScript Racer</div>
<div id="board_placeholder"></div>
<table id="board_controls">
<tr>
<td>
<button id="start_button">Start</button>
</td>
<td>
<span id="turn_display"></span>
<span id="status_bar">Press Start to Race!</span>
</td>
</tr>
</table>
</div>
</div>
<table id="history_controls">
<tr>
<td>
<div class="container">
<div class="heading">Win Distribution</div>
<div id="distribution_container">
<svg class="pie_chart"></svg>
</div>
</div>
</td>
<td>
<div class="container">
<div class="heading">Race History</div>
<div id="history_container"></div>
</div>
</td>
</tr>
</table>
</body>
<script src="race_model.js"></script>
<script src="graph_view.js"></script>
<script src="history_view.js"></script>
<script src="board_view.js"></script>
<script src="app.js"></script>
<script>
window.addEventListener("load", function() {
var app = new App(10, 30);
app.run();
});
</script>
</html>
function RaceModel(playerCount, numSpaces) {
this.resetPositions();
this._playerCount = playerCount;
this._numSpaces = numSpaces;
}
RaceModel.prototype = {
resetPositions: function() {
this._currentTurn = 0;
this._positions = this.createPlayerArray();
this._isComplete = false;
this._diceSize = 4;
},
createPlayerArray: function() {
var players = [];
for (var iPlayer = 0; iPlayer < this.getPlayerCount(); iPlayer++) {
players.push(0);
}
return players;
},
isTie: function() {
return this.getWinningPlayerId() == -1;
},
getWinningPlayerId: function() {
var currentWinner = -1;
var currentWinningPos = -1;
for (var iPlayer = 0; iPlayer < this.getPlayerCount(); iPlayer++) {
if (this.getPlayerPosition(iPlayer) > currentWinningPos) {
currentWinner = iPlayer;
currentWinningPos = this.getPlayerPosition(iPlayer);
} else if (this.getPlayerPosition(iPlayer) == currentWinningPos) {
currentWinner = -1; // no clear winner
}
}
return currentWinner;
},
getCurrentTurn: function() {
return this._currentTurn;
},
getPlayerCount: function() {
return this._playerCount;
},
getNumSpaces: function() {
return this._numSpaces;
},
getPlayerPosition: function(playerId) {
return this._positions[playerId];
},
takeTurn: function() {
this.incrementPositions();
},
rollDice: function() {
return Math.floor(Math.random() * this._diceSize) + 1
},
incrementPositions: function() {
this._currentTurn++;
for (var iPlayer = 0; iPlayer < this.getPlayerCount(); iPlayer++) {
this.advancePlayer(iPlayer, this.rollDice());
}
if (this.isComplete()) {
}
},
advancePlayer: function(playerId, score) {
this._positions[playerId] += score;
if (this._positions[playerId] >= this._numSpaces) {
this._positions[playerId] = this._numSpaces;
this._isComplete = true;
}
return this._isComplete;
},
isComplete: function() {
return this._isComplete;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment