Skip to content

Instantly share code, notes, and snippets.

@MatthewBarker
Last active December 23, 2016 10:23
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 MatthewBarker/f9d4e0906a2e1cece3c97c503d593c22 to your computer and use it in GitHub Desktop.
Save MatthewBarker/f9d4e0906a2e1cece3c97c503d593c22 to your computer and use it in GitHub Desktop.
Risk
svg {
background-color: #eeeeee;
}
text {
font-size: 50%;
}
.connector path {
fill: none;
stroke: #1F1A17;
stroke-dasharray: 2, 2;
stroke-width: 1;
}
.territory path {
fill: none;
stroke: #1F1A17;
stroke-width: 1;
}
.continents .continent .territory .selected {
fill: #ffffff;
}
.continents .continent .territory .neighbour {
fill: #999999;
}
.continents g:nth-child(1) .territory path {
fill: #9d7902;
}
.continents g:nth-child(2) .territory path {
fill: #89db8c;
}
.continents g:nth-child(3) .territory path {
fill: #47c1dc;
}
.continents g:nth-child(4) .territory path {
fill: #d9dd23;
}
.continents g:nth-child(5) .territory path {
fill: #a914ac;
}
.continents g:nth-child(6) .territory path {
fill: #ea4224;
}
.territory circle {
stroke: #1F1A17;
stroke-width: 1;
}
.territory.player--1 circle {
fill: #ff0000;
}
.territory.player--2 circle {
fill: #00ff00;
}
.territory.player--3 circle {
fill: #0000ff;
}
.territory text {
fill: #ffffff;
stroke: none;
}
<!-- ko if: loading -->
Loading...
<!-- /ko -->
<!-- ko ifnot: loading -->
<svg xmlns="http://www.w3.org/2000/svg" data-bind="with: board">
<g class="connectors" data-bind="foreach: connectors">
<g class="connector" data-bind="d3: { script: draw }"></g>
</g>
<g class="continents" data-bind="foreach: continents">
<g class="continent" data-bind="foreach: territories">
<g class="territory" data-bind="d3: { script: draw }, css: css"></g>
</g>
</g>
</svg>
<div data-bind="text: message"></div>
<!-- /ko -->
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
ko.bindingHandlers.d3 = {
update: function(element, valueAccessor) {
var target = d3.select(element);
var value = ko.utils.unwrapObservable(valueAccessor());
value.script(target, value.options);
}
};
var boardMapping = {
'continents': {
create: function(options) {
var territories = [];
options.data.territories.forEach(function(territory) {
territories.push(new TerritoryViewModel(territory));
});
options.data.territories = territories;
return options.data;
}
},
'connectors': {
create: function(options) {
return new ConnectorViewModel(options.data);
}
}
};
var ConnectorViewModel = function(data) {
var self = this;
this.path = data.path;
this.draw = function(target) {
// Joins
var path = target.selectAll('path').data([self]);
// Insert path
path.enter()
.append('path')
.attr('d', self.path);
};
};
var TerritoryViewModel = function(data) {
var self = this;
this.name = data.name;
this.neighbours = data.neighbours;
this.player = ko.observable();
this.path = data.path;
this.selection = ko.observable().syncWith('selection');
this.infantry = ko.observable(0);
this.css = ko.computed(function() {
return self.player() ? 'player--' + self.player().playerNumber : '';
});
this.draw = function(target) {
// Joins
var path = target.selectAll('path').data([self]);
var circle = target.selectAll('circle').data([self]);
var text = target.selectAll('text').data([self]);
// Insert path
path.enter()
.append('path')
.attr('d', self.path)
.on('click', self.select)
.append('title')
.text(self.name);
var bounds = target.select('path').node().getBBox();
// Update path
path.classed('selected', self.isSelected())
.classed('neighbour', self.isNeighbour());
// Insert circle
circle.enter()
.append('circle')
.attr('cx', bounds.x + (bounds.width / 2))
.attr('cy', bounds.y + (bounds.height / 2))
.attr('r', 10)
.on('click', self.select);
// Update circle
circle.transition()
.attr('r', 10 + (self.isSelected() ? 5 : 0));
// Insert text
text.enter()
.append('text')
.attr('dx', bounds.x + (bounds.width / 2))
.attr('dy', bounds.y + (bounds.height / 2))
.attr('text-anchor', 'middle')
.on('click', self.select);
// Update text
text.text(self.infantry());
};
this.select = function() {
self.selection(self.name);
};
this.isSelected = ko.computed(function() {
return self.name === self.selection();
});
this.isNeighbour = ko.computed(function() {
return self.neighbours.indexOf(self.selection()) > -1;
});
};
var PlayerViewModel = function(infantry, playerNumber) {
this.infantry = infantry;
this.playerNumber = playerNumber;
};
var GameViewModel = function() {
var self = this;
this.board = ko.observable();
this.loading = ko.observable(true);
this.message = ko.observable();
this.currentPlayerNumber = ko.observable();
this.players = ko.observableArray();
this.url = 'http://rawgit.com/MatthewBarker/353120d430bd75bb9f70765b944bc82f/raw/e87df7c2b5520110e601419d69a25a11652a3f59/risk.json';
this.infantryAllocation = function() {
return 20 + (5 * (6 - self.players.length));
};
this.currentPlayer = ko.computed(function() {
return self.players()[self.currentPlayerNumber() - 1];
});
this.updateNextPlayer = function() {
var nextPlayerNumber = self.currentPlayerNumber() + 1;
if (nextPlayerNumber > self.players().length) {
nextPlayerNumber = 1;
}
self.currentPlayerNumber(nextPlayerNumber);
};
this.createPlayers = function() {
var numberPlayers = 3;
var infantryAllocation = self.infantryAllocation();
while (self.players().length < numberPlayers) {
var player = new PlayerViewModel(infantryAllocation, self.players().length + 1);
self.players.push(player);
}
self.currentPlayerNumber(randomNumber(1, numberPlayers));
};
this.territories = function() {
var territories = [];
self.board().continents().forEach(function(continent) {
continent.territories.forEach(function(territory) {
territories.push(territory);
});
});
return territories;
};
this.fillUnclaimed = function() {
var unclaimed = ko.utils.arrayFilter(self.territories(), function(territory) {
return !territory.infantry();
});
var claimed = unclaimed[randomNumber(0, unclaimed.length - 1)];
claimed.player(self.currentPlayer());
claimed.infantry(1);
self.currentPlayer().infantry--;
if (unclaimed.length > 1) {
self.updateNextPlayer();
self.fillUnclaimed();
}
};
this.setup = function() {
self.createPlayers();
self.fillUnclaimed();
};
this.onLoad = function(data) {
self.board(ko.mapping.fromJS(data, boardMapping));
self.loading(false);
self.setup();
};
this.onError = function(error) {
console.log(error);
self.loading(false);
};
this.load = function() {
d3.json(self.url)
.on('error', self.onError)
.get(self.onLoad);
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment