From 4d318d20eec403c146ea9b0a8407177f42c9cbd0 Mon Sep 17 00:00:00 2001 From: Dylan Pizzo Date: Wed, 8 Jan 2025 19:08:40 -0800 Subject: [PATCH] add some cards and fix some coloring --- src/cards.ts | 207 +++++++++++++++++++++++++++++++++++++++++++- src/colorhelper.ts | 5 ++ src/dominiontext.ts | 2 +- src/draw.ts | 97 +++++++++++++++++++-- src/types.ts | 2 +- 5 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 src/colorhelper.ts diff --git a/src/cards.ts b/src/cards.ts index 687344c..dd2d1a0 100644 --- a/src/cards.ts +++ b/src/cards.ts @@ -1,8 +1,29 @@ -import { DominionCard, TYPE_TREASURE, TYPE_VICTORY } from "./types.ts"; +import { + DominionCard, + TYPE_ACTION, + TYPE_DURATION, + TYPE_NIGHT, + TYPE_TREASURE, + TYPE_VICTORY, +} from "./types.ts"; const expansionIcon = ""; const author = "Dylan"; +const sampleCard = { + orientation: "card", + title: "Sample", + description: "", + types: [TYPE_ACTION], + image: "", + artist: "", + author, + version: "0.1", + cost: "$", + preview: "", + expansionIcon, +}; + export const cards: DominionCard[] = [ { orientation: "card", @@ -21,14 +42,194 @@ export const cards: DominionCard[] = [ { orientation: "card", title: "Promising Land", - description: "Worth 1% per 3 cards you have that cost $4 or $5.", + description: "Worth 1% per 4 cards you have that cost $4 or $5.", types: [TYPE_VICTORY], image: "", artist: "", author, - version: "", + version: "0.1", + cost: "$4", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Steelworker", + description: + "If it's your Action phase, +3 Cards.\n\nIf it's your Buy phase, +1 Buy, and +$1.", + types: [TYPE_ACTION, TYPE_TREASURE], + image: "", + artist: "", + author, + version: "0.2", + cost: "$5", + preview: "$?", + expansionIcon, + }, + { + orientation: "card", + title: "Shovel", + description: + "Play a Treasure card from your hand. Then trash it from play to gain a Treasure card costing up to $3 more than it.", + types: [TYPE_TREASURE], + image: "", + artist: "", + author, + version: "0.1", + cost: "$6", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "High Council", + description: + "+2 Cards\n+1 Action\n+1 Buy\n\nEach player (including you) may choose one: +1 Card, or trash a card from their hand.", + types: [TYPE_ACTION], + image: "", + artist: "", + author: "Lou + Dylan", + version: "0.1", + cost: "$7", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Productive Village", + description: + "If it's your Action phase, +3 Actions.\n\nIf it's your Buy phase, +$1 per unused Action you have (Action, not Action card). +$1 if you have no Actions.", + types: [TYPE_ACTION, TYPE_TREASURE], + image: "", + artist: "", + author: "Dylan", + version: "0.1", + cost: "$3", + preview: "$?", + expansionIcon, + }, + { + orientation: "card", + title: "Secret Society", + description: + "+1 Action\n\nIf you have at least 3 copies of Secret Society in play, trash all of them to gain any number of cards costing at least $2, whose total combined cost is at most $50.\n\n-\n\nOn your turn, this costs $3 plus $2 per Secret Society you've gained this game.", + types: [TYPE_ACTION], + image: "", + artist: "", + author: "Dylan", + version: "0.1", + cost: "$?", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Eclipse", + description: + "+1 Card\n\nIf you have no Actions, +1 Action. If you have no Buys, +1 Buy. Return to your Action phase.", + types: [TYPE_NIGHT], + image: "", + artist: "", + author: "Dylan", + version: "0.1", + cost: "$5", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Moonlit Scheme", + description: "You may play an Action card from your hand.", + types: [TYPE_NIGHT], + image: "", + artist: "", + author: "Dylan", + version: "0.1", + cost: "$2", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Beaver", + description: + "Pay $1. If you did, gain a card costing up to the amount of $ you have.", + types: [TYPE_NIGHT], + image: "", + artist: "", + author: "Dylan", + version: "0.1", cost: "$3", preview: "", expansionIcon, }, + { + orientation: "card", + title: "Silk", + description: "Choose one: +$2, or gain a Silver.", + types: [TYPE_TREASURE], + image: "", + artist: "", + author, + version: "0.1", + cost: "$4", + preview: "$?", + expansionIcon, + }, + { + orientation: "card", + title: "Foundry", + description: + "Choose one: +1 Card, +1 Action and +$1; or trash a card from your hand to gain a card that costs up to $2 more than it.", + types: [TYPE_ACTION], + image: "", + artist: "", + author, + version: "0.1", + cost: "$5", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Vendor", + description: + "Choose three different options: +1 Card, +1 Action, +1 Buy, +$1, trash a card from your hand.", + types: [TYPE_ACTION], + image: "", + artist: "", + author, + version: "0.2", + cost: "$5", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Chateau", + description: + "1%\n\n-\n\nWhen you gain this, choose one: gain an Estate; or +1 Card, +1 Action, +1 Buy, +$1, and if it's your Buy phase, return to your Action phase.", + types: [TYPE_VICTORY], + image: "", + artist: "", + author, + version: "0.1", + cost: "$3", + preview: "", + expansionIcon, + }, + { + orientation: "card", + title: "Retainer", + description: + "Set aside a card from your hand (under this).\n\nAt any time during any of your turns, you may take +1 Action, and add the set aside card to your hand, discarding this from play.\n\nAt the start of each of your Buy phases, if the card is still set aside, +@1.", + types: [TYPE_ACTION, TYPE_DURATION], + image: "", + artist: "", + author, + version: "0.2", + cost: "$2", + preview: "", + expansionIcon, + }, ]; diff --git a/src/colorhelper.ts b/src/colorhelper.ts new file mode 100644 index 0000000..7ff915a --- /dev/null +++ b/src/colorhelper.ts @@ -0,0 +1,5 @@ +import parseColor1 from "npm:parse-color"; + +export const parseColor = (c: string): { rgb: [number, number, number] } => { + return parseColor1(c); +}; diff --git a/src/dominiontext.ts b/src/dominiontext.ts index bc35c5d..a2ced6c 100644 --- a/src/dominiontext.ts +++ b/src/dominiontext.ts @@ -391,7 +391,7 @@ export const parse = ( pieces.push({ type: "break" }); } else if (char in symbolMap) { const c = char as keyof typeof symbolMap; - const end = text.slice(i).match(new RegExp(`\\${c}\\w*`))![0] + const end = text.slice(i).match(new RegExp(`\\${c}[^ \n.,;]*`))![0] .length; const isBig = isDescription && diff --git a/src/draw.ts b/src/draw.ts index 01bcbd5..a05e567 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -1,3 +1,4 @@ +import { parseColor } from "./colorhelper.ts"; import { measureDominionText, parse, @@ -39,6 +40,10 @@ const imageList = [ key: "card-color-2", src: "/static/assets/CardColorTwo.png", }, + { + key: "card-color-2-night", + src: "/static/assets/CardColorTwoNight.png", + }, { key: "card-brown", src: "/static/assets/CardBrown.png", @@ -151,25 +156,74 @@ export const drawCard = ( } }; +const _rgbCache: Record = {}; +const getColorRgb = (c: string): { r: number; g: number; b: number } => { + const { rgb } = parseColor(c); + const [r, g, b] = rgb; + return { r, g, b }; + // if (c in _rgbCache) { + // return _rgbCache[c]!; + // } + // const canvas = document.createElement("canvas"); + // canvas.width = 10; + // canvas.height = 10; + // const context = canvas.getContext("2d")!; + // context.fillRect(0, 0, 10, 10); + // const data = context.getImageData(5, 5, 1, 1).data; + // console.log(data); + // const [r, g, b] = data; + // const rgb = { r: r!, g: g!, b: b! }; + // _rgbCache[c] = rgb; + // return rgb; +}; + +const getTextColorForBackground = (c: string): string => { + // return "black"; + const { r, g, b } = getColorRgb(c); + const avg = (r + g + b) / 3 / 255; + console.log([r, g, b], avg); + return avg > 0.5 ? "black" : "white"; +}; + const getColors = ( types: DominionCardType[] -): { primary: string; secondary: string | null } => { +): { + primary: string; + secondary: string | null; + description: string | null; + descriptionText: string; + titleText: string; +} => { + const descriptionType = + types.find((t) => t.color?.onConflictDescriptionOnly) ?? null; const byPriority = [...types] - .filter((type) => type.color) + .filter((type) => type.color && type !== descriptionType) .sort((a, b) => b.color!.priority - a.color!.priority); const priority1 = byPriority[0]!; - let primary = priority1.color?.value ?? "white"; - let secondary = byPriority[1]?.color?.value ?? null; + let primaryType: DominionCardType | null = priority1 ?? null; + let secondaryType = byPriority[1] ?? null; if (priority1 === TYPE_ACTION) { const overriders = byPriority.filter((t) => t.color!.overridesAction); if (overriders.length) { - primary = overriders[0]!.color!.value; + primaryType = overriders[0] ?? null; } - if (primary === secondary) { - secondary = byPriority[2]?.color?.value ?? null; + if (primaryType === secondaryType) { + secondaryType = byPriority[2] ?? null; } } - return { primary, secondary }; + primaryType = primaryType ?? descriptionType; + const primary = primaryType?.color?.value ?? "white"; + const secondary = secondaryType?.color?.value ?? null; + const description = descriptionType?.color?.value ?? null; + const descriptionText = getTextColorForBackground(description ?? primary); + const titleText = getTextColorForBackground(primary); + return { + primary, + secondary, + description, + descriptionText, + titleText, + }; }; const drawStandardCard = async ( @@ -213,6 +267,17 @@ const drawStandardCard = async ( 0, 0 ); + } else if (colors.description) { + context.drawImage( + colorImage(getImage("card-color-1"), colors.description), + 0, + 0 + ); + context.drawImage( + colorImage(getImage("card-color-2-night"), colors.primary), + 0, + 0 + ); } else { context.drawImage( colorImage(getImage("card-color-1"), colors.primary), @@ -224,6 +289,7 @@ const drawStandardCard = async ( context.drawImage(getImage("card-gray"), 0, 0); context.drawImage(colorImage(getImage("card-brown"), "#ff9911"), 0, 0); // Draw the name + context.fillStyle = colors.titleText; size = 78; context.font = `${size}pt DominionTitle`; while ( @@ -234,7 +300,16 @@ const drawStandardCard = async ( } await renderDominionText(context, parse(card.title), w / 2, 220); // Draw the description - context.font = "60pt DominionText"; + context.fillStyle = colors.descriptionText; + size = 60; + context.font = `${size}pt DominionText`; + while ( + (await measureDominionText(context, parse(card.description), 1000)) + .height > 650 + ) { + size -= 1; + context.font = `${size}pt DominionText`; + } await renderDominionText( context, parse(card.description, { isDescription: true }), @@ -243,6 +318,7 @@ const drawStandardCard = async ( 1000 ); // Draw the types + context.fillStyle = colors.titleText; size = 65; context.font = `${size}pt DominionTitle`; while ( @@ -264,6 +340,7 @@ const drawStandardCard = async ( 800 ); // Draw the cost + context.fillStyle = colors.titleText; context.font = "90pt DominionText"; const costMeasure = await measureDominionText(context, parse(card.cost)); await renderDominionText( @@ -273,6 +350,7 @@ const drawStandardCard = async ( 1940 ); // Draw the preview + context.fillStyle = colors.titleText; if (card.preview) { context.font = "90pt DominionText"; await renderDominionText(context, parse(card.preview), 200, 210); @@ -293,6 +371,7 @@ const drawStandardCard = async ( 2035 ); // Draw the artist credit + context.fillStyle = "white"; const artistMeasure = await measureDominionText( context, parse(card.artist) diff --git a/src/types.ts b/src/types.ts index 2fed73a..755df37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,7 +142,7 @@ export const TYPE_NIGHT: DominionBasicCardType = { typeType: "basic", name: "Night", color: { - value: "black", + value: "#485058", priority: 6, onConflictDescriptionOnly: true, },