Longest road is now tracking
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
parent
5cc69d04bc
commit
30bb9870da
@ -37,11 +37,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Pip.Active {
|
.Pip.Active {
|
||||||
filter: drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
|
filter: drop-shadow(0px 0px 10px rgba(255, 0, 255, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.Pip.Active.Option {
|
.Pip.Active.Option {
|
||||||
filter: brightness(150%) drop-shadow(0px 0px 5px rgba(255, 255, 0, 0.9));
|
filter: brightness(150%) drop-shadow(0px 0px 10px rgba(255, 0, 255, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.Pips[disabled],
|
.Pips[disabled],
|
||||||
|
@ -48,9 +48,11 @@ const Placard = ({table, type, active}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildClicked = (event) => {
|
const buildClicked = (event) => {
|
||||||
|
if (!type.match(/^l.*/)) {
|
||||||
if (!table.state.buildActive) {
|
if (!table.state.buildActive) {
|
||||||
table.setState({ buildActive: true });
|
table.setState({ buildActive: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const roadClicked = (event) => {
|
const roadClicked = (event) => {
|
||||||
@ -1004,6 +1006,13 @@ class Table extends React.Component {
|
|||||||
<div className="Hand">
|
<div className="Hand">
|
||||||
{ development }
|
{ development }
|
||||||
</div>
|
</div>
|
||||||
|
{ game.longestRoad && game.longestRoad === game.color &&
|
||||||
|
<Placard
|
||||||
|
active={false}
|
||||||
|
type='longest-road'
|
||||||
|
table={this}
|
||||||
|
/>
|
||||||
|
}
|
||||||
<Placard
|
<Placard
|
||||||
active={this.state.buildActive}
|
active={this.state.buildActive}
|
||||||
disabled={!game || !game.turn || !game.turn.roll}
|
disabled={!game || !game.turn || !game.turn.roll}
|
||||||
@ -1042,36 +1051,6 @@ class Table extends React.Component {
|
|||||||
(!game.player || !game.player.mustDiscard) && <WaitingForPlayer table={this}/>
|
(!game.player || !game.player.mustDiscard) && <WaitingForPlayer table={this}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ game && game.showCards &&
|
|
||||||
<div className="Cards">
|
|
||||||
{ game && game.state === "active" && <>
|
|
||||||
<div>In hand</div>
|
|
||||||
|
|
||||||
<div>Available to play</div>
|
|
||||||
<div className="Hand">
|
|
||||||
<Development type="monopoly" count="1"/>
|
|
||||||
<Development type="army-" max="14" count="4"/>
|
|
||||||
<div className="Stack">
|
|
||||||
<Development type="vp-library" count="1"/>
|
|
||||||
<Development type="vp-market" count="1"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>Points</div>
|
|
||||||
<div className="Hand">
|
|
||||||
<div className="Stack">
|
|
||||||
<Development type="vp-library" count="1"/>
|
|
||||||
<Development type="vp-palace" count="1"/>
|
|
||||||
<Development type="army-" max="14" count="6"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="Hand">
|
|
||||||
<Placard type="largest-army"/>
|
|
||||||
<Placard type="longest-road"/>
|
|
||||||
</div>
|
|
||||||
</> }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{ this.state.error && <Paper className="Error"><div>{this.state.error}</div></Paper> }
|
{ this.state.error && <Paper className="Error"><div>{this.state.error}</div></Paper> }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -805,6 +805,185 @@ const getPrevPlayer = (game, name) => {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const processCorner = (game, color, cornerIndex, placedCorner) => {
|
||||||
|
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
||||||
|
if (placedCorner.color && placedCorner.color !== color) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* If this corner is already being walked, skip it */
|
||||||
|
if (placedCorner.walking) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
placedCorner.walking = true;
|
||||||
|
/* Calculate the longest road branching from both corners */
|
||||||
|
let longest = 0;
|
||||||
|
layout.corners[cornerIndex].roads.forEach(roadIndex => {
|
||||||
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
|
longest = Math.max(processRoad(game, color, roadIndex, placedRoad), longest);
|
||||||
|
});
|
||||||
|
|
||||||
|
return longest;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildCornerGraph = (game, color, cornerIndex, placedCorner, set) => {
|
||||||
|
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
||||||
|
if (placedCorner.color && placedCorner.color !== color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* If this corner is already being walked, skip it */
|
||||||
|
if (placedCorner.walking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
placedCorner.walking = true;
|
||||||
|
/* Calculate the longest road branching from both corners */
|
||||||
|
layout.corners[cornerIndex].roads.forEach(roadIndex => {
|
||||||
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
|
buildRoadGraph(game, color, roadIndex, placedRoad, set);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processRoad = (game, color, roadIndex, placedRoad) => {
|
||||||
|
/* If this road isn't assigned to the walking color, skip it */
|
||||||
|
if (placedRoad.color !== color) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this road is already being walked, skip it */
|
||||||
|
if (placedRoad.walking) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
placedRoad.walking = true;
|
||||||
|
/* Calculate the longest road branching from both corners */
|
||||||
|
let longest = 0;
|
||||||
|
layout.roads[roadIndex].corners.forEach(cornerIndex => {
|
||||||
|
const placedCorner = game.placements.corners[cornerIndex];
|
||||||
|
longest = Math.max(processCorner(game, color, cornerIndex, placedCorner), longest);
|
||||||
|
});
|
||||||
|
placedRoad.longest = 1 + longest;
|
||||||
|
|
||||||
|
return placedRoad.longest;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildRoadGraph = (game, color, roadIndex, placedRoad, set) => {
|
||||||
|
/* If this road isn't assigned to the walking color, skip it */
|
||||||
|
if (placedRoad.color !== color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* If this road is already being walked, skip it */
|
||||||
|
if (placedRoad.walking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
placedRoad.walking = true;
|
||||||
|
set.push(roadIndex);
|
||||||
|
/* Calculate the longest road branching from both corners */
|
||||||
|
layout.roads[roadIndex].corners.forEach(cornerIndex => {
|
||||||
|
const placedCorner = game.placements.corners[cornerIndex];
|
||||||
|
buildCornerGraph(game, color, cornerIndex, placedCorner, set)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearRoadMarkers = (game) => {
|
||||||
|
/* Clear out walk markers on roads */
|
||||||
|
layout.roads.forEach((item, itemIndex) => {
|
||||||
|
const placed = game.placements.roads[itemIndex];
|
||||||
|
placed.walking = false;
|
||||||
|
placed.longest = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Clear out walk markers on corners */
|
||||||
|
layout.corners.forEach((item, itemIndex) => {
|
||||||
|
const placed = game.placements.corners[itemIndex];
|
||||||
|
placed.walking = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateRoadLengths = (game, session) => {
|
||||||
|
clearRoadMarkers(game);
|
||||||
|
|
||||||
|
/* Clear out player longest road counts */
|
||||||
|
for (let key in game.players) {
|
||||||
|
game.players[key].roadLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build a set of connected road graphs. Once all graphs are
|
||||||
|
* constructed, walk through each graph, starting from each
|
||||||
|
* location in the graph. If the length ever equals the
|
||||||
|
* number of items in the graph, short circuit--longest path.
|
||||||
|
* Otherwise, check all paths from each segment. This is
|
||||||
|
* needed to catch loops where starting from an outside end
|
||||||
|
* point may result in not counting the length of the loop
|
||||||
|
*/
|
||||||
|
let graphs = [];
|
||||||
|
layout.roads.forEach((road, roadIndex) => {
|
||||||
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
|
if (placedRoad.color) {
|
||||||
|
let set = [];
|
||||||
|
buildRoadGraph(game, placedRoad.color, roadIndex, placedRoad, set);
|
||||||
|
if (set.length) {
|
||||||
|
graphs.push({ color: placedRoad.color, set });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(graphs);
|
||||||
|
|
||||||
|
let currentLength = game.longestRoad ? game.players[game.longestRoad].roadLength : -1,
|
||||||
|
currentLongest = game.longestRoad;
|
||||||
|
clearRoadMarkers(game);
|
||||||
|
graphs.forEach(graph => {
|
||||||
|
graph.set.forEach(roadIndex => {
|
||||||
|
const placedRoad = game.placements.roads[roadIndex];
|
||||||
|
clearRoadMarkers(game);
|
||||||
|
const length = processRoad(game, placedRoad.color, roadIndex, placedRoad);
|
||||||
|
game.players[placedRoad.color].roadLength =
|
||||||
|
Math.max(game.players[placedRoad.color].roadLength, length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkForTies = false;
|
||||||
|
if (currentLongest && game.players[game.currentLongest].roadLength < currentLength) {
|
||||||
|
addChatMessage(game, session, `${getPlayerNameFromColor(game, game.currentLongest)} had their longest road split!`);
|
||||||
|
checkForTies = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let longest = game.longestRoad ? game.players[game.longestRoad].roadLength : 4,
|
||||||
|
longestPlayers = [];
|
||||||
|
for (let key in game.players) {
|
||||||
|
if (game.players[key].status === 'Not active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (game.players[key].roadLength > longest) {
|
||||||
|
longestPlayers = [ key ];
|
||||||
|
longest = game.players[key].roadLength;
|
||||||
|
} else if (game.players[key].roadLength == longest && checkForTies) {
|
||||||
|
longestPlayers.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (longestPlayers.length > 0) {
|
||||||
|
if (longestPlayers.length === 1) {
|
||||||
|
if (game.longestRoad !== longestPlayers[0]) {
|
||||||
|
game.longestRoad = longestPlayers[0];
|
||||||
|
addChatMessage(game, session, `${playerNameFromColor(game, game.longestRoad)} now has the longest road (${longest})!`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (checkForTies) {
|
||||||
|
const names = longestPlayers.map(color => playerNameFromColor(color));
|
||||||
|
addChatMessage(game, session, `${names.join(', ')} are tied for longest road (${longest})!`);
|
||||||
|
}
|
||||||
|
game.longestRoad = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
game.longestRoad = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
const getValidCorners = (game, color, type) => {
|
const getValidCorners = (game, color, type) => {
|
||||||
const limits = [];
|
const limits = [];
|
||||||
|
|
||||||
@ -982,6 +1161,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
/* Any player can make an offer */
|
/* Any player can make an offer */
|
||||||
if (value === 'offer') {
|
if (value === 'offer') {
|
||||||
const offer = req.body;
|
const offer = req.body;
|
||||||
|
console.log('TODO: Verify player has sufficient resources.');
|
||||||
session.player.gives = offer.gives;
|
session.player.gives = offer.gives;
|
||||||
session.player.gets = offer.gets;
|
session.player.gets = offer.gets;
|
||||||
if (game.turn.name === name) {
|
if (game.turn.name === name) {
|
||||||
@ -1024,10 +1204,17 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Verify the requesting offer wasn't jacked */
|
||||||
|
console.log('TODO: Verify the player trade matches the offer target');
|
||||||
|
|
||||||
/* Transfer goods */
|
/* Transfer goods */
|
||||||
player.gets.forEach(item => {
|
player.gets.forEach(item => {
|
||||||
if (target) {
|
if (target) {
|
||||||
target[item.type] -= item.count;
|
target[item.type] -= item.count;
|
||||||
|
if (target[item.type] < 0) {
|
||||||
|
console.log(`Cheating!!!`);
|
||||||
|
target[item.type] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
player[item.type] += item.count;
|
player[item.type] += item.count;
|
||||||
});
|
});
|
||||||
@ -1036,6 +1223,10 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
target[item.type] += item.count;
|
target[item.type] += item.count;
|
||||||
}
|
}
|
||||||
player[item.type] -= item.count;
|
player[item.type] -= item.count;
|
||||||
|
if (player[item.type] < 0) {
|
||||||
|
console.log(`Cheating!!!`);
|
||||||
|
player[item.type] = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
delete game.turn.offer;
|
delete game.turn.offer;
|
||||||
@ -1162,6 +1353,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
});
|
});
|
||||||
if (cards.length === 0) {
|
if (cards.length === 0) {
|
||||||
addChatMessage(game, session, `Victim did not have any cards to steal.`);
|
addChatMessage(game, session, `Victim did not have any cards to steal.`);
|
||||||
|
game.turn.actions = [];
|
||||||
|
game.turn.limits = {};
|
||||||
} else {
|
} else {
|
||||||
let index = Math.floor(Math.random() * cards.length),
|
let index = Math.floor(Math.random() * cards.length),
|
||||||
type = cards[index];
|
type = cards[index];
|
||||||
@ -1299,6 +1492,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
addChatMessage(game, session, `${name} placed a settlement.`);
|
addChatMessage(game, session, `${name} placed a settlement.`);
|
||||||
|
calculateRoadLengths(game, session);
|
||||||
} else if (game.state === 'initial-placement') {
|
} else if (game.state === 'initial-placement') {
|
||||||
if (game.direction && game.direction === 'backward') {
|
if (game.direction && game.direction === 'backward') {
|
||||||
session.initialSettlement = index;
|
session.initialSettlement = index;
|
||||||
@ -1340,7 +1534,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `You cannot build until you have rolled.`;
|
error = `You cannot build until you have rolled.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (player.wheat < 3 || player.stone < 2) {
|
if (player.wheat < 2 || player.stone < 3) {
|
||||||
error = `You have insufficient resources to build a city.`;
|
error = `You have insufficient resources to build a city.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1386,7 +1580,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
error = `This location already has a city!`;
|
error = `This location already has a city!`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (player.wheat < 3 || player.stone < 2) {
|
if (player.wheat < 2 || player.stone < 3) {
|
||||||
error = `You have insufficient resources to build a city.`;
|
error = `You have insufficient resources to build a city.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1398,8 +1592,8 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
corner.type = 'city';
|
corner.type = 'city';
|
||||||
player.cities--;
|
player.cities--;
|
||||||
player.settlements++;
|
player.settlements++;
|
||||||
player.wheat -= 3;
|
player.wheat -= 2;
|
||||||
player.stone -= 2;
|
player.stone -= 3;
|
||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
addChatMessage(game, session, `${name} upgraded a settlement to a city!`);
|
addChatMessage(game, session, `${name} upgraded a settlement to a city!`);
|
||||||
@ -1477,9 +1671,12 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
addChatMessage(game, session, `${name} placed a road.`);
|
addChatMessage(game, session, `${name} placed a road.`);
|
||||||
|
calculateRoadLengths(game, session);
|
||||||
|
|
||||||
} else if (game.state === 'initial-placement') {
|
} else if (game.state === 'initial-placement') {
|
||||||
road.color = session.color;
|
road.color = session.color;
|
||||||
addChatMessage(game, session, `${name} placed a road.`);
|
addChatMessage(game, session, `${name} placed a road.`);
|
||||||
|
calculateRoadLengths(game, session);
|
||||||
|
|
||||||
let next;
|
let next;
|
||||||
if (game.direction === 'forward' && getLastPlayerName(game) === name) {
|
if (game.direction === 'forward' && getLastPlayerName(game) === name) {
|
||||||
@ -1502,6 +1699,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
name: next,
|
name: next,
|
||||||
color: getColorFromName(game, next)
|
color: getColorFromName(game, next)
|
||||||
};
|
};
|
||||||
|
calculateRoadLengths(game, session);
|
||||||
addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`);
|
addChatMessage(game, null, `It is ${next}'s turn. Place a settlement.`);
|
||||||
} else {
|
} else {
|
||||||
game.turn = {
|
game.turn = {
|
||||||
@ -1737,14 +1935,35 @@ const resetGame = (game) => {
|
|||||||
roads: []
|
roads: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Object.assign(game, {
|
||||||
|
sheep: 19,
|
||||||
|
ore: 19,
|
||||||
|
wool: 19,
|
||||||
|
brick: 19,
|
||||||
|
wheat: 19,
|
||||||
|
longestRoad: null,
|
||||||
|
largestArmy: null,
|
||||||
|
developmentCards: assetData.developmentCards.slice()
|
||||||
|
});
|
||||||
|
|
||||||
for (let key in game.players) {
|
for (let key in game.players) {
|
||||||
game.players[key].wheat =
|
Object.assign(game.players[key], {
|
||||||
game.players[key].sheep =
|
wheat: 0,
|
||||||
game.players[key].stone =
|
sheep: 0,
|
||||||
game.players[key].brick =
|
stone: 0,
|
||||||
game.players[key].wood = 0;
|
brick: 0,
|
||||||
|
wood: 0,
|
||||||
|
roads: 15,
|
||||||
|
cities: 4,
|
||||||
|
settlements: 5,
|
||||||
|
points: 0,
|
||||||
|
development: []
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game.developmentCards = assetData.developmentCards.slice();
|
||||||
|
shuffle(game.developmentCards);
|
||||||
|
|
||||||
for (let i = 0; i < layout.corners.length; i++) {
|
for (let i = 0; i < layout.corners.length; i++) {
|
||||||
game.placements.corners[i] = {
|
game.placements.corners[i] = {
|
||||||
color: undefined,
|
color: undefined,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user