added support for multiple copies of cards, simplified sorting

This commit is contained in:
Lumen Keyes 2024-08-29 13:10:59 -06:00
parent 038dd4d78a
commit 238a4017f4
3 changed files with 953 additions and 738 deletions

View File

@ -0,0 +1,100 @@
1 Ancestral Recall (VMA) 1
1 Arid Mesa (MH2) 436 *E*
1 Boseiju, Who Endures (NEO) 266
1 Botanical Sanctum (KLD) 244
1 Brainstorm (MMQ) 61
1 Breeding Pool (RNA) 246
1 Consider (MID) 44
1 Cosmic Rebirth (MAT) 78 *F*
1 Counterspell (MMQ) 69
1 Cryptic Command (MMA) 38
1 Deserted Beach (WHO) 270
1 Dovin's Veto (TSR) 375
1 Dreamroot Cascade (VOW) 262
1 Dress Down (MH2) 39
1 Eiganjo, Seat of the Empire (NEO) 268
1 Entreat the Angels (AVR) 20
1 Field of the Dead (M20) 247
1 Flooded Strand (MH3) 220
1 Force of Negation (H1R) 9 *F*
1 Force of Will (ME1) 33
1 Get Lost (LCI) 333
1 Gideon Jura (ROE) 21
1 Gitaxian Probe (NPH) 35
1 Growth Spiral (RNA) 178
1 Hallowed Fountain (RTR) 241
1 Hedge Maze (MKM) 326 *F*
1 Ice-Fang Coatl (H1R) 27 *F*
1 Island (MMQ) 335 *F*
1 Jace, the Mind Sculptor (EMA) 57
1 Karakas (TD0) B32
1 Leyline Binding (PRM) 103396
1 Library of Alexandria (ARN) 76
1 Logic Knot (FUT) 52
1 Lose Focus (MH2) 49
1 Lush Portico (MKM) 327
1 Mana Leak (PLST) DDN-64
1 Marsh Flats (MH2) 437 *E*
1 Mental Misstep (NPH) 38
1 Mental Note (JUD) 46
1 Merchant Scroll (8ED) 91
1 Meticulous Archive (MKM) 328 *F*
1 Misty Rainforest (MH2) 250
1 Murktide Regent (MH2) 337 *F*
1 Mystic Sanctuary (ELD) 247
1 Mystical Tutor (MIR) 80
1 No More Lies (MKM) 221
1 Oko, Thief of Crowns (ELD) 197
1 Opt (STA) 19
1 Otawara, Soaring City (NEO) 271
1 Path to Exile (PF20) 1 *F*
1 Plains (INV) 331 *F*
1 Planar Genesis (MH3) 198
1 Polluted Delta (MH3) 224
1 Ponder (C21) 125
1 Portent (ICE) 90
1 Preordain (CMR) 84
1 Prismatic Ending (MH2) 384
1 Prismatic Vista (H1R) 40 *E*
1 Remand (RVR) 59
1 Reprieve (LTR) 26
1 Savannah (LEA) 280
1 Scalding Tarn (ZNE) 7
1 Scapeshift (M19) 201
1 Seachrome Coast (ONE) 258
1 Seasoned Dungeoneer (CLB) 610
1 Sensei's Divining Top (CHK) 268
1 Serum Visions (5DN) 36
1 Shark Typhoon (IKO) 67
1 Sleight of Hand (P02) 46
1 Snapcaster Mage (ISD) 78
1 Snow-Covered Forest (KHM) 285
1 Snow-Covered Island (ICE) 371
1 Snow-Covered Plains (ICE) 367
1 Solitude (MH2) 307 *F*
1 Spara's Headquarters (SNC) 257
1 Spell Pierce (SLD) 41
1 Spell Snare (DIS) 33
1 Supreme Verdict (RTR) 201
1 Swords to Plowshares (ICE) 54
1 Tamiyo, Inquisitive Student // Tamiyo, Seasoned Scholar (MH3) 443
1 Teferi, Hero of Dominaria (DOM) 207
1 Teferi, Time Raveler (WAR) 221
1 Temple Garden (RAV) 284
1 Terminus (AVR) 38
1 The One Ring (LTR) 451
1 Thought Scour (2X2) 351
1 Treasure Cruise (TSR) 319
1 Triumph of Saint Katherine (40K) 17
1 Tropical Island (LEA) 283
1 Tundra (LEA) 284
1 Underground Sea (SUM) 290
1 Up the Beanstalk (WOE) 195
1 Uro, Titan of Nature's Wrath (PTHB) 229p
1 Verdant Catacombs (MH2) 440 *E*
1 Wall of Blossoms (STH) 125
1 Wall of Omens (2X2) 344
1 Wasteland (SLD) 178
1 White Plume Adventurer (CLB) 558 *F*
1 Windswept Heath (KTK) 248
10 Plains (ONS) 330

