Conquer by Automation

2024-02-26 | 6 minute read | 1083 Words
 #writeup
 #programming

For one of my courses in uni, we are going through the basics of taking a concept from ideas to product, in this case being a board game. The idea is to get us prepared for later years when we will be doing this for later courses with much bigger prototypes and specifications. So for this project we need to create and test a prototype. Most of the details aren’t important, however what is important is that we need about ~60 cards from a collection of about ~10 defined “types” of cards. Each is sized 5cm by 8cm on A4 paper to be printed then cut out. All of the data that will be present in each of the cards is in a spreadsheet. Of course we could take a manual approach, import this into MS word, generate… mail bindings…1 and then manually define the layout for one card, then copy-paste 2 more, realign, then repeat with a new row of data. This process is extremely tedious and boring. So I decided to generate these cards as a printable pdf in about 10 minutes, because code and a little elbow grease is pretty useful for saving time.

To start, first lets make a template in LaTeX, since we can define content for this pdf in plaintext.

\newcommand{\card}[7]{
	\fbox{
	\begin{minipage}[c][8cm][t]{5cm}
		\vspace*{2em}
		\begin{itemize}[noitemsep,leftmargin=0]
			\item[] Name: #1 
			\vspace{0.5em}\hline\vspace{0.5em}
			\item[] Cost: #3 
			\item[] Energy: #4 
			\item[] Upgrade: #5 
			\item[] Range: #6 
			\item[] Type: #7 
		\end{itemize}
		\vspace{0.5em}\hline\vspace{0.5em}
		#2

	\end{minipage}
	}
}

\newcommand{\printgrid}[3]{
	\newcount\row
	\newcount\col
	\row=#1
	\loop
		\col=#2
		{\loop
			#3
			\advance\col by -1
			\ifnum\col > 0
		\repeat
		}
		\newline
	\advance\row by -1
	\ifnum\row > 0
	\repeat
}

Above, we define the macros \card and \printgrid. \card simply defines how our data input will fit into each card, \printgrid allows us to define how many rows and columns of cards we want to print. From testing, at most 3 columns can fit on this width, so each set of cards will be generated in groups of three. Next, we need to take our data and export it from the spreadsheet format to a csv file so that we can actually write code to parse it.

Special Card Name,Effects,Cost,Energy,Upgrade,Range,Action Type 
Boarding,Damage 6 to enemy ship,10,12,1,1,Combat
Cannon Barrage,Damage 8 to enemy ship,30,8,2,1,Combat
Hardened Armor,Reduces incoming damage by 1 point,10,12,1,0,Defensive
Duratae Armore,Reduces incoming damage by 2 points,30,8,2,0,Defensive
Silk Sails,Adds additional movement to a ship,20,4,2,0,Movement
Canvas Sails,Adds +2 movement points to a ship,40,2,3,0,Movement
Explosive rounds,6 damage to target,3 damage to entites 1 tile from target,40,6,3,2,Combat
Harpoon Net,Target ship has 0 movement for its next turn,50,3,4,1,Combat
Evade,Ship takes no damage if its health is below 10,60,2,4,0,Defensive

In this case we have seven fields that each have data. The attentive may notice that we already assumed 7 fields in the LaTeX template, this can be tweaked based on what kinds of data I wanted to input, but for now I just wanted to generate one set of cards.

To finish, lets glue this all together with some code. I chose go since its basically second nature to me and it is pretty solid. To preface this, there are two was we can approach this problem:

In the interest of time, I opted for the latter option since I wouldn’t have to write an extra ~10 lines of code and debug extra edge cases. Most of the extra logic is relegated to a loop that just cleans up the content of the array and strips any extra whitespace.

// The rest of the file is basically just a massive
// raw string that contains the header. The footer just ends the doc.
r := csv.NewReader(f)
vals, err:= r.ReadAll()
if err != nil {
    panic(err)
}

fmt.Println(head)
for k := range vals {
    for l := range vals[k] {
        vals[k][l] = strings.ReplaceAll(vals[k][l], "\n", " ")
        vals[k][l] = strings.TrimSpace(vals[k][l])
    }
    str := fmt.Sprintf(`\printgrid{1}{3}{{\card{%s}{%s}{%s}{%s}{%s}{%s}{%s}}}%c`,
        vals[k][0],
        vals[k][1],
        vals[k][2],
        vals[k][3],
        vals[k][4],
        vals[k][5],
        vals[k][6],
        '\n',
    )
    fmt.Print(str)
}
fmt.Println(foot)

Since I’m not working with any really arbitrary data, I opted just to make a really ugly format string to then print. I could have just made a fmt.Sprintf("{%s}", arg) if I really wanted any kind of arbitrary data to be used. I would have to modify the LaTeX template shown prior, probably as a text template, in order to print more fields for any kind of arbitrary data. However, I’m not really concerned with that since tweaking based on the input type isn’t really all that hard or slow to do. With all that said, we get a block of text printed to stdout to pipe into a texfile.

% header I dont care to show

\begin{document}

\printgrid{1}{3}{{\card{Special Card Name}{Effects}{Cost}{Energy}{Upgrade}{Range}{Action Type}}}
\printgrid{1}{3}{{\card{Boarding}{Damage 6 to enemy ship}{10}{12}{1}{1}{Combat}}}
\printgrid{1}{3}{{\card{Cannon Barrage}{Damage 8 to enemy ship}{30}{8}{2}{1}{Combat}}}
\printgrid{1}{3}{{\card{Hardened Armor}{Reduces incoming damage by  1 point}{10}{12}{1}{0}{Defensive}}}
\printgrid{1}{3}{{\card{Duratae Armore}{Reduces incoming damage by  2 points}{30}{8}{2}{0}{Defensive}}}
\printgrid{1}{3}{{\card{Silk Sails}{Adds additional movement  to a ship}{20}{4}{2}{0}{Movement}}}
\printgrid{1}{3}{{\card{Canvas Sails}{Adds +2 movement points to  a ship}{40}{2}{3}{0}{Movement}}}
\printgrid{1}{3}{{\card{Explosive rounds}{6 damage to target, 3 damage to entites 1 tile from target}{40}{6}{3}{2}{Combat}}}
\printgrid{1}{3}{{\card{Harpoon Net}{Target ship has 0 movement for its next turn}{50}{3}{4}{1}{Combat}}}
\printgrid{1}{3}{{\card{Evade}{Ship takes no damage if its  health is below 10}{60}{2}{4}{0}{Defensive}}}

\end{document}

Now we have 10 sets of 3 cards, each with a row of different stats. Ignoring the first line just being the toplevel header for the csv, we basically have a template for mass-producing any kind of card purely just by a spreadsheet. Of course we can do a whole lot to improve this to support arbitrary lengths of data, but I really don’t need that for my usecase. The overall end result actually looks pretty good! (Ignore the lackluster print quality, this is after all prototype materials).

Final version of the completed "deck" of cards
Final version of the completed "deck" of cards

So to wrap up things, theres a lot you can do just with a couple of simple programming tools way faster than you would think. Overall, I probably spent about ~10 minutes actually getting this all working. While the experience I have with these tools is much greater than most, still is a great example of what they can get done.


  1. Basically a feature in word used to template… emails? I dunno word is rubbish software that I only encounter because others use it. Still like anything on the #windowsplatformoftrash its a hacky work around for a solution that can be done with code. ↩︎

<= Previous Next =>

Liked the article? Send it to a friend!