Completed all seven deck requests
This commit is contained in:
		
							parent
							
								
									777e07035b
								
							
						
					
					
						commit
						653b309825
					
				
							
								
								
									
										12
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								build.zig
									
									
									
									
									
								
							@ -1,16 +1,11 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub fn build(b: *std.Build) void {
 | 
			
		||||
    const target = b.standardTargetOptions(.{
 | 
			
		||||
        .whitelist = &[_]std.Target.Query{
 | 
			
		||||
            std.Target.Query{ .cpu_arch = .aarch64, .os_tag = .macos },
 | 
			
		||||
            std.Target.Query{ .cpu_arch = .x86_64, .os_tag = .linux },
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    const target = b.standardTargetOptions(.{});
 | 
			
		||||
 | 
			
		||||
    // TODO
 | 
			
		||||
    // Prefer small size binaries for optimization
 | 
			
		||||
    const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .Debug });
 | 
			
		||||
    const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSmall });
 | 
			
		||||
 | 
			
		||||
    const exe = b.addExecutable(.{
 | 
			
		||||
        .name = "genius_deck",
 | 
			
		||||
@ -25,11 +20,12 @@ pub fn build(b: *std.Build) void {
 | 
			
		||||
        .openssl = false,
 | 
			
		||||
    });
 | 
			
		||||
    exe.root_module.addImport("zap", zap.module("zap"));
 | 
			
		||||
 | 
			
		||||
    b.installArtifact(exe);
 | 
			
		||||
 | 
			
		||||
    // Create Check step for zls
 | 
			
		||||
    const exe_check = b.addExecutable(.{
 | 
			
		||||
        .name = "zerver",
 | 
			
		||||
        .name = "genius_deck",
 | 
			
		||||
        .root_source_file = b.path("src/main.zig"),
 | 
			
		||||
        .target = target,
 | 
			
		||||
        .optimize = optimize,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										474
									
								
								src/deck.zig
									
									
									
									
									
								
							
							
						
						
									
										474
									
								
								src/deck.zig
									
									
									
									
									
								
							@ -2,11 +2,12 @@ const std = @import("std");
 | 
			
		||||
const assert = std.debug.assert;
 | 
			
		||||
 | 
			
		||||
pub const DeckError = error{
 | 
			
		||||
    DuplicateDiscard,
 | 
			
		||||
    Overflow,
 | 
			
		||||
    DuplicateInDiscard,
 | 
			
		||||
    DuplicateInDeck,
 | 
			
		||||
    OutOfRangeCut,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Card = struct { suite: Suit, faceValue: Face };
 | 
			
		||||
pub const Card = struct { suit: Suit, face_value: Face };
 | 
			
		||||
 | 
			
		||||
pub const Suit = enum(u4) { Diamonds = 0, Clubs = 1, Hearts = 2, Spades = 3 };
 | 
			
		||||
 | 
			
		||||
@ -26,40 +27,36 @@ pub const Face = enum(u4) {
 | 
			
		||||
    Ace = 14,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const ShuffleOptions = struct {
 | 
			
		||||
    seed: ?u64 = null,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
comptime {
 | 
			
		||||
    if (@typeInfo(Suit).Enum.fields.len != 4) {
 | 
			
		||||
        @compileError("Only four suites allowed");
 | 
			
		||||
        @compileError("Only four suits allowed");
 | 
			
		||||
    }
 | 
			
		||||
    if (@typeInfo(Face).Enum.fields.len != 13) {
 | 
			
		||||
        @compileError("Only 13 face cards permitted");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const NUM_SUITES = @typeInfo(Suit).Enum.fields.len;
 | 
			
		||||
const NUM_CARDS_IN_SUITE = @typeInfo(Face).Enum.fields.len;
 | 
			
		||||
const MAX_CARDS = NUM_SUITES * NUM_CARDS_IN_SUITE;
 | 
			
		||||
const CARD_STRUCT_SIZE = @sizeOf(Card) / 2;
 | 
			
		||||
const NUM_SUITS: u8 = @typeInfo(Suit).Enum.fields.len;
 | 
			
		||||
const NUM_CARDS_IN_SUIT: u8 = @typeInfo(Face).Enum.fields.len;
 | 
			
		||||
const MAX_CARDS: u8 = NUM_SUITS * NUM_CARDS_IN_SUIT;
 | 
			
		||||
const CARD_STRUCT_SIZE: u8 = @sizeOf(Card) / 2;
 | 
			
		||||
 | 
			
		||||
/// A Bounded Array with a fixed size to fit 52 `Card` structs inside it.
 | 
			
		||||
/// Requires no allocations because of the fixed max size known at compile time.
 | 
			
		||||
 | 
			
		||||
// pub fn printCards(cards: CardSlice, options: bufPrintCardOptions) void {
 | 
			
		||||
//     std.debug.print("Deck with {d} cards\tBuf len: {d}\n", .{ cards.len, cards.buffer.len });
 | 
			
		||||
//     std.debug.print("--- Bottom of Deck ---\n", .{});
 | 
			
		||||
//     var buf: [18]u8 = undefined;
 | 
			
		||||
//     for (0..cards.len) |cardIdx| {
 | 
			
		||||
//         const card = cards.get(cardIdx);
 | 
			
		||||
//         std.debug.print(" - {d}: {s}\n", .{ cardIdx, bufPrintCard(card, &buf, options) });
 | 
			
		||||
//     }
 | 
			
		||||
//     std.debug.print("--- Top of Deck ---\n", .{});
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
pub fn printDeck(deck: Deck, options: bufPrintCardOptions) void {
 | 
			
		||||
pub fn printDeck(deck: *Deck, options: bufPrintCardOptions) void {
 | 
			
		||||
    std.debug.print("Deck with {d} cards\n", .{deck.num_cards});
 | 
			
		||||
    printCards(deck.cards, deck.num_cards, options);
 | 
			
		||||
    std.debug.print("\nDiscard pile with {d} cards\n", .{deck.num_discards});
 | 
			
		||||
    printCards(deck.discard_pile, deck.num_discards, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn printCards(cards: []?Card, num_cards: u8, options: bufPrintCardOptions) void {
 | 
			
		||||
    std.debug.print("--- Bottom of Deck ---\n", .{});
 | 
			
		||||
    var buf: [18]u8 = undefined;
 | 
			
		||||
    for (0..deck.num_cards) |cardIdx| {
 | 
			
		||||
        std.debug.print(" - {d}: {s}\n", .{ cardIdx, bufPrintCard(deck.cards[cardIdx], &buf, options) });
 | 
			
		||||
    for (0..num_cards) |card_idx| {
 | 
			
		||||
        std.debug.print(" - {d}: {s}\n", .{ card_idx, bufPrintCard(cards[card_idx].?, &buf, options) });
 | 
			
		||||
    }
 | 
			
		||||
    std.debug.print("--- Top of Deck ---\n", .{});
 | 
			
		||||
}
 | 
			
		||||
@ -70,12 +67,12 @@ pub const bufPrintCardOptions = struct {
 | 
			
		||||
/// Returns a string representation of the card in the `buffer`
 | 
			
		||||
pub fn bufPrintCard(card: Card, buffer: []u8, options: bufPrintCardOptions) []const u8 {
 | 
			
		||||
    // std.debug.print("\n\t{any}", .{card});
 | 
			
		||||
    const fv = @tagName(card.faceValue);
 | 
			
		||||
    const icon = if (options.use_icon) suiteToIcon(card.suite) else @tagName(card.suite);
 | 
			
		||||
    const fv = @tagName(card.face_value);
 | 
			
		||||
    const icon = if (options.use_icon) suitToIcon(card.suit) else @tagName(card.suit);
 | 
			
		||||
    return std.fmt.bufPrint(buffer, "{s} of {s}", .{ fv, icon }) catch return "*err printing card";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn suiteToIcon(s: Suit) []const u8 {
 | 
			
		||||
pub fn suitToIcon(s: Suit) []const u8 {
 | 
			
		||||
    return switch (s) {
 | 
			
		||||
        .Spades => "",
 | 
			
		||||
        .Hearts => "",
 | 
			
		||||
@ -87,117 +84,146 @@ fn suiteToIcon(s: Suit) []const u8 {
 | 
			
		||||
pub const Deck = struct {
 | 
			
		||||
    num_cards: u8 = 0,
 | 
			
		||||
    num_discards: u8 = 0,
 | 
			
		||||
    cb1: [MAX_CARDS]Card = [_]Card{Card{ .suite = .Diamonds, .faceValue = .Two }} ** MAX_CARDS,
 | 
			
		||||
    cb2: [MAX_CARDS]Card = [_]Card{Card{ .suite = .Diamonds, .faceValue = .Two }} ** MAX_CARDS,
 | 
			
		||||
    cards: []Card = undefined,
 | 
			
		||||
    discard_pile: []Card = undefined,
 | 
			
		||||
    _cb1: [MAX_CARDS]?Card = [_]?Card{null} ** MAX_CARDS,
 | 
			
		||||
    _cb2: [MAX_CARDS]?Card = [_]?Card{null} ** MAX_CARDS,
 | 
			
		||||
    cards: []?Card = undefined,
 | 
			
		||||
    discard_pile: []?Card = undefined,
 | 
			
		||||
 | 
			
		||||
    pub fn init(self: *Deck) DeckError!void {
 | 
			
		||||
        // var cardBuf = [_]Card{Card{ .suite = .Diamonds, .faceValue = .Two }} ** MAX_CARDS;
 | 
			
		||||
        self.cards = &self.cb1;
 | 
			
		||||
        // var discardBuf = [_]Card{Card{ .suite = .Diamonds, .faceValue = .Two }} ** MAX_CARDS;
 | 
			
		||||
        self.discard_pile = &self.cb2;
 | 
			
		||||
    /// Populates the `Deck.cards` with the default sort order of face cards.
 | 
			
		||||
    pub fn init(self: *Deck) void {
 | 
			
		||||
        self.cards = &self._cb1;
 | 
			
		||||
        self.discard_pile = &self._cb2;
 | 
			
		||||
        // Construct the deck
 | 
			
		||||
        for (0..NUM_SUITES) |suiteIdx| {
 | 
			
		||||
            const suite: Suit = @enumFromInt(suiteIdx);
 | 
			
		||||
            for (0..NUM_CARDS_IN_SUITE) |f| {
 | 
			
		||||
                const faceIdx = NUM_CARDS_IN_SUITE - f + 1;
 | 
			
		||||
                const face: Face = @enumFromInt(faceIdx);
 | 
			
		||||
                self.cards[self.num_cards] = Card{ .suite = suite, .faceValue = face };
 | 
			
		||||
        for (0..NUM_SUITS) |suit_idx| {
 | 
			
		||||
            const suit: Suit = @enumFromInt(suit_idx);
 | 
			
		||||
            for (0..NUM_CARDS_IN_SUIT) |f| {
 | 
			
		||||
                const face_idx = NUM_CARDS_IN_SUIT - f + 1;
 | 
			
		||||
                const face: Face = @enumFromInt(face_idx);
 | 
			
		||||
                self.cards[self.num_cards] = Card{ .suit = suit, .face_value = face };
 | 
			
		||||
                self.num_cards += 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        assert(self.num_cards == MAX_CARDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Pops and returns the top card off the deck, `null` if none are left.
 | 
			
		||||
    pub fn deal(self: *Deck) ?Card {
 | 
			
		||||
        if (self.num_cards == 0) return null;
 | 
			
		||||
        self.num_cards -= 1;
 | 
			
		||||
        return self.cards[self.num_cards];
 | 
			
		||||
        const card = self.cards[self.num_cards];
 | 
			
		||||
        self.cards[self.num_cards] = null;
 | 
			
		||||
        return card;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns the top card off the deck without affecting the deck,
 | 
			
		||||
    /// `null` if none are left.
 | 
			
		||||
    pub fn peak(self: *Deck) ?Card {
 | 
			
		||||
        if (self.num_cards == 0) return null;
 | 
			
		||||
        return self.cards[self.num_cards - 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Places the `card` into the `Deck.discard_pile` slice of cards.
 | 
			
		||||
    /// Returns an error if the card already exists in the `Deck.cards` or
 | 
			
		||||
    /// `Deck.discard_pile`.
 | 
			
		||||
    pub fn discard(self: *Deck, card: Card) DeckError!void {
 | 
			
		||||
        // Check a duplicate isnt introduced
 | 
			
		||||
        for (0..self.num_discards) |discardIdx| {
 | 
			
		||||
            if (std.meta.eql(self.discard_pile[discardIdx], card)) {
 | 
			
		||||
                return DeckError.DuplicateDiscard;
 | 
			
		||||
        for (0..self.num_discards) |discard_idx| {
 | 
			
		||||
            if (std.meta.eql(self.discard_pile[discard_idx], card)) {
 | 
			
		||||
                return DeckError.DuplicateInDiscard;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (0..self.num_cards) |card_idx| {
 | 
			
		||||
            if (std.meta.eql(self.cards[card_idx], card)) {
 | 
			
		||||
                return DeckError.DuplicateInDeck;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        self.discard_pile[self.num_discards] = card;
 | 
			
		||||
        self.num_discards += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn rebuild(self: *Deck) DeckError!void {
 | 
			
		||||
        const newTotal = self.num_cards + self.num_discards;
 | 
			
		||||
        assert(newTotal <= MAX_CARDS);
 | 
			
		||||
    pub fn cut(self: *Deck, index: u8) DeckError!void {
 | 
			
		||||
        if (index >= self.num_cards or index < 0) return DeckError.OutOfRangeCut;
 | 
			
		||||
        if (index == 0) return;
 | 
			
		||||
 | 
			
		||||
        for (0..self.num_discards) |disIdx| {
 | 
			
		||||
            self.cards[self.num_cards + disIdx];
 | 
			
		||||
        const index_from_top: u8 = self.num_cards - index;
 | 
			
		||||
        var cb = [_]?Card{null} ** MAX_CARDS;
 | 
			
		||||
        var top_cards: []?Card = cb[index_from_top..MAX_CARDS];
 | 
			
		||||
        for (0..index) |top_idx| {
 | 
			
		||||
            top_cards[top_idx] = self.cards[index_from_top + top_idx];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (0..index_from_top) |bot_idx| {
 | 
			
		||||
            self.cards[self.num_cards - bot_idx - 1] = self.cards[index_from_top - bot_idx - 1];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (0..index) |top_idx| {
 | 
			
		||||
            self.cards[top_idx] = top_cards[top_idx];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Empties the `Deck.discard_pile` into the `Deck.cards` and
 | 
			
		||||
    /// sorts the resulting `[]Card` slice.
 | 
			
		||||
    pub fn rebuild(self: *Deck) void {
 | 
			
		||||
        const new_total = self.num_cards + self.num_discards;
 | 
			
		||||
        assert(new_total <= MAX_CARDS);
 | 
			
		||||
 | 
			
		||||
        const back_of_cards_idx = self.num_cards;
 | 
			
		||||
        for (0..self.num_discards) |dis_idx| {
 | 
			
		||||
            const card_idx = back_of_cards_idx + dis_idx;
 | 
			
		||||
            self.cards[card_idx] = self.discard_pile[dis_idx];
 | 
			
		||||
            self.num_cards += 1;
 | 
			
		||||
        }
 | 
			
		||||
        self.num_discards = 0;
 | 
			
		||||
        assert(self.num_cards == newTotal);
 | 
			
		||||
        assert(self.num_cards == new_total);
 | 
			
		||||
 | 
			
		||||
        try sort(self.cards);
 | 
			
		||||
        self.sort();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn order_deck(self: *Deck) DeckError!void {
 | 
			
		||||
        try sort(self.cards);
 | 
			
		||||
    /// Sorts the `Deck.cards` slice in place
 | 
			
		||||
    pub fn order_deck(self: *Deck) void {
 | 
			
		||||
        self.sort();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn sort(cards: []Card, numCards: u8) DeckError!void {
 | 
			
		||||
        if (cards.len <= 1) return;
 | 
			
		||||
        var cb = [MAX_CARDS]?Card{null} ** MAX_CARDS;
 | 
			
		||||
        var sortedGapsDeck: []?Card = cb[0..MAX_CARDS];
 | 
			
		||||
    /// Sorts a `Deck.cards` in place
 | 
			
		||||
    /// We can sort the deck quickly because we
 | 
			
		||||
    /// know the index of each card by default,
 | 
			
		||||
    /// the sorting takes N time, and 2N space
 | 
			
		||||
    ///
 | 
			
		||||
    /// Default Order from Top to Bottom is:
 | 
			
		||||
    /// Suits: Spades > Hearts > Clubs > Diamonds
 | 
			
		||||
    /// FaceValue: 2 > 10 > Jack > Queen > King > Ace
 | 
			
		||||
    /// 2 of Spades is on top, Ace of Diamonds is the bottom card
 | 
			
		||||
    fn sort(self: *Deck) void {
 | 
			
		||||
        if (self.num_cards <= 1) return;
 | 
			
		||||
        var cb = [_]?Card{null} ** MAX_CARDS;
 | 
			
		||||
        var sorted_gaps_deck: []?Card = cb[0..MAX_CARDS];
 | 
			
		||||
        var idx_used = [_]bool{false} ** MAX_CARDS;
 | 
			
		||||
 | 
			
		||||
        for (cards) |card| {
 | 
			
		||||
            const cardIdx = faceValueIdx(card.faceValue) + suiteValueIdx(card.suite);
 | 
			
		||||
            sortedGapsDeck[cardIdx] = card;
 | 
			
		||||
        }
 | 
			
		||||
        var cb2: [MAX_CARDS]Card = undefined;
 | 
			
		||||
        var sortedGaplessDeck = cb2[0..numCards];
 | 
			
		||||
        var glIdx: u8 = 0;
 | 
			
		||||
        for (0..sortedGapsDeck.len) |gIdx| {
 | 
			
		||||
            if (sortedGapsDeck[gIdx] != null) {
 | 
			
		||||
                sortedGaplessDeck[glIdx] = sortedGapsDeck[gIdx];
 | 
			
		||||
                glIdx += 1;
 | 
			
		||||
        var cards_added: u8 = 0;
 | 
			
		||||
        for (self.cards) |maybe_card| {
 | 
			
		||||
            if (maybe_card) |card| {
 | 
			
		||||
                const card_idx = faceValueIdx(card.face_value) + suitValueIdx(card.suit);
 | 
			
		||||
                assert(!idx_used[card_idx]);
 | 
			
		||||
                idx_used[card_idx] = true;
 | 
			
		||||
                sorted_gaps_deck[card_idx] = card;
 | 
			
		||||
                cards_added += 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var newCards = []Card.init(0) catch return DeckError.Overflow;
 | 
			
		||||
        for (sortedGaplessDeck) |card| {
 | 
			
		||||
            newCards.append(card) catch return DeckError.Overflow;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn cloneDeck(deck: *[]Card) DeckError![]Card {
 | 
			
		||||
        var newDeck = []Card.init(0) catch return DeckError.Overflow;
 | 
			
		||||
 | 
			
		||||
        for (deck.buffer) |card| {
 | 
			
		||||
            newDeck.append(card) catch return DeckError.Overflow;
 | 
			
		||||
        }
 | 
			
		||||
        return newDeck;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test "Deck.cloneDeck" {
 | 
			
		||||
        var deck = Deck{};
 | 
			
		||||
        try deck.init();
 | 
			
		||||
        const clonedCards = try Deck.cloneDeck(&deck.cards);
 | 
			
		||||
 | 
			
		||||
        try std.testing.expectEqual(deck.cards.len, clonedCards.len);
 | 
			
		||||
        for (deck.cards, 0..) |card, i| {
 | 
			
		||||
            try std.testing.expectEqual(card, clonedCards[i]);
 | 
			
		||||
        assert(cards_added == self.num_cards);
 | 
			
		||||
        self.num_cards = 0;
 | 
			
		||||
        for (0..MAX_CARDS) |g_idx| {
 | 
			
		||||
            if (sorted_gaps_deck[g_idx] != null) {
 | 
			
		||||
                self.cards[self.num_cards] = sorted_gaps_deck[g_idx].?;
 | 
			
		||||
                self.num_cards += 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        assert(self.num_cards == cards_added);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gives the index within the suit for a given `Face`
 | 
			
		||||
    fn faceValueIdx(f: Face) u8 {
 | 
			
		||||
        const negIdx: i16 = @intCast(@intFromEnum(f));
 | 
			
		||||
        return @intCast(@abs(negIdx - 14));
 | 
			
		||||
        const neg_idx: i16 = @intCast(@intFromEnum(f));
 | 
			
		||||
        return @intCast(@abs(neg_idx - 14));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test "Deck.faceValueIdx" {
 | 
			
		||||
@ -205,83 +231,40 @@ pub const Deck = struct {
 | 
			
		||||
        try std.testing.expectEqual(1, faceValueIdx(Face.King));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn suiteValueIdx(s: Suit) u8 {
 | 
			
		||||
        const cards: u8 = @intCast(NUM_CARDS_IN_SUITE);
 | 
			
		||||
    fn suitValueIdx(s: Suit) u8 {
 | 
			
		||||
        const cards: u8 = @intCast(NUM_CARDS_IN_SUIT);
 | 
			
		||||
        return @intFromEnum(s) * cards;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // fn mergeSplit(a: *BoundedArrayOfCards, b: *BoundedArrayOfCards, iBegin: u8, iEnd: u8) void {
 | 
			
		||||
    //     if (iEnd - iBegin <= 1) return;
 | 
			
		||||
    //     const iMiddle: u8 = (iBegin + iEnd) / 2;
 | 
			
		||||
    //     mergeSplit(a, b, iBegin, iMiddle);
 | 
			
		||||
    //     mergeSplit(a, b, iMiddle, iEnd);
 | 
			
		||||
    //     merge(b, a, iBegin, iMiddle, iEnd);
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    // /// Merges two arrays of cards, deck `a` is merged into deck `b`
 | 
			
		||||
    // fn merge(a: *BoundedArrayOfCards, b: *BoundedArrayOfCards, iBegin: u8, iMiddle: u8, iEnd: u8) void {
 | 
			
		||||
    //     var i = iBegin;
 | 
			
		||||
    //     var j = iMiddle;
 | 
			
		||||
    //     for (iBegin..iEnd) |k| {
 | 
			
		||||
    //         if (i < iMiddle and (j >= iEnd or gt(a.get(j), a.get(i)))) {
 | 
			
		||||
    //             b.set(k, a.get(i));
 | 
			
		||||
    //             i = i + 1;
 | 
			
		||||
    //         } else {
 | 
			
		||||
    //             b.set(k, a.get(j));
 | 
			
		||||
    //             j = j + 1;
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    /// Greater than comparison function for two cards
 | 
			
		||||
    /// A card is considered greater, if it is found closer
 | 
			
		||||
    /// to the top of the deck when set to the default sort order
 | 
			
		||||
    ///
 | 
			
		||||
    /// Default Order from Top to Bottom is:
 | 
			
		||||
    /// Suites: Spades > Hearts > Clubs > Diamonds
 | 
			
		||||
    /// FaceValue: 2 > 10 > Jack > Queen > King > Ace
 | 
			
		||||
    /// 2 of Spades is on top, Ace of Diamonds is the bottom card
 | 
			
		||||
    fn gt(l: Card, r: Card) bool {
 | 
			
		||||
        const lSuite: u4 = @intFromEnum(l.suite);
 | 
			
		||||
        const lFace: u4 = @intFromEnum(l.faceValue);
 | 
			
		||||
        const rSuite: u4 = @intFromEnum(r.suite);
 | 
			
		||||
        const rFace: u4 = @intFromEnum(r.faceValue);
 | 
			
		||||
 | 
			
		||||
        if (lSuite != rSuite) {
 | 
			
		||||
            return lSuite > rSuite;
 | 
			
		||||
    pub fn shuffle(self: *Deck, shuffle_options: ShuffleOptions) void {
 | 
			
		||||
        const seed: u64 = if (shuffle_options.seed) |s| s else @intCast(std.time.milliTimestamp());
 | 
			
		||||
        var prng = std.Random.DefaultPrng.init(seed);
 | 
			
		||||
        const random = prng.random();
 | 
			
		||||
        var i: u8 = 0;
 | 
			
		||||
        while (i < self.num_cards - 1) : (i += 1) {
 | 
			
		||||
            const j = random.intRangeLessThan(u8, 0, self.num_cards);
 | 
			
		||||
            std.mem.swap(Card, &self.cards[i].?, &self.cards[j].?);
 | 
			
		||||
        }
 | 
			
		||||
        // reverse comparison for face value, because 3 (3) is > ace (14)
 | 
			
		||||
        return lFace < rFace;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test "Deck.gt" {
 | 
			
		||||
        const twoHearts = Card{ .suite = .Hearts, .faceValue = .Two };
 | 
			
		||||
        const kingSpades = Card{ .suite = .Spades, .faceValue = .King };
 | 
			
		||||
        const aceSpades = Card{ .suite = .Spades, .faceValue = .Ace };
 | 
			
		||||
 | 
			
		||||
        try std.testing.expect(Deck.gt(kingSpades, twoHearts));
 | 
			
		||||
        try std.testing.expect(Deck.gt(kingSpades, aceSpades));
 | 
			
		||||
        try std.testing.expect(!Deck.gt(aceSpades, aceSpades));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
test "Deck.init" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    try deck.init();
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(MAX_CARDS, deck.num_cards);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suite = .Diamonds, .faceValue = .Ace }, deck.cards[0]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suite = .Spades, .faceValue = .Two }, deck.cards[51]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .Ace }, deck.cards[0]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Two }, deck.cards[51]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test "Deck.deal" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    try deck.init();
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(deck.deal(), Card{ .suite = .Spades, .faceValue = .Two });
 | 
			
		||||
    try std.testing.expectEqual(deck.deal(), Card{ .suit = .Spades, .face_value = .Two });
 | 
			
		||||
    try std.testing.expectEqual(51, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(deck.deal(), Card{ .suite = .Spades, .faceValue = .Three });
 | 
			
		||||
    try std.testing.expectEqual(deck.deal(), Card{ .suit = .Spades, .face_value = .Three });
 | 
			
		||||
    try std.testing.expectEqual(50, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    for (0..50) |_| {
 | 
			
		||||
@ -294,9 +277,9 @@ test "Deck.deal" {
 | 
			
		||||
 | 
			
		||||
test "Deck.peak" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    try deck.init();
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suite = .Spades, .faceValue = .Two }, deck.peak());
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Two }, deck.peak());
 | 
			
		||||
    try std.testing.expectEqual(MAX_CARDS, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    // Deal out 20 cards
 | 
			
		||||
@ -304,7 +287,7 @@ test "Deck.peak" {
 | 
			
		||||
        _ = deck.deal();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(deck.peak(), Card{ .suite = .Hearts, .faceValue = .Nine });
 | 
			
		||||
    try std.testing.expectEqual(deck.peak(), Card{ .suit = .Hearts, .face_value = .Nine });
 | 
			
		||||
    try std.testing.expectEqual(deck.num_cards, 32);
 | 
			
		||||
 | 
			
		||||
    // Try to peak empty deck
 | 
			
		||||
@ -316,12 +299,12 @@ test "Deck.peak" {
 | 
			
		||||
 | 
			
		||||
test "Deck.discard" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    try deck.init();
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    const twoSpadesCard = deck.deal();
 | 
			
		||||
    try std.testing.expect(twoSpadesCard != null);
 | 
			
		||||
    try std.testing.expect(twoSpadesCard.?.suite == .Spades);
 | 
			
		||||
    try std.testing.expect(twoSpadesCard.?.faceValue == .Two);
 | 
			
		||||
    try std.testing.expect(twoSpadesCard.?.suit == .Spades);
 | 
			
		||||
    try std.testing.expect(twoSpadesCard.?.face_value == .Two);
 | 
			
		||||
 | 
			
		||||
    // Deal out 13 cards
 | 
			
		||||
    for (0..12) |_| {
 | 
			
		||||
@ -329,8 +312,8 @@ test "Deck.discard" {
 | 
			
		||||
    }
 | 
			
		||||
    const twoHeartsCard = deck.deal();
 | 
			
		||||
    try std.testing.expect(twoHeartsCard != null);
 | 
			
		||||
    try std.testing.expect(twoHeartsCard.?.suite == .Hearts);
 | 
			
		||||
    try std.testing.expect(twoHeartsCard.?.faceValue == .Two);
 | 
			
		||||
    try std.testing.expect(twoHeartsCard.?.suit == .Hearts);
 | 
			
		||||
    try std.testing.expect(twoHeartsCard.?.face_value == .Two);
 | 
			
		||||
 | 
			
		||||
    // Discard two cards
 | 
			
		||||
    try deck.discard(twoSpadesCard.?);
 | 
			
		||||
@ -340,12 +323,12 @@ test "Deck.discard" {
 | 
			
		||||
    try std.testing.expectEqual(twoSpadesCard.?, deck.discard_pile[0]);
 | 
			
		||||
 | 
			
		||||
    // Fails to add duplicate card
 | 
			
		||||
    try std.testing.expectError(DeckError.DuplicateDiscard, deck.discard(twoSpadesCard.?));
 | 
			
		||||
    try std.testing.expectError(DeckError.DuplicateInDiscard, deck.discard(twoSpadesCard.?));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test "Deck.rebuild" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    try deck.init();
 | 
			
		||||
    deck.init();
 | 
			
		||||
    var buf1 = [_]?Card{null} ** 5;
 | 
			
		||||
    var buf2 = [_]?Card{null} ** 5;
 | 
			
		||||
    var buf3 = [_]?Card{null} ** 5;
 | 
			
		||||
@ -355,7 +338,7 @@ test "Deck.rebuild" {
 | 
			
		||||
 | 
			
		||||
    // Alternate dealing player one and two hands
 | 
			
		||||
    for (0..10) |i| {
 | 
			
		||||
        if (i % 2 == 0) try playerOneHand.append(deck.deal().?) else try playerTwoHand.append(deck.deal().?);
 | 
			
		||||
        if (i % 2 == 0) playerOneHand[i / 2] = deck.deal().? else playerTwoHand[i / 2] = deck.deal().?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(5, playerOneHand.len);
 | 
			
		||||
@ -372,66 +355,131 @@ test "Deck.rebuild" {
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(5, playerThreeHand.len);
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(10, deck.discard_pile.len);
 | 
			
		||||
    try std.testing.expectEqual(27, deck.cards.len);
 | 
			
		||||
    try std.testing.expectEqual(10, deck.num_discards);
 | 
			
		||||
    try std.testing.expectEqual(27, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    for (0..15) |i| {
 | 
			
		||||
        switch (i % 3) {
 | 
			
		||||
            0 => try deck.discard(playerThreeHand.pop()),
 | 
			
		||||
            1 => try deck.discard(playerOneHand.pop()),
 | 
			
		||||
            2 => try deck.discard(playerTwoHand.pop()),
 | 
			
		||||
            0 => try deck.discard(playerThreeHand[i / 3].?),
 | 
			
		||||
            1 => try deck.discard(playerOneHand[i / 3].?),
 | 
			
		||||
            2 => try deck.discard(playerTwoHand[i / 3].?),
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    try std.testing.expectEqual(25, deck.discard_pile.len);
 | 
			
		||||
    try std.testing.expectEqual(25, deck.num_discards);
 | 
			
		||||
 | 
			
		||||
    try deck.rebuild();
 | 
			
		||||
    deck.rebuild();
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(0, deck.discard_pile.len);
 | 
			
		||||
    try std.testing.expectEqual(52, deck.cards.len);
 | 
			
		||||
    try std.testing.expectEqual(0, deck.num_discards);
 | 
			
		||||
    try std.testing.expectEqual(52, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suite = .Diamonds, .faceValue = .Ace }, deck.cards[0]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suite = .Spades, .faceValue = .Two }, deck.cards[51]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .Ace }, deck.cards[0]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Two }, deck.cards[51]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// test "Deck.rebuild with missing cards" {
 | 
			
		||||
//     var deck = Deck{};
 | 
			
		||||
//     try deck.init();
 | 
			
		||||
//     // var playerOneHand = []Card.init(0) catch unreachable;
 | 
			
		||||
//     // var playerTwoHand = []Card.init(0) catch unreachable;
 | 
			
		||||
test "Deck.rebuild with missing cards" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    deck.init();
 | 
			
		||||
    var buf1 = [_]?Card{null} ** 13;
 | 
			
		||||
    var buf2 = [_]?Card{null} ** 13;
 | 
			
		||||
    var player_one_hand: []?Card = &(buf1);
 | 
			
		||||
    var player_two_hand: []?Card = &(buf2);
 | 
			
		||||
 | 
			
		||||
//     // Alternate dealing player one discarding cards
 | 
			
		||||
//     for (0..52) |i| {
 | 
			
		||||
//         switch (i % 4) {
 | 
			
		||||
//             0 => try playerTwoHand.append(deck.deal().?),
 | 
			
		||||
//             1 => try deck.discard(deck.deal().?),
 | 
			
		||||
//             2 => try playerOneHand.append(deck.deal().?),
 | 
			
		||||
//             3 => {
 | 
			
		||||
//                 _ = deck.deal();
 | 
			
		||||
//             },
 | 
			
		||||
//             else => unreachable,
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
    // Mix up the cards, throw some away
 | 
			
		||||
    for (0..MAX_CARDS) |i| {
 | 
			
		||||
        switch (i % 4) {
 | 
			
		||||
            0 => player_two_hand[i / 4] = deck.deal().?,
 | 
			
		||||
            1 => try deck.discard(deck.deal().?),
 | 
			
		||||
            2 => player_one_hand[i / 4] = deck.deal().?,
 | 
			
		||||
            3 => {
 | 
			
		||||
                _ = deck.deal();
 | 
			
		||||
            },
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//     try std.testing.expectEqual(13, playerOneHand.len);
 | 
			
		||||
//     try std.testing.expectEqual(13, playerTwoHand.len);
 | 
			
		||||
//     try std.testing.expectEqual(13, deck.discard_pile.len);
 | 
			
		||||
//     try std.testing.expectEqual(0, deck.cards.len);
 | 
			
		||||
    try std.testing.expectEqual(13, deck.num_discards);
 | 
			
		||||
    try std.testing.expectEqual(0, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
//     for (0..26) |i| {
 | 
			
		||||
//         switch (i % 2) {
 | 
			
		||||
//             0 => try deck.discard(playerOneHand.pop()),
 | 
			
		||||
//             1 => try deck.discard(playerTwoHand.pop()),
 | 
			
		||||
//             else => unreachable,
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
//     try std.testing.expectEqual(39, deck.discard_pile.len);
 | 
			
		||||
    for (0..26) |i| {
 | 
			
		||||
        switch (i % 2) {
 | 
			
		||||
            0 => try deck.discard(player_one_hand[i / 2].?),
 | 
			
		||||
            1 => try deck.discard(player_two_hand[i / 2].?),
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    try std.testing.expectEqual(39, deck.num_discards);
 | 
			
		||||
 | 
			
		||||
//     try deck.rebuild();
 | 
			
		||||
    deck.rebuild();
 | 
			
		||||
 | 
			
		||||
//     try std.testing.expectEqual(0, deck.discard_pile.len);
 | 
			
		||||
//     try std.testing.expectEqual(39, deck.cards.len);
 | 
			
		||||
    try std.testing.expectEqual(0, deck.num_discards);
 | 
			
		||||
    try std.testing.expectEqual(39, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
//     try std.testing.expectEqual(Card{ .suite = .Diamonds, .faceValue = .Ace }, deck.cards[0]);
 | 
			
		||||
//     try std.testing.expectEqual(Card{ .suite = .Spades, .faceValue = .Two }, deck.cards[38]);
 | 
			
		||||
// }
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .King }, deck.cards[0]);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Two }, deck.cards[38]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test "Deck.cut" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    try deck.cut(1);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Three }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    try deck.cut(0);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Three }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    try deck.cut(21);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Hearts, .face_value = .Jack }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    // lots of cuts to get back to sorted
 | 
			
		||||
    try deck.cut(5);
 | 
			
		||||
    try deck.cut(0);
 | 
			
		||||
    try deck.cut(2);
 | 
			
		||||
    try deck.cut(8);
 | 
			
		||||
    try deck.cut(12);
 | 
			
		||||
    try deck.cut(3);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Spades, .face_value = .Two }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    var deck2 = Deck{};
 | 
			
		||||
    deck2.init();
 | 
			
		||||
    try std.testing.expectEqual(deck2.num_cards, deck.num_cards);
 | 
			
		||||
 | 
			
		||||
    // Should be just like a new deck at this point
 | 
			
		||||
    for (0..deck2.num_cards) |idx| {
 | 
			
		||||
        try std.testing.expectEqual(deck2.cards[idx], deck.cards[idx]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Invalid cuts
 | 
			
		||||
    for (0..10) |_| {
 | 
			
		||||
        _ = deck.deal();
 | 
			
		||||
    }
 | 
			
		||||
    try std.testing.expectEqual(42, deck.num_cards);
 | 
			
		||||
    try std.testing.expectError(DeckError.OutOfRangeCut, deck.cut(50));
 | 
			
		||||
    try std.testing.expectError(DeckError.OutOfRangeCut, deck.cut(42));
 | 
			
		||||
 | 
			
		||||
    try deck.cut(41);
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .Ace }, deck.peak());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test "Deck.shuffle" {
 | 
			
		||||
    var deck = Deck{};
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    deck.shuffle(.{ .seed = 0 });
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .Five }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    for (0..25) |_| {
 | 
			
		||||
        _ = deck.deal();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    deck.shuffle(.{ .seed = 0 });
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Diamonds, .face_value = .Ace }, deck.peak());
 | 
			
		||||
 | 
			
		||||
    deck.sort();
 | 
			
		||||
    deck.shuffle(.{ .seed = 0 });
 | 
			
		||||
 | 
			
		||||
    try std.testing.expectEqual(Card{ .suit = .Hearts, .face_value = .Nine }, deck.peak());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										333
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										333
									
								
								src/main.zig
									
									
									
									
									
								
							@ -1,6 +1,333 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const deck = @import("deck.zig");
 | 
			
		||||
const zap = @import("zap");
 | 
			
		||||
 | 
			
		||||
pub fn main() !void {
 | 
			
		||||
    std.debug.print("It works!\n", .{});
 | 
			
		||||
const d = @import("deck.zig");
 | 
			
		||||
const DeckHttpType = enum { GET, POST };
 | 
			
		||||
const DeckHttpRoute = union(DeckHttpType) {
 | 
			
		||||
    GET: *const fn (*DeckContext) void,
 | 
			
		||||
    POST: *const fn (*DeckContext) void,
 | 
			
		||||
};
 | 
			
		||||
const DeckContext = struct {
 | 
			
		||||
    deck: *d.Deck,
 | 
			
		||||
    request: zap.Request,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var routes: std.StringHashMap(DeckHttpRoute) = undefined;
 | 
			
		||||
var deck: d.Deck = undefined;
 | 
			
		||||
var verbose = false;
 | 
			
		||||
var very_verbose = false;
 | 
			
		||||
 | 
			
		||||
pub fn main() void {
 | 
			
		||||
    // Setup stack allocator
 | 
			
		||||
    var buf: [1024]u8 = undefined;
 | 
			
		||||
    var buf_allocator = std.heap.FixedBufferAllocator.init(&buf);
 | 
			
		||||
    var arena = std.heap.ArenaAllocator.init(buf_allocator.allocator());
 | 
			
		||||
    const allocator = arena.allocator();
 | 
			
		||||
    defer arena.deinit();
 | 
			
		||||
 | 
			
		||||
    // Parse Args
 | 
			
		||||
    var args_iter = std.process.argsWithAllocator(allocator) catch {
 | 
			
		||||
        std.debug.print("ERR: Unable to initialize args parser");
 | 
			
		||||
    };
 | 
			
		||||
    var port_or_err: error{ BadPort, MissingPort }!u16 = 8081;
 | 
			
		||||
 | 
			
		||||
    _ = args_iter.skip();
 | 
			
		||||
    while (args_iter.next()) |arg| {
 | 
			
		||||
        if (std.mem.eql(u8, arg, "-h")) {
 | 
			
		||||
            printHelp();
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (std.mem.eql(u8, arg, "-p")) {
 | 
			
		||||
            const port_slice = args_iter.next();
 | 
			
		||||
            if (port_slice) |ps| {
 | 
			
		||||
                port_or_err = std.fmt.parseInt(u16, ps, 10) catch error.BadPort;
 | 
			
		||||
            } else {
 | 
			
		||||
                port_or_err = error.MissingPort;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (std.mem.eql(u8, arg, "-v")) {
 | 
			
		||||
            verbose = true;
 | 
			
		||||
        } else if (std.mem.eql(u8, arg, "-vv")) {
 | 
			
		||||
            verbose = true;
 | 
			
		||||
            very_verbose = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (port_or_err == error.BadPort) {
 | 
			
		||||
        std.debug.print("The port provided is not a valid number.", .{});
 | 
			
		||||
        return;
 | 
			
		||||
    } else if (port_or_err == error.MissingPort) {
 | 
			
		||||
        std.debug.print("A port was not provided after the cli -p option, try `-p 8000`\n", .{});
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const port: usize = @intCast(port_or_err catch 8081);
 | 
			
		||||
    std.debug.print("Starting deck server on port {d}\n", .{port});
 | 
			
		||||
    deck = d.Deck{};
 | 
			
		||||
    deck.init();
 | 
			
		||||
 | 
			
		||||
    routes = std.StringHashMap(DeckHttpRoute).init(allocator);
 | 
			
		||||
 | 
			
		||||
    routes.put("/deal", DeckHttpRoute{ .GET = deal }) catch return;
 | 
			
		||||
    routes.put("/cheat", DeckHttpRoute{ .GET = cheat }) catch return;
 | 
			
		||||
    routes.put("/discard", DeckHttpRoute{ .POST = discard }) catch return;
 | 
			
		||||
    routes.put("/cut", DeckHttpRoute{ .POST = cut }) catch return;
 | 
			
		||||
    routes.put("/rebuild", DeckHttpRoute{ .POST = rebuild }) catch return;
 | 
			
		||||
    routes.put("/sort", DeckHttpRoute{ .POST = sort }) catch return;
 | 
			
		||||
    routes.put("/shuffle", DeckHttpRoute{ .POST = shuffle }) catch return;
 | 
			
		||||
 | 
			
		||||
    var listener = zap.HttpListener.init(.{ .port = port, .on_request = onRequest, .log = verbose });
 | 
			
		||||
 | 
			
		||||
    std.debug.print("Listening\n", .{});
 | 
			
		||||
    listener.listen() catch |err| {
 | 
			
		||||
        std.debug.print("Unable to start http server: {any}\n", .{err});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    zap.start(.{ .threads = 1, .workers = 1 });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn onRequest(request: zap.Request) void {
 | 
			
		||||
    if (request.path == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    var found_route = false;
 | 
			
		||||
    if (routes.get(request.path.?)) |route| {
 | 
			
		||||
        switch (route) {
 | 
			
		||||
            .GET => {
 | 
			
		||||
                if (request.method == null) return;
 | 
			
		||||
                if (!std.mem.eql(u8, request.method.?, "GET")) return;
 | 
			
		||||
                var ctx = DeckContext{ .deck = &deck, .request = request };
 | 
			
		||||
                found_route = true;
 | 
			
		||||
                route.GET(&ctx);
 | 
			
		||||
                if (very_verbose) {
 | 
			
		||||
                    d.printDeck(&deck, .{});
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            .POST => {
 | 
			
		||||
                if (request.method == null) return;
 | 
			
		||||
                if (!std.mem.eql(u8, request.method.?, "POST")) return;
 | 
			
		||||
                var ctx = DeckContext{ .deck = &deck, .request = request };
 | 
			
		||||
                found_route = true;
 | 
			
		||||
                route.POST(&ctx);
 | 
			
		||||
                if (very_verbose) {
 | 
			
		||||
                    d.printDeck(&deck, .{});
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!found_route) {
 | 
			
		||||
        sendErrorMessage(request, "Unknown route", .not_found);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const DealGetRequest = struct {
 | 
			
		||||
    cards_in_deck: u8,
 | 
			
		||||
    face_value: []const u8,
 | 
			
		||||
    suit: []const u8,
 | 
			
		||||
    suit_unicode: []const u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn deal(ctx: *DeckContext) void {
 | 
			
		||||
    const card_or_null = ctx.deck.deal();
 | 
			
		||||
    if (card_or_null) |card| {
 | 
			
		||||
        var buf: [200]u8 = undefined;
 | 
			
		||||
        const card_json = zap.stringifyBuf(&buf, DealGetRequest{
 | 
			
		||||
            .cards_in_deck = ctx.deck.num_cards,
 | 
			
		||||
            .face_value = @tagName(card.face_value),
 | 
			
		||||
            .suit = @tagName(card.suit),
 | 
			
		||||
            .suit_unicode = d.suitToIcon(card.suit),
 | 
			
		||||
        }, .{});
 | 
			
		||||
        if (card_json == null) {
 | 
			
		||||
            ctx.request.sendError(error.BadJsonStringify, if (@errorReturnTrace()) |t| t.* else null, 500);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ctx.request.sendJson(card_json.?) catch |err| {
 | 
			
		||||
            std.debug.print("{any}", .{err});
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        ctx.request.sendBody("No cards left") catch return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn cheat(ctx: *DeckContext) void {
 | 
			
		||||
    const card_or_null = ctx.deck.peak();
 | 
			
		||||
    if (card_or_null) |card| {
 | 
			
		||||
        var buf: [200]u8 = undefined;
 | 
			
		||||
        const card_json = zap.stringifyBuf(&buf, DealGetRequest{
 | 
			
		||||
            .cards_in_deck = ctx.deck.num_cards,
 | 
			
		||||
            .face_value = @tagName(card.face_value),
 | 
			
		||||
            .suit = @tagName(card.suit),
 | 
			
		||||
            .suit_unicode = d.suitToIcon(card.suit),
 | 
			
		||||
        }, .{});
 | 
			
		||||
        if (card_json == null) {
 | 
			
		||||
            ctx.request.sendError(error.BadJsonStringify, if (@errorReturnTrace()) |t| t.* else null, 500);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ctx.request.sendJson(card_json.?) catch |err| {
 | 
			
		||||
            std.debug.print("{any}", .{err});
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        ctx.request.sendBody("No cards left") catch return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn discard(ctx: *DeckContext) void {
 | 
			
		||||
    ctx.request.parseBody() catch return;
 | 
			
		||||
    if (ctx.request.body == null) {
 | 
			
		||||
        ctx.request.setStatus(.bad_request);
 | 
			
		||||
        ctx.request.sendJson("{\"success\" = false, \"error\": \"Missing card data\"}") catch return;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
			
		||||
    const allocator = gpa.allocator();
 | 
			
		||||
    const json_body = std.json.parseFromSlice(d.Card, allocator, ctx.request.body.?, .{}) catch |err| {
 | 
			
		||||
        if (err == error.InvalidEnumTag) {
 | 
			
		||||
            ctx.request.setStatus(.bad_request);
 | 
			
		||||
            ctx.request.sendJson("{\"success\" = false, \"error\": \"Invalid card value provided\"}") catch return;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ctx.request.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 500);
 | 
			
		||||
        return;
 | 
			
		||||
    };
 | 
			
		||||
    defer json_body.deinit();
 | 
			
		||||
    ctx.deck.discard(json_body.value) catch |err| {
 | 
			
		||||
        switch (err) {
 | 
			
		||||
            d.DeckError.DuplicateInDiscard => {
 | 
			
		||||
                ctx.request.setStatus(.bad_request);
 | 
			
		||||
                ctx.request.sendJson("{\"success\": false, \"error\": \"Unable to add to discard, already exists.\"}") catch return;
 | 
			
		||||
                return;
 | 
			
		||||
            },
 | 
			
		||||
            d.DeckError.DuplicateInDeck => {
 | 
			
		||||
                ctx.request.setStatus(.bad_request);
 | 
			
		||||
                ctx.request.sendJson("{\"success\": false, \"error\": \"Unable to add to discard, card exists in deck.\"}") catch return;
 | 
			
		||||
                return;
 | 
			
		||||
            },
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ctx.request.setStatus(.ok);
 | 
			
		||||
    var buf: [100]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, .{ .success = true }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const CutPostRequest = struct {
 | 
			
		||||
    index: u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn cut(ctx: *DeckContext) void {
 | 
			
		||||
    if (ctx.request.body == null and ctx.request.query == null) {
 | 
			
		||||
        ctx.request.setStatus(.bad_request);
 | 
			
		||||
        ctx.request.sendJson("{\"success\" = false, \"error\": \"Missing cut index\"}") catch return;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    var index: u8 = undefined;
 | 
			
		||||
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
			
		||||
    const allocator = gpa.allocator();
 | 
			
		||||
    // use json body
 | 
			
		||||
    if (ctx.request.body != null) {
 | 
			
		||||
        ctx.request.parseBody() catch return;
 | 
			
		||||
        const json_body = std.json.parseFromSlice(CutPostRequest, allocator, ctx.request.body.?, .{}) catch |err| {
 | 
			
		||||
            if (err == error.Overflow) {
 | 
			
		||||
                ctx.request.setStatus(.bad_request);
 | 
			
		||||
                ctx.request.sendJson("{\"success\": false, \"error\": \"Expecting unsigned 8 bit integer as index\"}") catch return;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            ctx.request.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 500);
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
        defer json_body.deinit();
 | 
			
		||||
        index = json_body.value.index;
 | 
			
		||||
    }
 | 
			
		||||
    // use query params
 | 
			
		||||
    else {
 | 
			
		||||
        ctx.request.parseQuery();
 | 
			
		||||
        var params = ctx.request.parametersToOwnedList(allocator, false) catch |err| {
 | 
			
		||||
            ctx.request.sendError(err, if (@errorReturnTrace()) |t| t.* else null, 500);
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
        defer params.deinit();
 | 
			
		||||
        if (params.items.len != 1 or !std.mem.eql(u8, "index", params.items[0].key.str)) {
 | 
			
		||||
            sendErrorMessage(ctx.request, "Only key 'index' allowed in query params", .bad_request);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        index = @intCast(params.items[0].value.?.Int);
 | 
			
		||||
    }
 | 
			
		||||
    ctx.deck.cut(index) catch |err| {
 | 
			
		||||
        switch (err) {
 | 
			
		||||
            d.DeckError.OutOfRangeCut => {
 | 
			
		||||
                ctx.request.setStatus(.bad_request);
 | 
			
		||||
                ctx.request.sendJson("{\"success\": false, \"error\": \"Index provided is out of range\"}") catch return;
 | 
			
		||||
                return;
 | 
			
		||||
            },
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    ctx.request.setStatus(.ok);
 | 
			
		||||
    var buf: [100]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, .{ .success = true }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn rebuild(ctx: *DeckContext) void {
 | 
			
		||||
    ctx.deck.rebuild();
 | 
			
		||||
    ctx.request.setStatus(.ok);
 | 
			
		||||
    var buf: [100]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, .{ .success = true }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sort(ctx: *DeckContext) void {
 | 
			
		||||
    ctx.deck.order_deck();
 | 
			
		||||
    ctx.request.setStatus(.ok);
 | 
			
		||||
    var buf: [100]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, .{ .success = true }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn shuffle(ctx: *DeckContext) void {
 | 
			
		||||
    ctx.deck.shuffle(.{});
 | 
			
		||||
    ctx.request.setStatus(.ok);
 | 
			
		||||
    var buf: [100]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, .{ .success = true }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    ctx.request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DeckErrorMessage = struct {
 | 
			
		||||
    success: bool,
 | 
			
		||||
    message: []const u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn sendErrorMessage(request: zap.Request, msg: []const u8, status: zap.StatusCode) void {
 | 
			
		||||
    request.setStatus(status);
 | 
			
		||||
    var buf: [300]u8 = undefined;
 | 
			
		||||
    const json = zap.stringifyBuf(&buf, DeckErrorMessage{ .success = false, .message = msg }, .{});
 | 
			
		||||
    if (json == null) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    request.sendJson(json.?) catch return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn printHelp() void {
 | 
			
		||||
    const menu_text =
 | 
			
		||||
        \\Help Menu
 | 
			
		||||
        \\ -v        Verbose logging
 | 
			
		||||
        \\ -vv       Very verbose logging
 | 
			
		||||
        \\ -p <PORT> Specify a port other than the default 8081
 | 
			
		||||
        \\ -h        Prints this help menu
 | 
			
		||||
    ;
 | 
			
		||||
    std.debug.print("{s}\n", .{menu_text});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user