DEV Community

Cover image for Creating Printable Cards Games with Code
Colin Kierans
Colin Kierans

Posted on

Creating Printable Cards Games with Code

I like to make card games, and I like to code. Previously I've joined those two using React.

For my self-published card game, Dark Prophecies, I designed the cards with a TypeScript React app. Card data was written in TypeScript, rendered with React components, and styled with CSS.

I like the ability to define my cards with code. For example, this card:

Crimson Vial card from Dark Prophecies

is defined like so:

cards.push({
  quantity: 2,
  card: {
    power: 2,
    name: 'Crimson Vial',
    image: {
      url: 'https://dark.prophecies/images/2.png',
      heightPercent: 85,
      marginTop: 15,
    },
    discipline: BloodMagic,
    type: ITEM,
    effects: [
      {
        action: {
          icon: 'PlotDarkDeed',
          text: AnyDarkDeed,
        }
      },
      {
        action: {
          icon: 'Stash',
        },
        requirements: [
          {
            discipline: BloodMagic,
            level: 2,
          },
          {
            topCard: BloodMagic,
          }
        ]
      },
      {
        action: {
          text: 'Draw two Arcana cards'
        },
        requirements: [
          {
            discipline: BloodMagic,
            level: 4,
          },
          {
            reveal: BloodMagic,
          }
        ]
      },
    ]
  }
})
Enter fullscreen mode Exit fullscreen mode

I could view the cards in my browser and can make sweeping changes easily. All cards have CSS classes based on their data. For example I can set the border color of every .card-necromancy or adjust the margins on .card .action .requirement.reveal.

If I want to change the icon for all my Necromancy cards, I just update the image property in my const Necromancy : Discipline = variable.

Adding batches of cards is easy. To add something to each of the 3 Disciplines in my game, I can do:

disciplines.forEach((d) => {
  cards.push({
    quantity: 5,
    card: {
      discipline: d,
      title: d.name + ' Ritual',
      description: `Draw a card for every level of ${d.name} you have.`
    }
  })
})
Enter fullscreen mode Exit fullscreen mode

Having the cards in a big array also let me do specific counting with a .reduce. I used this to determine if I had the right number of cards in the categories I cared about. I wanted a fairly equal weighting of Spells vs Items, for example. This count was useful for knowing where to add or adjust cards.

I got the final images I need from my browser. I used a combination of Chrome's screenshot tools and the html-to-image package to them. The "Capture full size screenshot" option in the devtools was useful for making one large image to import into Tabletop Simulator for playtesting. The individual images were needed to upload my game to DriveThruCards.com.

Since print wants CMYK and the browser is RGB, getting the colors right was a pain. Something that looked purple on my screen would print as dark red. I ended up printing a page with a rainbow of colors and their hex codes, then using that to pick the colors to use in the CSS.

This was all done in a React app started from scratch, built specifically for this game. It's too much of a mess to reuse for other projects, sadly.

If you want to try coding your cards and then generating images, check out Squib with Ruby. I'm learning it for my next project.

Squib seems to really want to read data from YAML, so I'm doing a two-step process. Step 1 is creating YAML files from cards I define in code.

# generate-yaml.rb
colors = ["red", "blue", "green", "yellow", "purple"]
ships = []
portals = []
num_ships = 4
colors.each { |clr|
    puts "Adding #{num_ships} for #{clr}"
    for x in 1..num_ships do
        ships.push({
            "name" => clr.titleize + " Ship #{x}",
            "dice" => "2d6",
            "description" => "This ship is the color " + clr,
            "color" => clr,
            "qty" => 1,
        })
    end
}
File.write('data/ships.yaml', data.to_yaml)
Enter fullscreen mode Exit fullscreen mode

Step 2 is using another Ruby file to make the images:

# generate-images.rb
require 'squib'

data = Squib.yaml file: 'data/ships.yaml'
layouts = ['economy.yml', 'layout.yml']

puts data
puts data['name'].size
Squib::Deck.new(cards: data['name'].size, layout: layouts) do
  background color: 'white'
  rect layout: 'cut' # cut line as defined by TheGameCrafter
  rect layout: 'safe' # safe zone as defined by TheGameCrafter
  text str: data['name'], layout: 'title', color: data['color']
  text str: data['description'], layout: 'description'
  text str: data['dice'], layout: 'dice'
  text str: Time.now, layout: 'credits'
    svg layout: data['suit']
  save_sheet margin: 0, gap: 0, trim: 0
end
Enter fullscreen mode Exit fullscreen mode

I end up with this beautiful sheet I can start prototype in Tabletop Simulator with.

Card prototypes generated with Squib

Top comments (0)