/// <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);
            }
        }