/// <summary> /// These test cases verify the LowestCost algorithm implementation, which tries /// every possible resource path for fulfilling the resource requirements and /// returns the cheapest possible commmerce option. /// </summary> static void LowestCostTests() { CommerceOptions expectedResult = new CommerceOptions(); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_1 }, new List<ResourceEffect> { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_1 }, new List<ResourceEffect> { stone_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.rightCoins = 4; Verify2(new Cost("SSSS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_1, stone_1, stone_1, stone_1 }, new List<ResourceEffect> { stone_1, stone_1, stone_1, stone_1 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.rightCoins = 3; Verify2(new Cost("WSO"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_wood, stone_1, ore_2 }, new List<ResourceEffect> { wood_clay, wood_ore, stone_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 6; expectedResult.rightCoins = 1; Verify2(new Cost("WSBPG"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth, glass, stone_wood, stone_1 }, new List<ResourceEffect> { papyrus, glass, cloth, wood_clay }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 3; Verify2(new Cost("WSBPG"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth, glass, stone_wood, stone_1 }, new List<ResourceEffect> { papyrus, glass, cloth, wood_clay }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("WSBPG"), new List<ResourceEffect> { stone_1 /* putting a wood here works, stone does not */, caravansery, forum }, new List<ResourceEffect> { papyrus, glass, }, new List<ResourceEffect> { wood_clay }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("WSBPG"), new List<ResourceEffect> { stone_wood, caravansery, forum, }, new List<ResourceEffect> { papyrus, cloth, glass, stone_wood, stone_1 }, new List<ResourceEffect> { papyrus, glass, cloth, wood_clay }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 6; // papyrus, cloth + ore expectedResult.rightCoins = 1; // clay // The key to this one is that the Caravansery must be used for a Clay resource, not an Ore. // That way the right neighbor can be used to fill the clay resource and the left neighbor only // needs to provide 1 clay (in addition to the Paper and Cloth). The current implementation // won't work because after a successful path is found, the algorithm continues looking for // cheaper alternatives from the remaining resources. It does not go back up the chain and reconsider // resource sources that were already used. I am thinking the algorithm should be changed when we're // going for LowestCost to start with Verify2(new Cost("OOOBCP"), new List<ResourceEffect> { ore_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, clay_2, ore_1, wood_ore, }, new List<ResourceEffect> { glass, wood_1, clay_1, wood_2, stone_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // Make sure that a double resource works properly. expectedResult.leftCoins = 2; // ore expectedResult.rightCoins = 1; // clay Verify2(new Cost("OOB"), new List<ResourceEffect> { caravansery }, new List<ResourceEffect> { ore_2, }, new List<ResourceEffect> { clay_1, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 2; // 1 ore expectedResult.rightCoins = 2; // 2 clay Verify2(new Cost("OOBB"), new List<ResourceEffect> { clay_ore, }, new List<ResourceEffect> { ore_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 2; // 2 clay Verify2(new Cost("OOBB"), new List<ResourceEffect> { clay_ore, caravansery }, new List<ResourceEffect> { ore_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 2; // 2 ore expectedResult.rightCoins = 0; Verify2(new Cost("OOBB"), new List<ResourceEffect> { clay_ore, caravansery }, new List<ResourceEffect> { ore_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 2; // 2 ore expectedResult.rightCoins = 0; Verify2(new Cost("OOBB"), new List<ResourceEffect> { clay_ore, caravansery }, new List<ResourceEffect> { ore_2, }, new List<ResourceEffect> { clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 3; expectedResult.rightCoins = 0; Verify2(new Cost("OOBB"), new List<ResourceEffect> { clay_ore }, new List<ResourceEffect> { stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 3; expectedResult.rightCoins = 4; Verify2(new Cost("OOBBPGC"), new List<ResourceEffect> { forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 7; expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGC"), new List<ResourceEffect> { forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 5; expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGC"), new List<ResourceEffect> { forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 5; expectedResult.rightCoins = 1; Verify2(new Cost("OOBBPGCC"), new List<ResourceEffect> { forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("OOBBPGCC"), new List<ResourceEffect> { clay_ore, clay_2, forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("O"), new List<ResourceEffect> { }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGCC"), new List<ResourceEffect> { clay_ore, clay_2, forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.bankCoins = 1; expectedResult.leftCoins = 1; expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGCC"), new List<ResourceEffect> { clay_ore, clay_2, forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 1; // Bilkis purchases one resource expectedResult.leftCoins = 2; // buy glass or papyrus from the left neighbor expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGCC"), new List<ResourceEffect> { clay_ore, clay_2, forum, caravansery, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, ore_2, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 1; // Bilkis purchases one resource expectedResult.leftCoins = 0; expectedResult.rightCoins = 4; // buy glass or papyrus from the right neighbor Verify2(new Cost("PGCC"), new List<ResourceEffect> { forum, }, new List<ResourceEffect> { glass, papyrus, }, new List<ResourceEffect> { cloth, glass, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 5; // buy glass or papyrus from the left neighbor expectedResult.rightCoins = 0; Verify2(new Cost("OOBBPGC"), new List<ResourceEffect> { clay_1, stone_1, ore_1, forum, }, new List<ResourceEffect> { glass, papyrus, stone_ore, wood_clay, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 2; // buy glass or papyrus from the left neighbor expectedResult.rightCoins = 0; Verify2(new Cost("OOBBP"), new List<ResourceEffect> { clay_ore, forum, }, new List<ResourceEffect> { glass, papyrus, clay_1, stone_ore, wood_clay, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 0; // buy glass or papyrus from the left neighbor expectedResult.rightCoins = 2; Verify2(new Cost("OOBBP"), new List<ResourceEffect> { clay_ore, forum, }, new List<ResourceEffect> { glass, papyrus, clay_1, stone_ore, wood_clay, }, new List<ResourceEffect> { cloth, glass, clay_2, ore_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("PWOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWOO"), new List<ResourceEffect> { wood_ore }, new List<ResourceEffect> { papyrus, ore_1, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, ore_1, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Check that the algorithm realizes that by doubling the 2nd of an either/or, the // resource cost can be fulfilled expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWOO"), new List<ResourceEffect> { wood_ore }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 2; Verify2(new Cost("OOOOPG"), new List<ResourceEffect> { ore_2 }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, stone_ore, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2, clay_ore }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.SecretWarehouse | ResourceManager.CommerceEffects.WestTradingPost, expectedResult); // Black Market tests expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWWB"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWWB"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("WWWB"), new List<ResourceEffect> { clay_1 }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); expectedResult.bAreResourceRequirementsMet = false; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { clay_1 }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 4; expectedResult.rightCoins = 2; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { caravansery, }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); expectedResult.leftCoins = 2; // 1 wood expectedResult.rightCoins = 1; // 1 clay Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { glass, wood_clay, caravansery, }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2 | ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // This time we have a West Trading Post, which allows the structure to be purchased for one // fewer coin. expectedResult.leftCoins = 2; // 2 wood expectedResult.rightCoins = 0; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { glass, wood_clay, caravansery, }, new List<ResourceEffect> { wood_2, }, new List<ResourceEffect> { clay_2, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2 | ResourceManager.CommerceEffects.WestTradingPost, expectedResult); // Clandestine Dock tests expectedResult.leftCoins = 7; expectedResult.rightCoins = 0; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { glass, caravansery, }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_2, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.leftCoins = 3; expectedResult.rightCoins = 4; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { glass, caravansery, }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_2, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.leftCoins = 3; expectedResult.rightCoins = 3; Verify2(new Cost("WWWBPG"), new List<ResourceEffect> { glass, caravansery, }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_2, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 6; Verify2(new Cost("WBG"), new List<ResourceEffect> { }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // This test fails without the Clandestine Dock logic in the reducer as it would produce 0/6 instead of the cheaper 1/4 expectedResult.leftCoins = 1; expectedResult.rightCoins = 4; Verify2(new Cost("WBG"), new List<ResourceEffect> { }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); // this would cost 6 coins without the commerce effects. With them, it only costs 2 coins. expectedResult.leftCoins = 0; expectedResult.rightCoins = 2; Verify2(new Cost("WBG"), new List<ResourceEffect> { }, new List<ResourceEffect> { glass, papyrus, wood_2, wood_ore }, new List<ResourceEffect> { glass, cloth, clay_ore, stone_wood, }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast | ResourceManager.CommerceEffects.WestTradingPost| ResourceManager.CommerceEffects.EastTradingPost, expectedResult); }
/// <summary> /// Basic tests. These validate whether a structure can be built at all. /// </summary> static void BasicTest() { /* I've made the API take a cost, not a card, for now. I may change this in the future to take a card. * using (System.IO.StreamReader file = new System.IO.StreamReader(System.Reflection.Assembly.Load("GameManager"). GetManifestResourceStream("GameManager.7 Wonders Card list.csv"))) { // skip the header line file.ReadLine(); String line = file.ReadLine(); while (line != new List<ResourceEffect>() && line != String.Empty) { fullCardList.Add(new Card(line.Split(','))); line = file.ReadLine(); } } Card cardPawnShop = fullCardList.Find(x => x.Id == CardId.Pawnshop); Verify(cardPawnShop.cost.coin == 0); Verify(cardPawnShop.cost.wood == 0 && cardPawnShop.cost.stone == 0 && cardPawnShop.cost.clay == 0 && cardPawnShop.cost.ore == 0); Verify(cardPawnShop.cost.glass == 0 && cardPawnShop.cost.cloth == 0 && cardPawnShop.cost.papyrus == 0); Verify(cardPawnShop.cost.CostAsString() == string.Empty); Card cardTimberYard = fullCardList.Find(x => x.Id == CardId.Timber_Yard); Verify(cardTimberYard.cost.coin == 1); Verify(cardTimberYard.cost.wood == 0 && cardTimberYard.cost.stone == 0 && cardTimberYard.cost.clay == 0 && cardTimberYard.cost.ore == 0); Verify(cardTimberYard.cost.glass == 0 && cardTimberYard.cost.cloth == 0 && cardTimberYard.cost.papyrus == 0); Verify(cardTimberYard.cost.CostAsString() == string.Empty); Card cardScriptorium = fullCardList.Find(x => x.Id == CardId.Scriptorium); Verify(cardScriptorium.cost.coin == 0); Verify(cardScriptorium.cost.wood == 0 && cardScriptorium.cost.stone == 0 && cardScriptorium.cost.clay == 0 && cardScriptorium.cost.ore == 0); Verify(cardScriptorium.cost.glass == 0 && cardScriptorium.cost.cloth == 0 && cardScriptorium.cost.papyrus == 1); Verify(cardScriptorium.cost.CostAsString() == "P"); */ // create some test cost structures /* Cost costZero = new Cost(); // i.e. Pawnshop Cost costCoinsOnly = new Cost("3"); // Leader Cost costSingleResource = new Cost("S"); // Baths Cost costTripleResource = new Cost("OOO"); // Halikarnassos 2nd stage Cost costQuadResource = new Cost("BBBB"); // Bablon A 3rd stage Cost costSingleGood = new Cost("C"); Cost costDoubleGood = new Cost("PP"); Cost costTripleGood = new Cost("CGP"); Cost costMix1 = new Cost("BBBCP"); Cost costMix2 = new Cost("OOCG"); Cost costMix3 = new Cost("SSSO"); Cost costMix4 = new Cost("SSSSP"); // Giza B 4th stage Cost costMix5 = new Cost("WSBOCGP"); // Palace Cost costMix6 = new Cost("3OOG"); // Torture Chamber Cost costMix7 = new Cost("5C"); // Contingent */ // CommerceOptions costResult; CommerceOptions expectedResult = new CommerceOptions(); expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost(string.Empty), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bankCoins = 3; Verify2(new Cost("3"), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // Verify(costResult.commerceOptions[0].purchasedResourceFromLeftNeighbor == false); // Verify(costResult.commerceOptions[0].purchasedResourceFromRightNeighbor == false); expectedResult.bankCoins = 0; expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // Given a list of available resources (i.e. this city's resources and those of its neighbors), // we want to know a list of commere options for building it. // Can we build a single-resource card with that resource missing? Verify2(new Cost("S"), new List<ResourceEffect> { wood_1, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // Can we build a single-resource card with that resource present? expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("S"), new List<ResourceEffect> { stone_1, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bankCoins = 5; Verify2(new Cost("5C"), new List<ResourceEffect> { cloth, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // Check that last one again, to confirm it's not buildable if the resource required is missing. expectedResult.bAreResourceRequirementsMet = false; expectedResult.bankCoins = 0; Verify2(new Cost("5C"), new List<ResourceEffect> { papyrus, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("WW"), new List<ResourceEffect> { wood_2, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bankCoins = 4; Verify2(new Cost("4WW"), new List<ResourceEffect> { wood_2, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bankCoins = 0; Verify2(new Cost("OWW"), new List<ResourceEffect> { wood_2, ore_1 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("OWW"), new List<ResourceEffect> { wood_2, ore_2 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WW"), new List<ResourceEffect> { wood_1, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("WWB"), new List<ResourceEffect> { wood_1, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("WWB"), new List<ResourceEffect> { wood_ore, stone_wood, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // here's a more intersesting case: the structure costs wood and 2 ore, with two flexes and a caravansery. // it should be buildable but the algorithm must realize that it must take the ore from the wood/ore option. Verify2(new Cost("WOO"), new List<ResourceEffect> { wood_ore, stone_wood, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("OOO"), new List<ResourceEffect> { ore_2 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("OOO"), new List<ResourceEffect> { ore_1, ore_2 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // Make sure it's not a problem to have an unused resource in the middle of the string Verify2(new Cost("WOO"), new List<ResourceEffect> { wood_ore, stone_clay, clay_ore, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WBOO"), new List<ResourceEffect> { wood_ore, stone_clay, clay_ore, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("BSOO"), new List<ResourceEffect> { wood_ore, stone_clay, clay_ore, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWBS"), new List<ResourceEffect> { wood_ore, stone_clay, clay_ore, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWOOP"), new List<ResourceEffect> { stone_wood, clay_ore, wood_ore, wood_clay, papyrus }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWOOPPG"), new List<ResourceEffect> { papyrus, papyrus, stone_wood, clay_ore, wood_ore, wood_clay, forum, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWOOPPG"), new List<ResourceEffect> { papyrus, papyrus, stone_wood, clay_ore, stone_clay, wood_ore, wood_clay, forum }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWSOO"), new List<ResourceEffect> { stone_wood, clay_ore, stone_clay, wood_ore, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWSBOO"), new List<ResourceEffect> { clay_1, stone_wood, clay_ore, stone_clay, wood_ore, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("WWSS"), new List<ResourceEffect> { wood_ore, stone_clay, clay_ore, caravansery }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWOOP"), new List<ResourceEffect> { stone_wood, clay_ore, wood_ore, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("PWWOOP"), new List<ResourceEffect> { stone_wood, clay_ore, wood_ore, wood_clay, papyrus }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // This one has more than one success path as it contains more resources than requirements. Verify2(new Cost("WWSBOO"), new List<ResourceEffect> { stone_wood, clay_ore, stone_clay, wood_ore, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWSBOO"), new List<ResourceEffect> { clay_2, stone_wood, clay_ore, wood_ore, wood_clay }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); /////////////////////////// // Start of commerce tests /////////////////////////// expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { wood_1, }, new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 2; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_1, }, new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 2; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.rightCoins = 4; Verify2(new Cost("SS"), new List<ResourceEffect>(), new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 2; Verify2(new Cost("SSB"), new List<ResourceEffect>(), new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("SSB"), new List<ResourceEffect> { wood_1, }, new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("SSBW"), new List<ResourceEffect> { wood_1, }, new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = false; expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("SSSBB"), new List<ResourceEffect> { stone_wood, }, new List<ResourceEffect> { clay_1, }, new List<ResourceEffect>() { stone_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); List<ResourceEffect> myCity = new List<ResourceEffect> { wood_1, stone_2, stone_clay, forum, }; List<ResourceEffect> leftCity = new List<ResourceEffect> { papyrus, cloth, ore_2, stone_wood, }; List<ResourceEffect> rightCity = new List<ResourceEffect> { papyrus, wood_2, wood_ore, wood_clay, }; expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("SSSG"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 2; Verify2(new Cost("SSSSP"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 4; Verify2(new Cost("PCG"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 2; Verify2(new Cost("WWWO"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = false; expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("BBB"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 2; expectedResult.rightCoins = 2; Verify2(new Cost("WWBBSS"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); myCity = new List<ResourceEffect> { ore_1, caravansery }; expectedResult.bAreResourceRequirementsMet = false; expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("WWBBSS"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 2; // stone expectedResult.rightCoins = 6; // 2 wood, 2 one brick Verify2(new Cost("WWBBSO"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWOP"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 2; Verify2(new Cost("WW"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 4; Verify2(new Cost("WWP"), myCity, leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 4; expectedResult.rightCoins = 10; Verify2(new Cost("WWWWBPP"), new List<ResourceEffect>(), leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("WWWWBPP"), new List<ResourceEffect>(), leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.rightCoins = 0; Verify2(new Cost("WO"), new List<ResourceEffect>(), leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 4; Verify2(new Cost("WO"), new List<ResourceEffect>(), leftCity, rightCity, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 0; Verify2(new Cost("P"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("C"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 0; Verify2(new Cost("C"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 1; Verify2(new Cost("CC"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("PC"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 1; Verify2(new Cost("PC"), new List<ResourceEffect>(), new List<ResourceEffect> { papyrus, cloth }, new List<ResourceEffect> { cloth, glass }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 0; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 0; expectedResult.rightCoins = 2; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("SS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 2; Verify2(new Cost("SS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 2; Verify2(new Cost("SSS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("SSS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.leftCoins = 1; expectedResult.rightCoins = 1; Verify2(new Cost("SS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect> { stone_1 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("WOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, clay_2, ore_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // In this test, we are looking for 3 resources, but for two of them, the only source is in my own // resource stack. The wood has to be purchased from a neighbor expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("WOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // In this test, we are looking for 4 resources, but for two of them, the only source is in my own // resource stack. The wood has to be purchased from the non-preferred neighbor while the papyrus // is purchased for a discounted amount from the preferred neighbor expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("PWOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // Test for special flags. expectedResult.bAreResourceRequirementsMet = false; expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.None, expectedResult); // 1-resource discount expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("S"), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("SSS"), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); Verify2(new Cost("SSSS"), new List<ResourceEffect> { stone_1, stone_2 }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); // same test as above, but now we have a one-resource discount due to a leader effect, so we // no longer have to buy the wood from our left neighbor expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("PWOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.EastTradingPost, expectedResult); // Test double-resources. Only one of the double-resources is needed from the left neighbor due to the discount. expectedResult.leftCoins = 2; expectedResult.rightCoins = 0; Verify2(new Cost("SS"), new List<ResourceEffect>(), new List<ResourceEffect> { stone_2 }, new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 6; expectedResult.rightCoins = 4; Verify2(new Cost("WWWSPC"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, wood_2, }, new List<ResourceEffect> { papyrus, wood_1, }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor | ResourceManager.CommercePreferences.OneResourceDiscount, ResourceManager.CommerceEffects.None, expectedResult); // Bilkis expectedResult.bAreResourceRequirementsMet = true; expectedResult.bankCoins = 1; expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("G"), new List<ResourceEffect>(), new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 1; expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("PWOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.bankCoins = 1; expectedResult.leftCoins = 6; expectedResult.rightCoins = 4; Verify2(new Cost("WWWSPC"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, wood_2, }, new List<ResourceEffect> { papyrus, wood_1, }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Bilkis, expectedResult); // Secret Warehouse tests expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("GG"), new List<ResourceEffect> { glass, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("OOO"), new List<ResourceEffect> { ore_2, }, new List<ResourceEffect>(), new List<ResourceEffect>(), ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Secret Warehouse only applies to our city resources expectedResult.bAreResourceRequirementsMet = false; expectedResult.bankCoins = expectedResult.leftCoins = expectedResult.rightCoins = 0; Verify2(new Cost("OOO"), new List<ResourceEffect> { }, new List<ResourceEffect> { ore_1, }, new List<ResourceEffect> { ore_1 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 0; expectedResult.rightCoins = 1; Verify2(new Cost("PWOO"), new List<ResourceEffect> { wood_ore, caravansery }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); expectedResult.leftCoins = 2; expectedResult.rightCoins = 2; Verify2(new Cost("WWOO"), new List<ResourceEffect> { wood_ore }, new List<ResourceEffect> { papyrus, ore_1, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, ore_1, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Check that the algorithm realizes that by doubling the 2nd of an either/or, the // resource cost can be fulfilled expectedResult.leftCoins = 4; expectedResult.rightCoins = 0; Verify2(new Cost("WWOO"), new List<ResourceEffect> { wood_ore }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2 }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Make sure the Secret Warehouse is used only one time. expectedResult.leftCoins = 3; expectedResult.rightCoins = 1; Verify2(new Cost("OOOOPG"), new List<ResourceEffect> { ore_2 }, new List<ResourceEffect> { papyrus, stone_2, wood_2, clay_2, stone_ore, }, new List<ResourceEffect> { papyrus, glass, stone_2, clay_2, clay_ore }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Check that Forums and Caravansery resources are not doubled with the Secret Warehouse. expectedResult.bAreResourceRequirementsMet = false; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("CC"), new List<ResourceEffect> { forum, }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.SecretWarehouse, expectedResult); // Clandestine Dock tests expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 1; expectedResult.rightCoins = 0; Verify2(new Cost("C"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, papyrus, }, new List<ResourceEffect> { cloth, }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 3; expectedResult.rightCoins = 0; Verify2(new Cost("CP"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, papyrus, }, new List<ResourceEffect> { cloth, }, ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 1; expectedResult.rightCoins = 2; Verify2(new Cost("CP"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, papyrus, }, new List<ResourceEffect> { cloth, }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 2; expectedResult.rightCoins = 1; Verify2(new Cost("CP"), new List<ResourceEffect> { }, new List<ResourceEffect> { cloth, papyrus, }, new List<ResourceEffect> { cloth, }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.ClandestineDockEast, expectedResult); // Black Market tests expectedResult.bAreResourceRequirementsMet = true; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("C"), new List<ResourceEffect> { }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1, expectedResult); Verify2(new Cost("WC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1, expectedResult); expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("WWC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1, expectedResult); // Verify the wood in our normal resource stack is removed from the Black Market(s). expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("WWC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); // But when we build a different structure, with both Black Markets, it's successful. expectedResult.bAreResourceRequirementsMet = true; Verify2(new Cost("WPC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.BlackMarket2, expectedResult); // Secret Warehouse doubles the single wood... Verify2(new Cost("WWC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.SecretWarehouse | ResourceManager.CommerceEffects.BlackMarket1, expectedResult); // ... but not the Black Market expectedResult.bAreResourceRequirementsMet = false; Verify2(new Cost("WCC"), new List<ResourceEffect> { wood_1 }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.SecretWarehouse | ResourceManager.CommerceEffects.BlackMarket1, expectedResult); // After we have a list of options for building a card, we can apply commercial effects, // then resolve the options into: // * minimal cost // * prefer to pay left neighbor (may be minimal cost, mat be higher than minimal) // * prefer to pay right neighbor (may be minimal cost, may be higher than minimal) // // What's the best way to handle wild-card resources? Adding a check at the end works // except for buying doubled resources from neighbors :( // I could also put a new resource source on the list (before Bilkis) // Secret Warehouse. How am I going to implement this? The way the implementation has // been done so far, is for the search algorithm to return the first resource stack that // is able to build the card that meets a certain search criteria (i.e. prefer left or // right neighbors). It may be that when I try to do minimal cost this whole scheme // completely breaks down and I have to go back to doing an (cost string length)^(resource combinations) // search, then sort the options by cost. The problem is knowing which resource to double. // For example, suppose you're building PWWOO, and you have W/O. Both neighbors have wood // but not ore. You have a trading post or dock pointed at one. It's better to use the // Secret Warehouse to build the Ore and buy the wood rather than the other way around. // Hmm, it may be that I'll have to note when I use an flex card and go back and do it // the other way. I may have to do the same thing for the Black Market anyway, actually. // using one of its resources may be more efficient than using another. // I have to figure out how to best use wild resources (including Bilkis), // Secret Warehouse, and Black Market. It'll get complicated when I am // trying for minimal cost and there are multiple paths for success. // If we use these special resources first, a successful path may be found, // but it may not be the cheapest possible successful path. I think the // correct algorithm will be to use my city's non-choice resources, then // those of my neighbors, then my own city's choice resources. If I have // any used choice resources after a match is found, we go back up the // stack, and starting with the least desirable resources purchased from // neighbors, see if they can be replaced with resources that have not // be used yet in my city. // Nov. 15, 2016 I had a good one today. Babylon B, Glassworks, Caravansery, // trading posts in each direction, Marketplace, Clandestine Dock West. // West neighbor: China A, Press, Glassworks, Forest Cave, Brickyard. // East neighbor: Olympia B, Timber Yard, single stone, double ore. }
/// <summary> /// Most complex test cases. /// </summary> static void ComplexTests() { CommerceOptions expectedResult = new CommerceOptions(); expectedResult.bAreResourceRequirementsMet = true; expectedResult.bankCoins = 1; expectedResult.leftCoins = 3; expectedResult.rightCoins = 0; Verify2(new Cost("SSSSP"), new List<ResourceEffect> { stone_wood, forum, }, new List<ResourceEffect> { stone_1, papyrus, glass, stone_ore }, new List<ResourceEffect> { papyrus }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.Bilkis | ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); // Nov. 15, 2016 I had a good one today. Babylon B, Glassworks, Caravansery, // trading posts in each direction, Marketplace, Clandestine Dock West. // West neighbor: China A, Press, Glassworks, Forest Cave, Brickyard. // East neighbor: Olympia B, Timber Yard, single stone, double ore. expectedResult.bankCoins = 0; expectedResult.leftCoins = 1; // cloth, papyrus (+Clandestine Dock) expectedResult.rightCoins = 2; // 2 stone Verify2(new Cost("SSSCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2 }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 0; // cloth or papyrus (+Clandestine Dock) expectedResult.rightCoins = 2; // 2 stone Verify2(new Cost("SSSCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 1; // cloth or papyrus (+Clandestine Dock) expectedResult.rightCoins = 1; // 1 stone Verify2(new Cost("SSSCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest, expectedResult); expectedResult.bankCoins = 1; expectedResult.leftCoins = 0; // cloth or papyrus (+Clandestine Dock) expectedResult.rightCoins = 0; // 1 stone Verify2(new Cost("SSSCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 2; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("2SSBBGP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast, expectedResult); expectedResult.bankCoins = 3; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("2SSBBCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 2; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("2SSBBCP"), new List<ResourceEffect> { clay_1, glass, caravansery, }, new List<ResourceEffect> { cloth, papyrus, glass, clay_2, wood_ore, }, new List<ResourceEffect> { wood_1, stone_1, stone_wood, ore_2, forum }, ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor, ResourceManager.CommerceEffects.SecretWarehouse | ResourceManager.CommerceEffects.BlackMarket1 | ResourceManager.CommerceEffects.WestTradingPost | ResourceManager.CommerceEffects.EastTradingPost | ResourceManager.CommerceEffects.Marketplace | ResourceManager.CommerceEffects.ClandestineDockWest | ResourceManager.CommerceEffects.ClandestineDockEast | ResourceManager.CommerceEffects.Bilkis, expectedResult); expectedResult.bankCoins = 0; expectedResult.leftCoins = 0; expectedResult.rightCoins = 0; Verify2(new Cost("WSBO"), new List<ResourceEffect> { glass, stone_clay, forum, caravansery, caravansery, }, new List<ResourceEffect> { }, new List<ResourceEffect> { }, ResourceManager.CommercePreferences.BuyFromRightNeighbor, ResourceManager.CommerceEffects.BlackMarket1, expectedResult); }
static void Verify2(Cost cost, List<ResourceEffect> cityResources, List<ResourceEffect> leftResources, List<ResourceEffect> rightResources, ResourceManager.CommercePreferences pref, ResourceManager.CommerceEffects commerceEffects, CommerceOptions expectedResult) { ResourceManager resMan = new ResourceManager(); cityResources.ForEach(x => { resMan.add(x); }); resMan.SetCommerceEffect(commerceEffects); CommerceOptions co = resMan.CanAfford(cost, leftResources, rightResources, pref); Verify(co.bAreResourceRequirementsMet == expectedResult.bAreResourceRequirementsMet); Verify(co.bankCoins == expectedResult.bankCoins); Verify(co.leftCoins == expectedResult.leftCoins); Verify(co.rightCoins == expectedResult.rightCoins); }
/// <summary> /// The entry point to the reduction algorithm /// </summary> /// <param name="cost">The cost in coins and resources of the structure</param> /// <param name="leftResources">Resources available for my city to purchase (Browns/Greys only)</param> /// <param name="rightResources">Resources available for my city to purchase (Browns/Greys only)</param> /// <param name="pref">Flags indicating how the search should be performed (Buy from Left or Right), find LowestCost, etc.</param> /// <returns></returns> public CommerceOptions CanAfford(Cost cost, List<ResourceEffect> leftResources, List<ResourceEffect> rightResources, CommercePreferences pref = CommercePreferences.LowestCost | CommercePreferences.BuyFromLeftNeighbor) { CommerceOptions commOptions = new CommerceOptions(); ReduceState rs = new ReduceState(); rs.myResources = resources; rs.leftResources = leftResources; rs.rightResources = rightResources; rs.leftResourcesAvailable = leftResources.Count(); rs.rightResourcesAvailable = rightResources.Count(); rs.currentResourceStack = new Stack<ResourceUsed>(); rs.marketEffects = this.marketEffects; rs.pref = pref; rs.lowestCostResourceStack = new List<ResourceUsed>(); ResourceCost lowCost = new ResourceCost(); rs.wildResource = pref.HasFlag(CommercePreferences.OneResourceDiscount) ? SpecialTrait.Unused : SpecialTrait.Unavailable; rs.bilkis = marketEffects.HasFlag(CommerceEffects.Bilkis) ? SpecialTrait.Unused : SpecialTrait.Unavailable; if (rs.wildResource == SpecialTrait.Unused || rs.bilkis == SpecialTrait.Unused) rs.wildResourceEffect = new ResourceEffect(false, "WSBOCGP"); rs.secretWarehouse = marketEffects.HasFlag(CommerceEffects.SecretWarehouse) ? SpecialTrait.Unused : SpecialTrait.Unavailable; rs.nBlackMarketIndex = 0; rs.nBlackMarketAvailable = 0; if (marketEffects.HasFlag(CommerceEffects.BlackMarket1)) ++rs.nBlackMarketAvailable; if (marketEffects.HasFlag(CommerceEffects.BlackMarket2)) ++rs.nBlackMarketAvailable; if (rs.nBlackMarketAvailable > 0) { string strBlackMarket = "WSBOCGP"; foreach (ResourceEffect re in resources) { // The Black Market only excludes resources produced by this // city's brown or grey structures, which only have 1 or 2 resource types // So this excludes the Caravansery, Forum, and any Wonder stages. if (re.resourceTypes.Length <= 2) { foreach (char c in re.resourceTypes) { int index = strBlackMarket.IndexOf(c); if (index >= 0) strBlackMarket = strBlackMarket.Remove(index, 1); } } } rs.blackMarketResource = new ResourceEffect(false, strBlackMarket); } if (cost.resources != string.Empty) { // kick off a recursive reduction of the resource cost. Paths that completely eliminate the cost // are returned in the requiredResourcesLists. ReduceRecursively(rs, ref lowCost, cost.resources); commOptions.bAreResourceRequirementsMet = rs.lowestCostResourceStack.Count != 0; } else { commOptions.bAreResourceRequirementsMet = true; } if (commOptions.bAreResourceRequirementsMet) { commOptions.bankCoins = lowCost.bank + cost.coin; commOptions.leftCoins = lowCost.left; commOptions.rightCoins = lowCost.right; } return commOptions; }
/// <summary> /// Determines if a given card is buildable. /// Returns "T" if it is, returns "F" if it is not /// </summary> /// <param name="card"></param> /// <returns></returns> public CommerceOptions isCardBuildable(Card card) { CommerceOptions ret = new CommerceOptions(); //retrieve the cost Cost cost = card.cost; //if the player already owns a copy of the card, Return F immediatley // Note cannot use playedStructure.Contains(card) because Age 1 Loom != Age 2 Loom, so it's possible to build more than one of them. if (playedStructure.Exists(x => x.Id == card.Id)) { ret.bAreResourceRequirementsMet = false; ret.buildable = CommerceOptions.Buildable.StructureAlreadyBuilt; return ret; } //if the cost is !, that means its free. Return T immediately if (cost.coin == 0 && cost.resources == string.Empty) { ret.bAreResourceRequirementsMet = true; ret.buildable = CommerceOptions.Buildable.True; return ret; } //if the player owns the prerequiste, Return T immediately if (playedStructure.Exists(x => (x.chain[0] == card.strName) || (x.chain[1] == card.strName))) { ret.bAreResourceRequirementsMet = true; ret.buildable = CommerceOptions.Buildable.True; return ret; } if (card.structureType == StructureType.Guild && playedStructure.Exists(x => x.Id == CardId.Ramses)) { // Ramses: The player can build any Guild card for free. ret.bAreResourceRequirementsMet = true; ret.buildable = CommerceOptions.Buildable.True; return ret; } int nWildResources = 0; if (playedStructure.Exists(x => x.effect is StructureDiscountEffect && ((StructureDiscountEffect)x.effect).discountedStructureType == card.structureType)) { // A leader card has been played that matches the structure type being built, so we can add a wild resource // e.g. We're building a science structure while Archimedes is in play for this player, or a military structure // when Leonidas is in play. ++nWildResources; } int coinCost = cost.coin; if (card.structureType == StructureType.Leader) { if (playerBoard.name == "Roma (A)" || playedStructure.Exists(x => x.Id == CardId.Maecenas)) { coinCost = 0; } else if (playerBoard.name == "Roma (B)") { coinCost = Math.Max(0, coinCost - 2); } else if (leftNeighbour.playerBoard.name == "Roma (B)" || rightNeighbour.playerBoard.name == "Roma (B)") { coinCost -= 1; } } if (coin < coinCost) { // if the card has a coin cost and we don't have enough money, the card is not buildable. ret.bAreResourceRequirementsMet = false; ret.buildable = CommerceOptions.Buildable.InsufficientCoins; return ret; } ret = resourceMgr.CanAfford(cost, leftNeighbour.resourceMgr.getResourceList(false), rightNeighbour.resourceMgr.getResourceList(false), ResourceManager.CommercePreferences.LowestCost | ResourceManager.CommercePreferences.BuyFromLeftNeighbor); if (ret.bAreResourceRequirementsMet) { if ((ret.leftCoins == 0 && ret.rightCoins == 0) && (ret.bankCoins == 0 || (ret.bankCoins == cost.coin))) { if (coin < ret.bankCoins) ret.buildable = CommerceOptions.Buildable.InsufficientCoins; else ret.buildable = CommerceOptions.Buildable.True; } else if (coin < ret.bankCoins) { ret.buildable = CommerceOptions.Buildable.InsufficientCoins; } else { ret.buildable = CommerceOptions.Buildable.CommerceRequired; } } else { ret.buildable = CommerceOptions.Buildable.InsufficientResources; } return ret; }
/// <summary> /// Determines if the Player's current stage is buildable /// Returns "T" if it is, returns "F" if it is not /// </summary> /// <returns></returns> public CommerceOptions isStageBuildable() { CommerceOptions ret = new CommerceOptions(); //check if the current Stage is already the maximum stage if (currentStageOfWonder >= playerBoard.numOfStages) { ret.bAreResourceRequirementsMet = false; ret.buildable = CommerceOptions.Buildable.StructureAlreadyBuilt; return ret; } //retrieve the cost Cost cost = playerBoard.stageCard[currentStageOfWonder].cost; //check for the stage discount card (Imhotep) int nWildResources = 0; if (playedStructure.Exists(x => x.effect is StructureDiscountEffect && ((StructureDiscountEffect)x.effect).discountedStructureType == StructureType.WonderStage)) { // A leader card has been played that matches the structure type being built, so we can add a wild resource // e.g. We're building a science structure while Archimedes is in play for this player, or a military structure // when Leonidas is in play. ++nWildResources; } if (hasArchitectCabinet) { // Wonder stages have no resource cost, but the coin cost (Petra's second stage) must still be paid. if (coin < cost.coin) { // not enough coins in the treasury ret.bAreResourceRequirementsMet = false; ret.buildable = CommerceOptions.Buildable.InsufficientCoins; } else { ret.bAreResourceRequirementsMet = true; ret.buildable = CommerceOptions.Buildable.True; } return ret; } ret = resourceMgr.CanAfford(cost, leftNeighbour.resourceMgr.getResourceList(false), rightNeighbour.resourceMgr.getResourceList(false)); if (ret.bAreResourceRequirementsMet) { if (ret.leftCoins == 0 && ret.rightCoins == 0) { if (coin < ret.bankCoins) ret.buildable = CommerceOptions.Buildable.InsufficientCoins; else ret.buildable = CommerceOptions.Buildable.True; } else { ret.buildable = CommerceOptions.Buildable.CommerceRequired; } } else { ret.buildable = CommerceOptions.Buildable.InsufficientResources; } return ret; }
public void makeMove(Player player, GameManager gm) { //go for blue cards only on the third age //if not, Discard Red Cards //otherwise, discard first card /* string strOutput = string.Format("{0} hand: [ ", player.nickname); if (gm.phase == GamePhase.LeaderRecruitment) { foreach (Card card in player.draftedLeaders) { strOutput += card.Id; strOutput += " "; } } else { foreach (Card card in player.hand) { strOutput += card.Id; strOutput += " "; } } strOutput += "]"; logger.Info(strOutput); */ if (gm.phase == GamePhase.LeaderDraft || gm.phase == GamePhase.LeaderRecruitment) { // int[] favouredLeaders = { 216, 220, 222, 232, 200, 208, 205, 221, 214, 236, 213 }; CardId [] favouredLeaders = { CardId.Nero, CardId.Tomyris, CardId.Alexander, CardId.Hannibal, CardId.Caesar, CardId.Nefertiti, CardId.Cleopatra, CardId.Zenobia, CardId.Justinian }; Card bestLeader = null; //try to find the highest rated card in hand //start looking for the highest rated card, then go down to the next highest, etc. foreach (CardId leaderName in favouredLeaders) { if (gm.phase == GamePhase.LeaderDraft) { bestLeader = player.hand.Find(x => x.Id == leaderName); } else if (gm.phase == GamePhase.LeaderRecruitment) { bestLeader = player.draftedLeaders.Find(x => x.Id == leaderName); } if (bestLeader != null && player.isCardBuildable(bestLeader).buildable == CommerceOptions.Buildable.True) { break; } } if (bestLeader == null && gm.phase == GamePhase.LeaderDraft) { // this hand didn't contain a favoured leader, so draft the first one in the list. We cannot // discard during the draft. Leaders may only be discarded for 3 coins during recruitment. bestLeader = player.hand[0]; } if (bestLeader != null) { logger.Info(player.nickname + "Drafted leader: {0}", bestLeader.Id); gm.playCard(player, bestLeader, BuildAction.BuildStructure, true, false, 0, 0, false); } else { logger.Info(player.nickname + " Action: Discard {0}", player.draftedLeaders[0].Id); gm.playCard(player, player.draftedLeaders[0], BuildAction.Discard, true, false, 0, 0, false); } return; } // Dictionary<Card, CardCost> cardValues = new Dictionary<Card, CardCost>(player.hand.Count); // Card cost: // NotBuildable // Free (the city has sufficient resources) // Coin cost to the bank only (flex brown, double, some City cards) // Commerce Required (can be constructed by paying neighbors for their resources) // If Commerce is required, how many coins to each neighbor and/or the bank CommerceOptions[] co = new CommerceOptions[player.hand.Count]; int [] cardValues = new int[player.hand.Count]; for (int i = 0; i < player.hand.Count; ++i) { co[i] = player.isCardBuildable(player.hand[i]); } CommerceOptions nextStageCost = player.isStageBuildable(); for (int i = 0; i < player.hand.Count; ++i) { Card card = player.hand[i]; if (co[i].buildable == CommerceOptions.Buildable.True || co[i].buildable == CommerceOptions.Buildable.CommerceRequired) { switch (card.structureType) { case StructureType.RawMaterial: { ResourceEffect re = card.effect as ResourceEffect; if (re.resourceTypes.Length == 2) { if (re.resourceTypes[0] == re.resourceTypes[1]) { // doubles can be useful, but we need to examine whether we // already have enough of them. cardValues[i] = 50; } else { // Flex resources should almost always be taken cardValues[i] = 80; } } else { // single-resource browns are fairly useless. cardValues[i] = 25; } } break; case StructureType.Goods: { ResourceEffect res = card.effect as ResourceEffect; cardValues[i] = 45; if (player.leftNeighbour.resourceMgr.getResourceList(false).Contains(res) || player.rightNeighbour.resourceMgr.getResourceList(false).Contains(res)) { // Yes: drop its value significantly and even more if we have a Marketplace too. cardValues[i] = player.resourceMgr.GetCommerceEffect().HasFlag(ResourceManager.CommerceEffects.Marketplace) ? 6 : 24; } else { if (gm.currentAge == 2) { if (gm.currentTurn > 5) cardValues[i] = 90; else if (gm.currentTurn > 2) cardValues[i] = 65; } else { if (gm.currentTurn > 4) { cardValues[i] = 55; } } if (player.resourceMgr.GetCommerceEffect().HasFlag(ResourceManager.CommerceEffects.Marketplace) && gm.currentTurn < 5) { cardValues[i] /= 2; } } } break; case StructureType.Civilian: cardValues[i] = ((card.effect as CoinsAndPointsEffect).victoryPointsAtEndOfGameMultiplier - (2 - gm.currentAge)) * 10; break; case StructureType.Commerce: switch (card.Id) { case CardId.Tavern: cardValues[i] = 30 - (player.coin * 10); break; case CardId.West_Trading_Post: { List<ResourceEffect> leftResources = player.leftNeighbour.resourceMgr.getResourceList(false); int nLeftResources = 0; foreach (ResourceEffect re in leftResources) { if (!re.IsManufacturedGood()) nLeftResources += re.resourceTypes.Length; } cardValues[i] = (nLeftResources - gm.currentTurn + 6) * 10; } break; case CardId.East_Trading_Post: { List<ResourceEffect> rightResources = player.rightNeighbour.resourceMgr.getResourceList(false); int nRightResources = 0; foreach (ResourceEffect re in rightResources) { if (!(re.IsManufacturedGood())) nRightResources += re.resourceTypes.Length; } cardValues[i] = (nRightResources - gm.currentTurn + 6) * 10; } break; case CardId.Marketplace: { string strGoodsNeighbors = "PCG"; List<ResourceEffect> leftResources = player.leftNeighbour.resourceMgr.getResourceList(false); foreach (ResourceEffect re in leftResources) { int resIndex = strGoodsNeighbors.IndexOf(re.resourceTypes[0]); if (resIndex != -1) strGoodsNeighbors = strGoodsNeighbors.Substring(resIndex, 1); } List<ResourceEffect> rightResources = player.rightNeighbour.resourceMgr.getResourceList(false); foreach (ResourceEffect re in rightResources) { int resIndex = strGoodsNeighbors.IndexOf(re.resourceTypes[0]); if (resIndex != -1) strGoodsNeighbors = strGoodsNeighbors.Substring(resIndex, 1); } int nMyCityGoods = player.resourceMgr.getResourceList(true).FindAll(x => x.IsManufacturedGood()).Count; // for each available grey card in neighboring cities, add 20 points, // and subtract 30 points for each grey card in our city. cardValues[i] = 40 + ((3 - strGoodsNeighbors.Length) * 20) - nMyCityGoods * 30; } break; case CardId.Caravansery: // This one is pretty much automatic: if it's there, take it. cardValues[i] = 90; break; case CardId.Forum: cardValues[i] = 35; break; } break; case StructureType.Military: { int nShieldsLeft = player.leftNeighbour.shield; int nShieldsRight = player.leftNeighbour.shield; int myShields = player.shield; int nShields = (card.effect as MilitaryEffect).nShields; if (myShields > (nShieldsRight + nShields) && myShields > (nShieldsLeft + nShields)) { // our city has more military strength than our neighbors and adding more won't gain us any more points cardValues[i] = 10; } else if (myShields > nShieldsRight && myShields > nShieldsLeft) { // Our city's military strength is stronger than both of our neighbors but if one of them plays military and // we don't, we'll be tied or losing against them. cardValues[i] = 20; } else if (((myShields + nShields) <= nShieldsRight) && ((myShields + nShields) <= nShieldsLeft)) { // Even if we played this card, we would still have fewer shields than our neighbors. We are so far behind // in military it's probably not worth playing any military. cardValues[i] = 5; } else if (((myShields + nShields) > nShieldsRight) && ((myShields + nShields) > nShieldsLeft)) { // If we play this card, we'll go from losing against both neighbors to winning. cardValues[i] = 75; } else if (((myShields + nShields) > nShieldsRight) || ((myShields + nShields) > nShieldsLeft)) { // If we play this card, we'll go from losing against one neighbor to winning cardValues[i] = 60; } else { // can we logically ever get here? throw new NotImplementedException(); } } break; case StructureType.Science: cardValues[i] = 65; // calculate the value of this card, and consider whether we are going for sets (1, 2, 3) or symbols break; case StructureType.Guild: // calculate the value of this card. if (card.effect is CoinsAndPointsEffect) { // most guilds fall into this category: they count points based on something the neighboring cities. cardValues[i] = player.CountVictoryPoints(card.effect as CoinsAndPointsEffect) * 10; CoinsAndPointsEffect cpe = card.effect as CoinsAndPointsEffect; // account for the possibility that the neighboring cities will play more of these structures // during the 3rd age and thus increase the value of these guild cards. switch (cpe.classConsidered) { case StructureType.RawMaterial: // there are no Raw Materials cards available in the 3rd age. break; case StructureType.Goods: // there are no Goods cards available in the 3rd age. break; case StructureType.Civilian: case StructureType.Commerce: cardValues[i] += (gm.nTurnsInEachAge - gm.currentTurn) * 2; break; case StructureType.Military: case StructureType.Science: cardValues[i] += ((gm.nTurnsInEachAge - gm.currentTurn) * (cardValues[i] / 20)); break; case StructureType.Guild: cardValues[i] += (gm.nTurnsInEachAge - gm.currentTurn); break; case StructureType.WonderStage: // for the Builder's Guild, we'll assume a high probability of all wonder stages getting built. cardValues[i] += (((player.playerBoard.numOfStages + player.leftNeighbour.playerBoard.numOfStages + player.rightNeighbour.playerBoard.numOfStages) * 10) - cardValues[i])/2; break; case StructureType.MilitaryLosses: case StructureType.ConflictToken: // Not sure what to do here. I guess look at their shield strength. break; case StructureType.ThreeCoins: // would be better to consider other factors too, such as ours and our neighbors' resource availability // (i.e. how much we'll pay out and how much they are likely to pay us for the rest of the age). cardValues[i] += (gm.nTurnsInEachAge - gm.currentTurn) * 3; break; } } else if (card.Id == CardId.Shipowners_Guild) { // Shipowners guild counts 1 point for each RawMaterial, Goods, and Guild structure in the players' city. cardValues[i] = player.playedStructure.Where(x => x.structureType == StructureType.RawMaterial || x.structureType == StructureType.Goods || x.structureType == StructureType.Guild).Count() * 10; } else if (card.Id == CardId.Counterfeiters_Guild) { // Add 1 because all other players has to lose 3 coins which is the same as 1 VP. // Possible TODO: adjust the value based on whether players who are close in VP to this player // have many or few coins in their treasury. Loss of coins cards hurt players with few // coins more than players with many. This card could cause an opponent to lose up to 3 VP // if they have to take 3 debt tokens, which makes it more valuable to us. Also, we could // consider the effect of another player playing this card on our own treasury. cardValues[i] = ((card.effect as LossOfCoinsEffect).victoryPoints + 1) * 10; } else if (card.Id == CardId.Scientists_Guild) { throw new NotImplementedException(); } else { throw new NotImplementedException(); } break; case StructureType.City: break; } } } int bestCardValue = 0; int bestCardIndex = -1; for (int i = 0; i < player.hand.Count; ++i) { if (cardValues[i] > bestCardValue) { bestCardValue = cardValues[i]; bestCardIndex = i; } else if (cardValues[i] == bestCardValue) { // two cards are of equal value. We need to consider secondary factors } } Card c = null; if (bestCardIndex != -1) c = player.hand[bestCardIndex]; // go through the total value of this hand and select the best card #if FALSE /* foreach (Card crd in player.hand) { player.GetCost(crd); } */ // Build Guild cards in the 3rd age (except for the Courtesan's Guild, which requires entering a special Game Phase // that the AI has not been programmed to think about. Card c = player.hand.Find(x => x.structureType == StructureType.Guild && x.Id != CardId.Courtesans_Guild && player.isCardBuildable(x).buildable == CommerceOptions.Buildable.True); if (c == null) { //look for buildable blue cards at the third age .. c = player.hand.Find(x => x.structureType == StructureType.Civilian && player.isCardBuildable(x).buildable == CommerceOptions.Buildable.True && x.age == 3); } if (c == null) { //look for buildable green cards c = player.hand.Find(x => x.structureType == StructureType.Science && player.isCardBuildable(x).buildable == CommerceOptions.Buildable.True); } if (c == null) { //look for buildable resource cards that give more than one manufactory resources ... foreach (Card card in player.hand) { if ((card.structureType == StructureType.Commerce && player.isCardBuildable(card).buildable == CommerceOptions.Buildable.True) && card.effect is ResourceEffect) { // char resource = player.hand[i].effect[2]; // hunh? string resource = ((ResourceEffect)card.effect).resourceTypes; if (resource.Length < 3) continue; if (resource.Contains("C") && player.loom < maxLPG * 2) { c = card; } else if (resource.Contains("P") && player.papyrus < maxLPG * 2) { c = card; } else if (resource.Contains("G") && player.glass < maxLPG * 2) { c = card; } // not sure what's going on here. I think there may have been a bug in the original implementation. } } } if (c == null) { //look for buildable resource cards that give more than one resource ... foreach (Card card in player.hand) { if ((card.structureType == StructureType.RawMaterial && player.isCardBuildable(card).buildable == CommerceOptions.Buildable.True) && card.effect is ResourceEffect) { string resource = ((ResourceEffect)card.effect).resourceTypes; if (player.brick < maxOBW && resource.Contains('B') ) { c = card; } else if (player.ore < maxOBW && resource.Contains('O') ) { c = card; } else if (player.stone < maxStone && resource.Contains('S') ) { c = card; } else if (player.wood < maxOBW && resource.Contains('W') ) { c = card; } } } } if (c == null) { //look for buildable resource cards that only give one and the manufactory resources .. foreach (Card card in player.hand) { if ((card.structureType == StructureType.RawMaterial || card.structureType == StructureType.Goods) && player.isCardBuildable(card).buildable == CommerceOptions.Buildable.True && card.effect is ResourceEffect) { ResourceEffect e = card.effect as ResourceEffect; char resource = e.resourceTypes[0]; int numOfResource = e.IsDoubleResource() ? 2 : 1; if (resource == 'C' && player.loom < maxLPG) { c = card; } else if (resource == 'G' && player.glass < maxLPG) { c = card; } else if (resource == 'P' && player.papyrus < maxLPG) { c = card; } else if (resource == 'B' && numOfResource + player.brick < maxOBW) { c = card; } else if (resource == 'O' && numOfResource + player.ore < maxOBW) { c = card; } else if (resource == 'S' && numOfResource + player.stone < maxStone) { c = card; } else if (resource == 'W' && numOfResource + player.wood < maxOBW) { c = card; } } } } if (c == null) { //look for buildable Red cards c = player.hand.Find(x => x.structureType == StructureType.Military && player.isCardBuildable(x).buildable == CommerceOptions.Buildable.True); } if (c == null) { // play a city card, if there is one List<Card> cityCardList = player.hand.FindAll(x => x.structureType == StructureType.City && player.isCardBuildable(x).buildable == CommerceOptions.Buildable.True); if (cityCardList.Count > 0) { // Try to find a card that causes other players to lose cards c = cityCardList.Find(x => x.effect is LossOfCoinsEffect); if (c == null) { c = cityCardList.Find(x => x.effect is CopyScienceSymbolFromNeighborEffect); } if (c == null) { c = cityCardList.Find(x => x.effect is MilitaryEffect); } if (c == null) { c = cityCardList.Find(x => x.effect is DiplomacyEffect); } if (c == null) { // play a point-scoring card. c = cityCardList.Find(x => x.effect is CoinsAndPointsEffect); } // if none of the above criteria match, it means this card is has a commerce special effect, // such as the Secret Warehouse, Black Market, Clandestine Dock, or Architect Cabinet, which // this AI has not been programmed to think about. So in that case, just play the first // card in the list of playable city cards. c = cityCardList[0]; } } #endif if (c == null) { //Discard the non-buildable Red cards foreach (Card card in player.hand) { if (card.structureType == StructureType.Military && player.isCardBuildable(card).buildable != CommerceOptions.Buildable.True) { logger.Info(player.nickname + " Action: Discard {0}", card.Id); gm.playCard(player, card, BuildAction.Discard, true, false, 0, 0, false); return; } } } if (c != null) { logger.Info(player.nickname + " Action: Construct {0}", c.Id); gm.playCard(player, c, BuildAction.BuildStructure, true, false, co[bestCardIndex].leftCoins, co[bestCardIndex].rightCoins, false); } else { // If a card is not found that matches any of the above criteria, discard the first card listed. c = player.hand[0]; logger.Info(player.nickname + " Action: Discard {0}", c.Id); gm.playCard(player, c, BuildAction.Discard, true, false, 0, 0, false); } }