96 lines
3 KiB
D
96 lines
3 KiB
D
import std.stdio, std.random, std.array, std.conv, std.traits,
|
|
std.exception, std.range, std.algorithm;
|
|
|
|
const class SetDealer {
|
|
protected {
|
|
enum Color: ubyte {green, purple, red}
|
|
enum Number: ubyte {one, two, three}
|
|
enum Symbol: ubyte {oval, diamond, squiggle}
|
|
enum Fill: ubyte {open, striped, solid}
|
|
|
|
static struct Card {
|
|
Color c;
|
|
Number n;
|
|
Symbol s;
|
|
Fill f;
|
|
}
|
|
|
|
static immutable Card[81] deck;
|
|
}
|
|
|
|
static this() pure nothrow @safe {
|
|
immutable colors = [EnumMembers!Color];
|
|
immutable numbers = [EnumMembers!Number];
|
|
immutable symbols = [EnumMembers!Symbol];
|
|
immutable fill = [EnumMembers!Fill];
|
|
|
|
deck = deck.length.iota.map!(i => Card(colors[i / 27],
|
|
numbers[(i / 9) % 3],
|
|
symbols[(i / 3) % 3],
|
|
fill[i % 3])).array;
|
|
}
|
|
|
|
// randomSample produces a sorted output that's convenient in our
|
|
// case because we're printing to stout. Normally you would want
|
|
// to shuffle.
|
|
immutable(Card)[] deal(in uint numCards) const {
|
|
enforce(numCards < deck.length, "Number of cards too large");
|
|
return deck[].randomSample(numCards).array;
|
|
}
|
|
|
|
// The summed enums of valid sets are always zero or a multiple
|
|
// of 3.
|
|
bool validSet(in ref Card c1, in ref Card c2, in ref Card c3)
|
|
const pure nothrow @safe @nogc {
|
|
return !((c1.c + c2.c + c3.c) % 3 ||
|
|
(c1.n + c2.n + c3.n) % 3 ||
|
|
(c1.s + c2.s + c3.s) % 3 ||
|
|
(c1.f + c2.f + c3.f) % 3);
|
|
}
|
|
|
|
immutable(Card)[3][] findSets(in Card[] cards, in uint target = 0)
|
|
const pure nothrow @safe {
|
|
immutable len = cards.length;
|
|
if (len < 3)
|
|
return null;
|
|
|
|
typeof(return) sets;
|
|
foreach (immutable i; 0 .. len - 2)
|
|
foreach (immutable j; i + 1 .. len - 1)
|
|
foreach (immutable k; j + 1 .. len)
|
|
if (validSet(cards[i], cards[j], cards[k])) {
|
|
sets ~= [cards[i], cards[j], cards[k]];
|
|
if (target != 0 && sets.length > target)
|
|
return null;
|
|
}
|
|
return sets;
|
|
}
|
|
}
|
|
|
|
const final class SetPuzzleDealer : SetDealer {
|
|
enum {basic = 9, advanced = 12}
|
|
|
|
override immutable(Card)[] deal(in uint numCards = basic) const {
|
|
immutable numSets = numCards / 2;
|
|
typeof(return) cards;
|
|
|
|
do {
|
|
cards = super.deal(numCards);
|
|
} while (findSets(cards, numSets).length != numSets);
|
|
|
|
return cards;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
const dealer = new SetPuzzleDealer;
|
|
const cards = dealer.deal;
|
|
|
|
writefln("DEALT %d CARDS:", cards.length);
|
|
writefln("%(%s\n%)", cards);
|
|
|
|
immutable sets = dealer.findSets(cards);
|
|
immutable len = sets.length;
|
|
writefln("\nFOUND %d SET%s:", len, len == 1 ? "" : "S");
|
|
writefln("%(%(%s\n%)\n\n%)", sets);
|
|
}
|