cettlers-of-satan
Version:
Shared client/server code for Cettlers of Satan, a dodgy remake of a popular board game.
177 lines (146 loc) • 4.09 kB
JavaScript
var util = require('./util.js');
class Grid {
constructor(n) {
util.assertType(n, 'number', 'n');
// TODO(guy): Generate an actual map. For now we just generate a size-n
// hex grid with random types and values for the tiles.
var y = 0;
var len = n;
this.tiles = [];
for (var i = 0; i < 2*n - 1; i++) {
for (var x = 0; x < len; x++) {
this.tiles.push(new Tile(TileTypes.DESERT,
8, new Hex(x, y)));
}
y++;
if (i < n-1) {
len++;
} else {
len--;
}
}
this.roads = [];
var seen = new util.PairSet();
for (var a of this.tiles) {
for (var b of this.tiles) {
if (!a.hex.adjacentTo(b.hex)) {
continue
}
var ah = a.hex;
var bh = b.hex;
if (seen.has(ah, bh) || seen.has(bh, ah)) {
continue;
}
this.roads.push(new Road(ah, bh));
seen.add(ah, bh);
}
}
}
}
// Cettlers of Satan uses cube coordinates for the hex grid.
// see https://www.redblobgames.com/grids/hexagons/#coordinates
//
// The basic idea is to have one coordinate for each of the 3 axes of symmetry
// of a hex, which we'll call "x, y, z". We then enforce the constraint that
// "x + y + z = 0" so that each point has a unique representation. Here we
// only store the "x, y" part of this representation.
class Hex {
constructor(x, y) {
util.assertType(x, 'number', 'x');
util.assertType(y, 'number', 'y');
this.x = x;
this.y = y;
}
move(dx, dy) {
util.assertType(dx, 'number', 'dx');
util.assertType(dy, 'number', 'dy');
this.x = this.x + dx;
this.y = this.y + dy;
}
neighbours() {
return [
new Hex(this.x + 1, this.y),
new Hex(this.x - 1, this.y),
new Hex(this.x, this.y + 1),
new Hex(this.x, this.y - 1),
new Hex(this.x + 1, this.y - 1),
new Hex(this.x - 1, this.y + 1),
];
}
// adjacentTo returns true if this hex shares an edge with the given hex.
adjacentTo(h) {
util.assertInstance(h, Hex, 'h');
var dx = this.x - h.x;
var dy = this.y - h.y;
return (dx === 0 && (dy === -1 || dy === 1)) ||
(dy === 0 && (dx === -1 || dx === 1)) ||
(dx === -1 && dy === 1) ||
(dx === 1 && dy === -1);
}
equals(h) {
return (this.x === h.x) && (this.y === h.y);
}
}
class Road {
constructor(a, b) {
util.assertInstance(a, Hex, 'a');
util.assertInstance(b, Hex, 'b');
this.a = a;
this.b = b;
this.owner = 0;
this.owned = false;
}
// incidentWith returns true if this road shares an endpoint with the given one.
// This is true if and only if they share a hex and the other two hexes are
// adjacent.
incidentWith(r) {
if (!(this.a.equals(r.a) || this.a.equals(r.b)) &&
!(this.b.equals(r.a) || this.b.equals(r.b))) {
return false;
}
if (this.a.equals(r.a)) {
return this.b.adjacentTo(r.b);
}
if (this.a.equals(r.b)) {
return this.b.adjacentTo(r.a);
}
if (this.b.equals(r.a)) {
return this.a.adjacentTo(r.b);
}
if (this.b.equals(r.b)) {
return this.a.adjacentTo(r.a);
}
return false;
}
equals(r) {
return (this.a.equals(r.a) && this.b.equals(r.b)) ||
(this.a.equals(r.b) && this.b.equals(r.a));
}
}
class Tile {
constructor(tipe, value, hex) {
util.assertEnum(tipe, TileTypes, 'tipe');
util.assertType(value, 'number', 'value');
util.assertInstance(hex, Hex, 'hex');
if (value < 2 || value > 12) {
throw new Error('tile value must be in the interval [2, 12]');
}
this.tipe = tipe;
this.value = value;
this.hex = hex;
}
}
const TileTypes = Object.freeze({
EMPTY: { name: 'empty' },
BRICK: { name: 'brick' },
DESERT: { name: 'desert' },
ORE: { name: 'ore' },
SHEEP: { name: 'sheep' },
WHEAT: { name: 'wheat' },
WOOD: { name: 'wood' },
});
exports.Grid = Grid;
exports.Hex = Hex;
exports.Road = Road;
exports.Tile = Tile;
exports.TileTypes = TileTypes;