1465
output.pdf

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,7 @@
//TODO: consider eliminating the TextCard struct
//TODO: add some kind of "update" command to support pulling new oracle data
//TODO: implement oracleFileName as a cli arg
//TODO: add more robust tests
//TODO: support other list formats
//TODO: support multiple copies of cards
const std = @import("std");
// https://github.com/Hejsil/zig-clap
@ -12,7 +10,6 @@ const clap = @import("clap");
// https://codeberg.org/dude_the_builder/zg
const DisplayWidth = @import("DisplayWidth");
// https://github.com/AndreRenaud/PDFGen
// including the header file only works for a full build, "zig run" can't find it
const c = @cImport({
@ -38,20 +35,14 @@ const Card = struct {
toughness: ?[]const u8 = null, //coerced to string
card_faces: ?[]Card = null, //array of cards
loyalty: ?[]const u8 = null, //coerced to string
isFace: bool = undefined, //cheeky little property that I added
object: []const u8 = "", //string
allText: [][]const u8 = undefined//
};
const TextCard = struct {
lines: [][]const u8 = undefined,
};
const CardSortContext = struct {
const Self = @This();
list: []TextCard,
pub fn lessThan(ctx: Self, a: usize, b: usize) bool {
return ctx.list[a].lines.len < ctx.list[b].lines.len;
}
};
fn compareCards(ctx: void, lhs: Card, rhs: Card) bool {
_ = ctx;
return lhs.allText.len < rhs.allText.len;
}
//dimension constants and defaults
const cardWidth = 30;
@ -104,43 +95,47 @@ pub fn main() !void {
constantHeight = std.mem.eql(u8, choice, "true");
}
// var timer = try std.time.Timer.start();
const oracleFileSize = (try cwd.statFile(oracleFileName)).size;
const oracleFile = try cwd.readFileAlloc(allocator, oracleFileName, oracleFileSize);
const parsedJson = try json.parseFromSlice([]Card, allocator, oracleFile, .{ .ignore_unknown_fields = true });
var cardNames = std.BufSet.init(allocator);
var cards = std.ArrayList(Card).init(allocator);
const listFileSize = (try cwd.statFile(listFileName)).size;
const listText = try cwd.readFileAlloc(allocator, listFileName, listFileSize);
var listLines = std.mem.splitAny(u8, listText, "\n");
while (listLines.next()) |line| {
if (line.len < 5) break;
const cardName = line[indexOf(u8, line, " ").? + 1 .. indexOf(u8, line, "(").? - 1];
try cardNames.insert(cardName);
const firstSpace = indexOf(u8, line, " ").?;
const cardName = line[firstSpace + 1 .. indexOf(u8, line, "(").? - 1];
const countString = line[0..firstSpace];
const printCount = try std.fmt.parseInt(u8, countString, 10);
for(0..printCount) |_| {
try cards.append(Card{.name = cardName});
}
}
var cards = std.StringArrayHashMap(TextCard).init(allocator);
for (parsedJson.value) |*cardObj| {
if (cardNames.contains(cardObj.name)) {
cardObj.isFace = false;
const printableCard = try card(cardObj.*, allocator);
try cards.put(cardObj.name, printableCard);
for(cards.items) |*cardInfo| {
if (std.mem.eql(u8, cardInfo.name, cardObj.name)) {
cardInfo.allText = try card(cardObj.*, allocator);
}
}
}
cards.sort(CardSortContext{ .list = cards.values() });
std.mem.sort(Card, cards.items, {}, compareCards);
var allPrinted = std.ArrayList([]const u8).init(allocator);
const pdf_doc: *c.pdf_doc = @ptrCast(c.pdf_create(pdfWidth, pdfHeight, &c.pdf_info{ .creator = ("My Software" ++ " " ** 53).* }).?);
var rowToPrint = std.ArrayList(TextCard).init(allocator);
for (cards.values()) |cardText| {
var rowToPrint = std.ArrayList(Card).init(allocator);
for (cards.items) |cardText| {
try rowToPrint.append(cardText);
if (rowToPrint.items.len >= 3) {
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
try printRow(allocator, rowToPrint.items[0..3].*, &allPrinted);
rowToPrint.clearAndFree();
}
} else {
try cardRow.print(allocator, rowToPrint.items, &allPrinted);
try printRow(allocator, [3]Card{rowToPrint.items[0], Card{}, Card{}}, &allPrinted);
rowToPrint.clearAndFree();
}
var page = c.pdf_append_page(pdf_doc);
@ -161,7 +156,8 @@ pub fn main() !void {
fn card(
cardObj: Card,
allocator: std.mem.Allocator,
) !TextCard {
) ![][]const u8 {
const isFace= std.mem.eql(u8, cardObj.object, "card_face");
var cardText = std.ArrayList([]const u8).init(allocator);
var fullUnformattedText = std.ArrayList(u8).init(allocator);
@ -176,10 +172,7 @@ fn card(
var finalOracleText: [30 * 100]u8 = undefined;
_ = std.mem.replace(u8, oracleBuf.items, cardObj.name, "~", &finalOracleText);
// print("{s}\n", .{finalOracleText});
if (cardObj.card_faces == null or cardObj.isFace) {
if (cardObj.card_faces == null or isFace) {
try fullUnformattedText.appendSlice(try std.mem.concat(allocator, u8, &[_][]const u8{
cardObj.name,
if (cardObj.mana_cost.len > 0) " " else "",
@ -189,20 +182,17 @@ fn card(
if(cardObj.power) |_| try std.fmt.allocPrint(allocator, "({s}/{s})", .{cardObj.power.?, cardObj.toughness.?}) else "",
if(cardObj.loyalty) |_| try std.fmt.allocPrint(allocator, "[{s}]", .{cardObj.loyalty.?}) else ""
}));
}
if (cardObj.card_faces) |faces| {
for (faces, 0..) |*face, idx| {
face.isFace = true;
const faceText = (try card(face.*, allocator)).lines;
const faceText = try card(face.*, allocator);
try fullUnformattedText.appendSlice(std.mem.trim(u8, try std.mem.join(allocator, " ", faceText), "\n"));
if (idx == 0) try fullUnformattedText.appendSlice("// ");
}
}
var line = std.ArrayList(u8).init(allocator);
//split on spaces, newlines and null
var wordIterator = std.mem.splitAny(u8, fullUnformattedText.items, &.{'\n',' ',170});
while (wordIterator.next()) |word| {
if (line.items.len + word.len + 1 >= cardWidth) {
@ -212,57 +202,49 @@ fn card(
if(word.len > 0) {
try line.appendSlice(word);
try line.append(' ');
// print("{any}\n", .{word});
// print("{s}\n", .{line.items});
assert(line.items.len < 30);
}
} else {
try cardText.append(try line.toOwnedSlice());
}
if(!cardObj.isFace and constantHeight) {
if(!isFace and constantHeight) {
assert(cardText.items.len <= cardHeight);
try cardText.appendNTimes(" " ** (cardWidth - 2), cardHeight - cardText.items.len);
} else if(!cardObj.isFace) {
} else if(!isFace) {
try cardText.append(" " ** (cardWidth - 2));
}
assert(cardText.items.len <= cardHeight);
return TextCard{ .lines = try cardText.toOwnedSlice() };
return try cardText.toOwnedSlice();
}
const linesList = std.MultiArrayList(cardRow);
fn optionalAccess(slice: anytype, index: usize) []const u8 {
if(slice.len > index) return slice[index];
return "";
}
const cardRow = struct {
fn print(allocator: std.mem.Allocator, cards: []TextCard, allPrinted: *std.ArrayList([]const u8)) !void {
var lines = std.ArrayList([]const u8).init(allocator);
const endIndex = if(cards.len > 1) std.mem.max(u64, &.{
cards[0].lines.len,
cards[1].lines.len,
cards[2].lines.len
}) else cards[0].lines.len - 1;
fn printRow(allocator: std.mem.Allocator, cards: [3]Card, allPrinted: *std.ArrayList([]const u8)) !void {
var lines = std.ArrayList([]const u8).init(allocator);
for(0..endIndex) |idx| {
try lines.append(
if(cards.len > 1)
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
optionalAccess(cards[0].lines, idx),
optionalAccess(cards[1].lines, idx),
optionalAccess(cards[2].lines, idx)
})
else cards[0].lines[idx]);
}
const endIndex = std.mem.max(u64, &.{
cards[0].allText.len,
cards[1].allText.len,
cards[2].allText.len
});
const rowHeight = lines.items.len;
while (pageHeight - (allPrinted.items.len % pageHeight) <= rowHeight) {
assert(rowHeight <= pageHeight);
try allPrinted.append(fullWidthSpacer);
}
for (lines.items) |line| {
try allPrinted.append(line);
}
for(0..endIndex) |idx| {
try lines.append(
try std.fmt.allocPrint(allocator, lineFormatter ** 3 ++ "\n", .{
optionalAccess(cards[0].allText, idx),
optionalAccess(cards[1].allText, idx),
optionalAccess(cards[2].allText, idx)
})
);
}
};
const rowHeight = lines.items.len;
while (pageHeight - (allPrinted.items.len % pageHeight) <= rowHeight) {
assert(rowHeight <= pageHeight);
try allPrinted.append(fullWidthSpacer);
}
try allPrinted.appendSlice(lines.items);
}