如何用JavaScript实现卡坦岛资源建筑组合计算功能?
卡坦岛建筑组合计算方案优化(含交易规则与扩展性支持)
针对你遇到的优先级遗漏组合、扩展性、交易规则及剩余资源展示问题,以下是一套完整的JavaScript实现方案:
核心问题解决:无优先级全组合枚举
之前的函数因固定建造优先级(比如先建城市再建道路)遗漏组合,这里采用递归回溯遍历所有可能的建筑数量组合,确保不遗漏任何可行方案:
- 先计算每种建筑的最大可建造数量(基于初始资源+极端兑换场景),缩小枚举范围
- 递归遍历所有建筑的数量组合(从0到最大数量)
- 对每个组合计算总消耗,结合交易规则验证是否可行,可行则记录组合及剩余资源
扩展性设计:配置化管理建筑与港口
把建筑消耗和港口规则做成可配置的对象,新增扩展建筑或修改港口规则无需改动核心逻辑:
建筑配置示例
const buildings = [ { name: 'City(城市)', cost: { wheat: 2, ore: 3 } }, { name: 'Settlement(定居点)', cost: { wood: 1, brick: 1, wool: 1, ore: 1 } }, { name: 'Road(道路)', cost: { wood: 1, brick: 1 } }, { name: 'Dev. Card(发展卡)', cost: { wool: 1, wheat: 1, ore: 1 } }, // 新增扩展建筑直接添加配置项即可 { name: 'Ship(扩展:船只)', cost: { wood: 1, wool: 1 } } ];
港口配置示例
// key为资源类型(专属港口)或'general'(通用港口),value为兑换比例 const ports = { ore: 2, // 矿石专属港口:2个矿石换1个任意资源 general: 3 // 通用港口:3个任意资源换1个任意资源 };
资源交易规则实现
实现一个函数,计算初始资源能否通过交易满足建造需求,并返回交易后的剩余资源:
- 先计算每种资源的盈余/缺口
- 按最优兑换优先级(专属港口>通用港口>默认4换1)将盈余资源转换为可兑换额度
- 验证额度是否覆盖缺口,同时精准计算剩余资源
完整实现代码
// 建筑配置 const buildings = [ { name: 'City(城市)', cost: { wheat: 2, ore: 3 } }, { name: 'Settlement(定居点)', cost: { wood: 1, brick: 1, wool: 1, ore: 1 } }, { name: 'Road(道路)', cost: { wood: 1, brick: 1 } }, { name: 'Dev. Card(发展卡)', cost: { wool: 1, wheat: 1, ore: 1 } } ]; // 港口配置 const ports = { general: 3, ore: 2 }; // 验证资源(含交易)是否满足需求,返回剩余资源或null function canAffordWithTrade(initialResources, requiredResources) { const resourceTypes = ['wood', 'brick', 'wool', 'wheat', 'ore']; const surplus = {}; const deficit = {}; let totalTradeable = 0; // 计算盈余与缺口 resourceTypes.forEach(type => { const has = initialResources[type] || 0; const need = requiredResources[type] || 0; if (has > need) { surplus[type] = has - need; // 计算该资源可兑换的额度 const exchangeRate = ports[type] || ports.general || 4; totalTradeable += Math.floor(surplus[type] / exchangeRate); } else if (has < need) { deficit[type] = need - has; } }); // 总缺口大于可兑换额度,无法满足 const totalDeficit = Object.values(deficit).reduce((sum, val) => sum + val, 0); if (totalTradeable < totalDeficit) return null; // 计算剩余资源 const remaining = {...initialResources}; // 先扣除直接消耗的资源 resourceTypes.forEach(type => { if (requiredResources[type]) { remaining[type] = Math.max((remaining[type] || 0) - requiredResources[type], 0); } }); // 处理交易消耗的盈余资源 let remainingDeficit = totalDeficit; resourceTypes.forEach(type => { if (!surplus[type] || remainingDeficit <= 0) return; const rate = ports[type] || ports.general || 4; // 计算最多能用来交易的数量 const tradeAmount = Math.min(surplus[type], remainingDeficit * rate); remaining[type] -= tradeAmount; remainingDeficit -= Math.floor(tradeAmount / rate); }); // 缺口资源已通过交易覆盖,剩余为0 resourceTypes.forEach(type => { if (deficit[type]) remaining[type] = 0; }); return remaining; } // 枚举所有可行建筑组合 function findAllValidBuilds(initialResources) { const validCombos = []; // 计算每种建筑的最大可能建造数量(极端场景:所有资源兑换为该建筑所需资源) const maxCounts = buildings.map(building => { return Object.entries(building.cost).reduce((minCount, [resource, costPer]) => { const owned = initialResources[resource] || 0; // 计算所有其他资源能兑换成该资源的最大数量 const totalPossible = owned + Math.floor( Object.entries(initialResources) .filter(([r]) => r !== resource) .reduce((sum, [, cnt]) => sum + cnt, 0) / (ports[resource] || ports.general || 4) ); return Math.min(minCount, Math.floor(totalPossible / costPer)); }, Infinity); }); // 递归回溯遍历所有组合 function backtrack(buildingIndex, currentCounts, currentTotalCost) { if (buildingIndex === buildings.length) { const remainingResources = canAffordWithTrade(initialResources, currentTotalCost); if (remainingResources) { validCombos.push({ buildings: buildings.map((b, idx) => ({ name: b.name, count: currentCounts[idx] })).filter(item => item.count > 0), remainingResources }); } return; } // 遍历当前建筑的所有可能建造数量(0到maxCounts[buildingIndex]) for (let count = 0; count <= maxCounts[buildingIndex]; count++) { const newTotalCost = {...currentTotalCost}; // 累加当前建筑count数量的消耗 Object.entries(buildings[buildingIndex].cost).forEach(([res, cost]) => { newTotalCost[res] = (newTotalCost[res] || 0) + cost * count; }); backtrack(buildingIndex + 1, [...currentCounts, count], newTotalCost); } } backtrack(0, [], {}); return validCombos; } // 使用示例 const playerResources = { wood: 3, brick: 3, wool: 2, wheat: 2, ore: 3 }; const validBuilds = findAllValidBuilds(playerResources); // 输出结果示例 validBuilds.forEach((combo, index) => { console.log(`=== 可行组合 ${index + 1} ===`); console.log('建造清单:', combo.buildings); console.log('剩余资源:', combo.remainingResources); });
方案优势
- 无遗漏组合:递归遍历所有可能的建筑数量组合,彻底解决优先级导致的遗漏问题
- 高扩展性:新增建筑/修改港口规则只需调整配置,核心逻辑无需改动
- 完整交易支持:覆盖卡坦岛所有交易规则,优先使用最优兑换比例
- 精准剩余资源:不仅验证可行性,还会输出交易和建造后的剩余资源详情
内容的提问来源于stack exchange,提问作者AnthonyM
相关产品推荐
相关产品推荐

