diff --git a/app/src/main/java/org/prolibertate/games/game/Catalog.kt b/app/src/main/java/org/prolibertate/games/game/Catalog.kt index ec88e0b..112e84f 100644 --- a/app/src/main/java/org/prolibertate/games/game/Catalog.kt +++ b/app/src/main/java/org/prolibertate/games/game/Catalog.kt @@ -10,8 +10,8 @@ package org.prolibertate.games.game */ enum class GameMenu(val label: String, val blurb: String) { SOLITAIRE("Solitaire", "Played alone, against the deal or the board."), - CARDS("Card Games", "Anything played with a pack."), TRICK_TAKING("Trick-taking", "Lead, follow, and count what you took."), + CARDS("Card Games", "Anything else played with a pack."), BOARD("Board Games", "Boards, pieces and dice."), ; @@ -29,9 +29,11 @@ data class GameDescriptor( val id: String, val title: String, /** - * Every menu this game appears under. A patience is both solitaire and a - * card game, and Euchre is both a card game and a trick-taking one — so - * this is a set, and the same game is listed in each place it belongs. + * Every menu this game could belong to. Klondike really is both a patience + * and a card game, and Euchre really is both a card game and a trick-taking + * one, so this stays a set: it is what the game *is*. + * + * Where it is *listed* is [home], which is only ever one place. */ val menus: Set, val minPlayers: Int, @@ -40,7 +42,22 @@ data class GameDescriptor( val teamBased: Boolean, val blurb: String, val available: Boolean, -) +) { + /** + * The one menu this game is listed under. + * + * The first of its menus in [GameMenu] declaration order, which is the + * priority: solitaire first, then trick-taking, then the rest of the card + * games, then board games. So a patience is filed under Solitaire rather + * than repeated under Card Games, and Euchre under Trick-taking rather than + * in both places above it. + * + * Listing a game twice made every section a partial duplicate of another, + * and a menu that repeats itself is harder to scan than a longer one that + * does not. + */ + val home: GameMenu get() = GameMenu.entries.first { menus.contains(it) } +} /** * The catalogue the menus are built from. @@ -207,8 +224,11 @@ object GameCatalog { ), GameDescriptor( id = SEQUENCE, + // Played with two packs, but a board game to anyone who owns it, + // and cards outrank board in the priority — so listing it as both + // would quietly file it away from where people look for it. title = "Sequence", - menus = setOf(GameMenu.BOARD, GameMenu.CARDS), + menus = setOf(GameMenu.BOARD), minPlayers = 2, maxPlayers = 12, teamBased = true, @@ -330,7 +350,7 @@ object GameCatalog { menu: GameMenu, includeComingSoon: Boolean = true, ): List = all.filter { - it.menus.contains(menu) && (includeComingSoon || it.available) + it.home == menu && (includeComingSoon || it.available) } /** diff --git a/app/src/test/java/org/prolibertate/games/game/CatalogTest.kt b/app/src/test/java/org/prolibertate/games/game/CatalogTest.kt index 674a2a4..f4b0663 100644 --- a/app/src/test/java/org/prolibertate/games/game/CatalogTest.kt +++ b/app/src/test/java/org/prolibertate/games/game/CatalogTest.kt @@ -40,13 +40,48 @@ class CatalogTest { } @Test - fun `a game can be in more than one menu`() { - // The whole point of the set. A patience is solitaire and a card game; - // Euchre is a card game and a trick-taking one. + fun `a game belongs to several menus but is listed in exactly one`() { + // The set says what a game is; home says where it is filed. Klondike + // really is a card game, and is still not listed among them. val klondike = GameCatalog.byId(GameCatalog.KLONDIKE)!! assertTrue(klondike.menus.containsAll(setOf(GameMenu.SOLITAIRE, GameMenu.CARDS))) + assertEquals(GameMenu.SOLITAIRE, klondike.home) assertTrue(GameCatalog.inMenu(GameMenu.SOLITAIRE).contains(klondike)) - assertTrue(GameCatalog.inMenu(GameMenu.CARDS).contains(klondike)) + assertFalse( + "a patience is filed under Solitaire and nowhere else", + GameCatalog.inMenu(GameMenu.CARDS).contains(klondike), + ) + } + + @Test + fun `no game is listed twice`() { + // The whole point of the change: every section is a partial duplicate + // of another the moment one game appears in two of them. + val listed = GameMenu.entries.flatMap { GameCatalog.inMenu(it) } + assertEquals("a game is listed more than once", listed.size, listed.distinct().size) + assertEquals(GameCatalog.all.size, listed.size) + } + + @Test + fun `home follows the priority order`() { + // Solitaire, then trick-taking, then the rest of the card games, then + // board games — which is the declaration order of the enum, and the + // only place that order is written down. + assertEquals( + listOf(GameMenu.SOLITAIRE, GameMenu.TRICK_TAKING, GameMenu.CARDS, GameMenu.BOARD), + GameMenu.entries.toList(), + ) + // Solitaire beats cards. + assertEquals( + GameMenu.SOLITAIRE, + GameCatalog.byId(GameCatalog.PEG_SOLITAIRE)!!.home, + ) + // Trick-taking beats cards. + assertEquals(GameMenu.TRICK_TAKING, GameCatalog.byId(GameCatalog.HEARTS)!!.home) + assertEquals(GameMenu.TRICK_TAKING, GameCatalog.byId(GameCatalog.EUCHRE)!!.home) + // Nothing above it, so it stays where it is. + assertEquals(GameMenu.CARDS, GameCatalog.byId(GameCatalog.CRIBBAGE)!!.home) + assertEquals(GameMenu.BOARD, GameCatalog.byId(GameCatalog.CHESS)!!.home) } @Test @@ -58,12 +93,16 @@ class CatalogTest { val tricks = GameCatalog.inMenu(GameMenu.TRICK_TAKING) assertTrue("there should be some", tricks.isNotEmpty()) - // A sub-menu that listed something its parent did not would be a game - // you could reach one way and not the other. + // They are card games, and say so — they are simply filed one level in + // rather than listed twice. assertTrue( "every trick-taking game is also a card game", tricks.all { it.menus.contains(GameMenu.CARDS) }, ) + assertTrue( + "and none of them is listed among the card games as well", + GameCatalog.inMenu(GameMenu.CARDS).none { it.menus.contains(GameMenu.TRICK_TAKING) }, + ) } @Test