2018-05-16 22:59:22 +02:00
|
|
|
function Hanafuda(modules) {
|
2018-05-13 18:08:12 +02:00
|
|
|
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
|
|
|
|
});
|
2018-05-16 22:59:22 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2018-05-13 18:08:12 +02:00
|
|
|
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
|
|
|
|
});
|
|
|
|
|
2018-05-16 22:59:22 +02:00
|
|
|
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, {})
|
|
|
|
);
|
|
|
|
*/
|
|
|
|
|
2018-05-13 18:08:12 +02:00
|
|
|
return {
|
|
|
|
Flower: Flower,
|
2018-05-16 22:59:22 +02:00
|
|
|
Family: Family,
|
2018-05-13 18:08:12 +02:00
|
|
|
Card: Card,
|
2018-05-20 19:15:06 +02:00
|
|
|
getValue: getValue,
|
2018-05-13 18:08:12 +02:00
|
|
|
sameMonth: sameMonth
|
|
|
|
};
|
|
|
|
|
|
|
|
function sameMonth(card) {
|
2018-05-16 22:59:22 +02:00
|
|
|
var first = 4 * card.flower;
|
|
|
|
return [0,1,2,3].map(function(i) {return Card[CardNames[first + i]];});
|
2018-05-13 18:08:12 +02:00
|
|
|
}
|
|
|
|
|
2018-05-20 19:15:06 +02:00
|
|
|
function getValue(card) {
|
|
|
|
var first = 4 * card.flower;
|
|
|
|
return card.id - first;
|
|
|
|
}
|
|
|
|
|
2018-05-13 18:08:12 +02:00
|
|
|
/*
|
|
|
|
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"
|
|
|
|
*/
|
|
|
|
}
|