Creating Printable Cards Games with Code
Tags: gamedev, react, typescript, ruby
Published: 2024-02-14 00:08:41

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](https://darkprophecies.com), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/daegrajx8pdnclvwmo35.png)

is defined like so:

```js
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,
          }
        ]
      },
    ]
  }
})
```

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:

```ts
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.`
    }
  })
})
```

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](https://www.npmjs.com/package/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](https://www.drivethrucards.com/product/446819/Dark-Prophecies?affiliate_id=3889566).

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](https://squib.rocks/) 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.

```ruby
# 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)
```

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

```ruby
# 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
```

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

![Card prototypes generated with Squib](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1zu6t9ccmzzlibqc507.png)