function Hanafuda(modules) { var Flower = Object.freeze({ Pine: 0, Plum: 1, Cherry: 2, Wisteria: 3, Iris: 4, Peony: 5, BushClover: 6, SusukiGrass: 7, Chrysanthemum: 8, Maple: 9, Willow: 10, Paulownia: 11 }); var Family = Object.freeze( ["Kasu", "Tan", "Tane", "Hikari"].reduce(function(o, name) { o[name] = {class: name[0].toLowerCase() + name.slice(1)}; return o; }, {}) ); var CardNames = [ "Pine0", "Pine1", "PinePoetry", "Crane", "Plum0", "Plum1", "PlumPoetry", "BushWarbler", "Cherry0", "Cherry1", "CherryPoetry", "CampCurtain", "Wisteria0", "Wisteria1", "WisteriaRed", "Cuckoo", "Iris0", "Iris1", "IrisRed", "EightPlankBridge", "Peony0", "Peony1", "PeonyBlue", "Butterflies", "BushClover0", "BushClover1", "BushCloverRed", "Boar", "SusukiGrass0", "SusukiGrass1", "Geese", "FullMoon", "Chrysanthemum0", "Chrysanthemum1", "ChrysanthemumBlue", "SakeCup", "Maple0", "Maple1", "MapleBlue", "Deer", "Lightning", "WillowRed", "Swallow", "RainMan", "Paulownia0", "Paulownia1", "Sand", "Phoenix" ]; var Card = Object.freeze( CardNames.reduce(function(o, name, i) { o[name] = { id: i, family: findFamily(name, i), flower: Math.floor(i / 4), name: name }; return o; }, {}) ); function findFamily(name, i) { if((i % 4 < 2 && i < 41) || (i > 43 && i < 47)) { return Family.Kasu; } else if(name.match(/(Blue|Poetry|Red)/)) { return Family.Tan; } else if(["Crane", "CampCurtain", "FullMoon", "RainMan", "Phoenix"].includes(name)) { return Family.Hikari; } else { return Family.Tane; } } /* Pine0: 0, Pine1: 1, PinePoetry: 2, Crane: 3, Plum0: 4, Plum1: 5, PlumPoetry: 6, BushWarbler: 7, Cherry0: 8, Cherry1: 9, CherryPoetry: 10, CampCurtain: 11, Wisteria0: 12, Wisteria1: 13, WisteriaRed: 14, Cuckoo: 15, Iris0: 16, Iris1: 17, IrisRed: 18, EightPlankBridge: 19, Peony0: 20, Peony1: 21, PeonyBlue: 22, Butterflies: 23, BushClover0: 24, BushClover1: 25, BushCloverRed: 26, Boar: 27, SusukiGrass0: 28, SusukiGrass1: 29, Geese: 30, FullMoon: 31, Chrysanthemum0: 32, Chrysanthemum1: 33, ChrysanthemumBlue: 34, SakeCup: 35, Maple0: 36, Maple1: 37, MapleBlue: 38, Deer: 39, Lightning: 40, WillowRed: 41, Swallow: 42, RainMan: 43, Paulownia0: 44, Paulownia1: 45, Sand: 46, Phoenix: 47 }); var kasu = Object.freeze( Array.apply(null, Array(10)).reduce( function(t, _, i) {return t.concat(4*i, 4*i + 1);}, [] ).concat( ["Lightning", "Paulownia0", "Paulownia1", "Sand"].map(modules.fun.of(Card)) ).reduce(setKey, {}) ); var tan = Object.freeze( ["Pine", "Plum", "Cherry"].map(function(name) {return name+"Poetry";}).concat( ["Wisteria", "Iris", "BushClover"].map(function(name) {return name+"Red";}), ["Peony", "Chrysanthemum", "Maple"].map(function(name) {return name+"Blue";}) ) .map(modules.fun.of(Card)) .reduce(setKey, {}) ); var tane = Object.freeze( [ "BushWarbler", "Cuckoo", "EightPlankBridge", "Butterflies", "Boar", "Geese", "SakeCup", "Deer", "Swallow" ] .map(modules.fun.of(Card)) .reduce(setKey, {}) ); var light = Object.freeze( ["Crane", "CampCurtain", "FullMoon", "RainMan", "Phoenix"] .map(modules.fun.of(Card)) .reduce(setKey, {}) ); */ return { Flower: Flower, Family: Family, Card: Card, getValue: getValue, sameMonth: sameMonth }; function sameMonth(card) { var first = 4 * card.flower; return [0,1,2,3].map(function(i) {return Card[CardNames[first + i]];}); } function getValue(card) { var first = 4 * card.flower; return card.id - first; } /* data Flower = Pine | Plum | Cherry | Wisteria | Iris | Peony | BushClover | SusukiGrass | Chrysanthemum | Maple | Willow | Paulownia deriving (Eq, Ord, Enum, Show) data Card = Pine0 | Pine1 | PinePoetry | Crane | Plum0 | Plum1 | PlumPoetry | BushWarbler | Cherry0 | Cherry1 | CherryPoetry | CampCurtain | Wisteria0 | Wisteria1 | WisteriaRed | Cuckoo | Iris0 | Iris1 | IrisRed | EightPlankBridge | Peony0 | Peony1 | PeonyBlue | Butterflies | BushClover0 | BushClover1 | BushCloverRed | Boar | SusukiGrass0 | SusukiGrass1 | Geese | FullMoon | Chrysanthemum0 | Chrysanthemum1 | ChrysanthemumBlue | SakeCup | Maple0 | Maple1 | MapleBlue | Deer | Lightning | WillowRed | Swallow | RainMan | Paulownia0 | Paulownia1 | Sand | Phoenix deriving (Eq, Ord, Enum, Show) flower :: Card -> Flower flower = toEnum . (`div` 4) . fromEnum type Monthly a = Reader Flower a newtype Pack = Pack { unpack :: Word64 } deriving (Eq) empty :: Pack empty = Pack 0 packOfCards :: [Card] -> Pack packOfCards = foldl add empty smallest :: Pack -> Card smallest = toEnum . countTrailingZeros . unpack cardsOfPack :: Pack -> [Card] cardsOfPack (Pack 0) = [] cardsOfPack p = let c = smallest p in c : cardsOfPack (remove p c) instance Show Pack where show = ("packOfCards " ++) . show . cardsOfPack portEnum :: Enum e => (Word64 -> Int -> b) -> Pack -> e -> b portEnum f (Pack p) = f p . fromEnum contains :: Pack -> Card -> Bool contains = portEnum testBit size :: Pack -> Int size (Pack p) = popCount p add :: Pack -> Card -> Pack add p = Pack . portEnum setBit p remove :: Pack -> Card -> Pack remove p = Pack . portEnum clearBit p portBinary :: (Word64 -> Word64 -> Word64) -> Pack -> Pack -> Pack portBinary operator (Pack a) (Pack b) = Pack $ operator a b intersection :: Pack -> Pack -> Pack intersection = portBinary (.&.) difference :: Pack -> Pack -> Pack difference = portBinary (\a b -> a `xor` (a .&. b)) sameMonth :: Card -> Pack sameMonth card = Pack $ 0xf `shift` (fromEnum card .&. 0xfc) cards :: [Card] cards = [Pine0 .. Phoenix] shuffle :: [a] -> IO [a] shuffle l = aux (length l) l where aux _ [] = return [] aux n (h:t) = do cut <- randomRIO (0, n-1) shuffled <- shuffle t let (top, bottom) = splitAt cut shuffled return $ top ++ h : bottom match :: Card -> Pack -> Either String (Pack, [Card]) match card pack = let sameMonthCards = sameMonth card `intersection` pack in case size sameMonthCards of 0 -> Right (add pack card, []) 1 -> Right (difference pack sameMonthCards, card : cardsOfPack sameMonthCards) _ -> Left "This card can match several others" */ }