1
0

Longest road is now tracking

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
James Ketrenos 2022-02-18 15:25:40 -08:00
parent 5cc69d04bc
commit 30bb9870da
3 changed files with 244 additions and 46 deletions

View File

@ -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],

View File

@ -48,8 +48,10 @@ const Placard = ({table, type, active}) => {
} }
const buildClicked = (event) => { const buildClicked = (event) => {
if (!table.state.buildActive) { if (!type.match(/^l.*/)) {
table.setState({ buildActive: true }); if (!table.state.buildActive) {
table.setState({ buildActive: true });
}
} }
}; };
@ -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>

View File

@ -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) {
@ -1006,7 +1186,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
target = game.players[offer.color]; target = game.players[offer.color];
offer.gives.forEach(item => { offer.gives.forEach(item => {
const isOffered = target.gives.find( const isOffered = target.gives.find(
match => match.type === item.type && match.count === item.count); match => match.type === item.type && match.count === item.count);
if (!isOffered) { if (!isOffered) {
mismatch = true; mismatch = true;
} }
@ -1022,12 +1202,19 @@ router.put("/:id/:action/:value?", async (req, res) => {
error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`; error = `Unfortunately, trades were re-negotiated in transit and the deal is invalid!`;
break; break;
} }
} }
/* 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,13 +1935,34 @@ 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] = {
@ -1883,7 +2102,7 @@ const shuffleBoard = (game) => {
} }
} }
shuffle(game.developmentCards); shuffle(game.developmentCards);
} }
/* /*