Last active
August 29, 2015 14:07
-
-
Save stujo/6e25eac36e7ebaf3ad12 to your computer and use it in GitHub Desktop.
JavaScript Racing Game
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
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); | |
} | |
}; |
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
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); | |
} | |
}; |
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
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)); | |
}; | |
} | |
} | |
} | |
}; |
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
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(); | |
} | |
} |
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 { | |
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; | |
} |
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
<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> |
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
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