piecepackr: Board Game Graphics

CRAN Status Badge

Build Status

AppVeyor Build Status

Coverage Status

Project Status: Active – The project has reached a stable, usable state and is being actively developed.

piecepackr is an R package designed to make configurable board game graphics. It can be used with the grid, rayrender, and rgl graphics packages to make board game diagrams, board game animations, and custom Print & Play layouts. By default it is configured to make piecepack game diagrams, animations, and "Print & Play" layouts but can be configured to make graphics for other board game systems as well.

Built-in Game Systems

The function game_systems returns configurations for multiple public domain game systems.


game_systems() returns a checkers1 and checkers2 configuration which has checkered and lined "boards" with matching checker "bits" in various sizes and colors.

df_board <- tibble(piece_side = "board_face", suit = 3, rank = 8,
               x = 4.5, y = 4.5)
df_w <- tibble(piece_side = "bit_face", suit = 6, rank = 1,
               x = rep(1:8, 2), y = rep(1:2, each=8))
df_b <- tibble(piece_side = "bit_face", suit = 1, rank = 1,
               x = rep(1:8, 2), y = rep(7:8, each=8))
df <- rbind(df_board, df_w, df_b)
df$cfg <- "checkers1"
pmap_piece(df, envir=game_systems(), default.units="in", trans=op_transform, op_scale=0.5)
Starting position for Dan Troyka's abstract game "Breakthrough"

Traditional 6-sided dice

game_systems() returns a dice configuration which can make standard 6-sided dice in six colors.

Double-12 dominoes

game_systems() returns seven different configurations for double-12 dominoes:

  1. dominoes
  2. dominoes_black
  3. dominoes_blue
  4. dominoes_green
  5. dominoes_red
  6. dominoes_white (identical to dominoes)
  7. dominoes_yellow
envir <- game_systems("dejavu")

df_dominoes <- tibble(piece_side = "tile_face", x=rep(4:1, 3), y=rep(2*3:1, each=4), suit=1:12, rank=1:12+1,
                      cfg = paste0("dominoes_", rep(c("black", "red", "green", "blue", "yellow", "white"), 2)))
df_tiles <- tibble(piece_side = "tile_back", x=5.5, y=c(2,4,6), suit=1:3, rank=1:3, cfg="piecepack")
df_dice <- tibble(piece_side = "die_face", x=6, y=0.5+1:6, suit=1:6, rank=1:6, cfg="dice")
df_coins1 <- tibble(piece_side = "coin_back", x=5, y=0.5+1:4, suit=1:4, rank=1:4, cfg="piecepack")
df_coins2 <- tibble(piece_side = "coin_face", x=5, y=0.5+5:6, suit=1:2, rank=1:2, cfg="piecepack")
df <- rbind(df_dominoes, df_tiles, df_dice, df_coins1, df_coins2)

pmap_piece(df, default.units="in", envir=envir, op_scale=0.5, trans=op_transform)
Double-12 dominoes and standard dice in a variety of colors


game_systems() returns a go configuration for Go boards and stones in a variety of colors and sizes. Here are is an example diagram for a game of Multi-player go plotted using rgl:

3D Multi-player Go diagram


game_systems() returns three different piecepack configurations:

  1. piecepack
  2. playing_cards_expansion
  3. dual_piecepacks_expansion

Plus a configuration for a subpack aka "mini" piecepack and a hexpack configuration.

The piecepack configurations also contain common piecepack accessories like piecepack pyramids, piecepack matchsticks, and piecepack saucers.

Playing Cards

game_systems() returns playing_cards, playing_cards_colored, and playing_cards_tarot (French Tarot) configurations for making diagrams with various decks of playing cards.

envir <- game_systems("dejavu", round=TRUE)

df <- tibble(piece_side = "card_face", 
             x=1.25 + 2.5 * 0:3, y=2, 
             suit=1:4, rank=c(1,6,9,12),
             cfg = "playing_cards")
pmap_piece(df, default.units="in", envir=envir)
Playing Cards

Looney Pyramids

Configurations for the proprietary Looney Pyramids aka Icehouse Pieces game system by Andrew Looney can be found in the companion R package piecenikr: https://github.com/piecepackr/piecenikr

API Intro


grid.piece is the core function that can used to draw board game components (by default piecepack game components) using grid:

g.p <- function(...) { grid.piece(..., default.units="in") }
g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("die_face", suit=3, rank=5, x=1, y=1)
g.p("pawn_face", x=1, y=4, angle=90)
g.p("coin_back", x=3, y=4, angle=180)
g.p("coin_back", suit=4, x=3, y=4, angle=180)
g.p("coin_back", suit=2, x=3, y=1, angle=90)
Piecepack diagram with default configuration

configuration lists

One can use lists to configure the appearance of the game components drawn by grid.piece:

dark_colorscheme <- list(suit_color="darkred,black,darkgreen,darkblue,black",
                     invert_colors.suited=TRUE, border_color="black", border_lex=2)
piecepack_suits <- list(suit_text="\U0001f31e,\U0001f31c,\U0001f451,\u269c,\uaa5c", # 🌞,🌜,πŸ‘‘,⚜,꩜
                    suit_fontfamily="Noto Emoji,Noto Sans Symbols2,Noto Emoji,Noto Sans Symbols,Noto Sans Cham",
traditional_ranks <- list(use_suit_as_ace=TRUE, rank_text=",a,2,3,4,5")
cfg <- c(piecepack_suits, dark_colorscheme, traditional_ranks)
g.p <- function(...) { grid.piece(..., default.units="in", cfg=pp_cfg(cfg)) }
g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("tile_back", x=0.5+3, y=0.5+1)
g.p("die_face", suit=3, rank=5, x=1, y=1)
g.p("pawn_face", x=1, y=4, angle=90)
g.p("coin_back", x=3, y=4, angle=180)
g.p("coin_back", suit=4, x=3, y=4, angle=180)
g.p("coin_back", suit=2, x=3, y=1, angle=90)
Piecepack diagram with custom configuration

oblique 3D projection

grid.piece even has some support for drawing 3D diagrams with an oblique projection:

cfg3d <- list(width.pawn=0.75, height.pawn=0.75, depth.pawn=1, 
                   dm_text.pawn="", shape.pawn="convex6", invert_colors.pawn=TRUE,
                   edge_color.coin="tan", edge_color.tile="tan")
cfg <- pp_cfg(c(cfg, cfg3d))
g.p <- function(...) { 
    grid.piece(..., op_scale=0.5, op_angle=45, cfg=cfg, default.units="in") 
g.p("tile_back", x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1))
g.p("tile_back", x=0.5+3, y=0.5+1, z=1/4+1/8)
g.p("tile_back", x=0.5+3, y=0.5+1, z=2/4+1/8)
g.p("die_face", suit=3, rank=5, x=1, y=1, z=1/4+1/4)
g.p("pawn_face", x=1, y=4, z=1/4+1/2, angle=90)
g.p("coin_back", x=3, y=4, z=1/4+1/16, angle=180)
g.p("coin_back", suit=4, x=3, y=4, z=1/4+1/8+1/16, angle=180)
g.p("coin_back", suit=2, x=3, y=1, z=3/4+1/8, angle=90)
Piecepack diagram in an oblique projection

save_print_and_play and save_piece_images

save_print_and_play makes a "Print & Play" pdf of a configured piecepack, save_piece_images makes individual images of each piecepack component:

save_print_and_play(cfg, "my_piecepack.pdf", size="letter")


If you are comfortable using R data frames there is also pmap_piece that processes data frame input. It accepts an optional trans argument for a function to pre-process the data frames, in particular if desiring to draw a 3D oblique projection one can use the function op_transform to guess both the pieces' z-coordinates and an appropriate re-ordering of the data frame given the desired angle of the oblique projection.

library("dplyr", warn.conflicts=FALSE)
df_tiles <- tibble(piece_side="tile_back", x=0.5+c(3,1,3,1,1,1), y=0.5+c(3,3,1,1,1,1))
df_coins <- tibble(piece_side="coin_back", x=rep(1:4, 4), y=rep(c(4,1), each=8),
                       suit=1:16%%2+rep(c(1,3), each=8),
                       angle=rep(c(180,0), each=8))
df <- bind_rows(df_tiles, df_coins)
cfg <- game_systems("dejavu")$piecepack
pmap_piece(df, cfg=cfg, default.units="in", trans=op_transform, op_scale=0.5, op_angle=135)
'pmap_piece' lets you use data frames as input

piece3d (rgl)

piece3d draws pieces using rgl graphics.

library("ppgames") # remotes::install_github("piecepackr/ppgames")
rgl::view3d(phi=-30, zoom = 0.8)

df <- ppgames::df_four_field_kono()
envir <- game_systems("dejavu3d")
pmap_piece(df, piece3d, trans=op_transform, envir = envir, scale = 0.98, res = 150)
3D render with rgl package

piece (rayrender)

piece creates rayrender objects.

library("ppgames") # remotes::install_github("piecepackr/ppgames")
df <- ppgames::df_xiangqi()
envir <- game_systems("dejavu3d", round=TRUE, pawn="peg-doll")
l <- pmap_piece(df, piece, trans=op_transform, envir = envir, scale = 0.98, res = 150, as_top="pawn_face")
table <- sphere(z=-1e3, radius=1e3, material=diffuse(color="green")) %>%
         add_object(sphere(x=5,y=-4, z=30, material=light(intensity=420)))
scene <- Reduce(rayrender::add_object, l, init=table)
rayrender::render_scene(scene, lookat = c(5, 5, 0), lookfrom = c(5, -7, 25), 
                        width = 500, height = 500, samples=200)
plot of chunk rayrender

Further documentation

A slightly longer intro to piecepackr's API plus several piecepackr demos and other piecpackr docs are available at piecepackr's companion website as well as some pre-configured Print & Play PDFs. More API documentation is also available in the package's built-in man pages.

Tak Example

Here we'll show an example of configuring piecepackr to draw diagrams for the abstract board game Tak (designed by James Ernest and Patrick Rothfuss).

Since one often plays Tak on differently sized boards one common Tak board design is to have boards made with colored cells arranged in rings from the center plus extra symbols in rings placed at the points so it is easy to see smaller sub-boards. To start we'll write a function to draw the Tak board.

library("grid", warn.conflicts=FALSE)
grobTakBoard <- function(...) {
    g <- "darkgreen"
    w <- "grey"
    fill <- c(rep(g, 5),
              rep(c(g, rep(w, 3), g),3),
              rep(g, 5))
    inner <- rectGrob(x = rep(1:5, 5), y = rep(5:1, each=5),
                 width=1, height=1, default.units="in", 
                 gp=gpar(col="gold", fill=fill, lwd=3))
    outer <- rectGrob(gp=gpar(col="black", fill="grey", gp=gpar(lex=2)))
    circles <- circleGrob(x=0.5+rep(1:4, 4), y=0.5+rep(4:1, each=4), r=0.1, 
                         gp=gpar(col=NA, fill="gold"), default.units="in")
    rects <- rectGrob(x=0.5+c(0:5, rep(c(0,5), 4), 0:5), 
                      y=0.5+c(rep(5,6), rep(c(4:1), each=2), rep(0, 6)),
                      width=0.2, height=0.2,
                      gp=gpar(col=NA, fill="orange"), default.units="in")
    grobTree(outer, inner, circles, rects)

Then we'll configure a Tak set and write some helper functions to draw Tak pieces with it.

cfg <- pp_cfg(list(suit_text=",,,", suit_color="white,tan4,", invert_colors=TRUE,
            ps_text="", dm_text="",
            width.board=6, height.board=6, depth.board=1/4,
            width.r1.bit=0.6, height.r1.bit=0.6, depth.r1.bit=1/4, shape.r1.bit="rect",
            width.r2.bit=0.6, height.r2.bit=1/4, depth.r2.bit=0.6, shape.r2.bit="rect", 
            width.pawn=0.5, height.pawn=0.5, depth.pawn=0.8, shape.pawn="circle",
            edge_color="white,tan4", border_lex=2,
            edge_color.board="tan", border_color.board="black"))
g.p <- function(...) { 
    grid.piece(..., op_scale=0.7, op_angle=45, cfg=cfg, default.units="in")
draw_tak_board <- function(x, y) { 
    g.p("board_back", x=x+0.5, y=y+0.5) 
draw_flat_stone <- function(x, y, suit=1) { 
    z <- 1/4*seq(along=suit)+1/8
    g.p("bit_back", x=x+0.5, y=y+0.5, z=z, suit=suit, rank=1)
draw_standing_stone <- function(x, y, suit=1, n_beneath=0, angle=0) {
    z <- (n_beneath+1)*1/4+0.3
    g.p("bit_back", x=x+0.5, y=y+0.5, z=z, suit=suit, rank=2, angle=angle)
draw_capstone <- function(x, y, suit=1, n_beneath=0) {
    z <- (n_beneath+1)*1/4+0.4
    g.p("pawn_back", x=x+0.5, y=y+0.5, z=z, suit=suit)

Then we'll draw an example Tak game diagram:

pushViewport(viewport(width=inch(6), height=inch(6)))
draw_tak_board(3, 3)
draw_flat_stone(1, 1, 1)
draw_flat_stone(1, 2, 2)
draw_flat_stone(2, 4, 1)
draw_capstone(2, 4, 2, n_beneath=1)
draw_flat_stone(2, 5, 2)
draw_flat_stone(3, 4, 1:2)
draw_flat_stone(3, 3, c(2,1,1,2))
draw_flat_stone(3, 2, 1:2)
draw_flat_stone(3, 1, 2)
draw_standing_stone(4, 2, 2, angle=90)
draw_flat_stone(5, 2, 1)
draw_capstone(5, 3, 1)
Tak game diagram


To install the last version released on CRAN use the following command in R:


To install the development version use the following commands:


The default piecepackr configuration should work out on the box on most modern OSes including Windows without the user needing to mess with their system fonts. However if you wish to use advanced piecepackr configurations you'll need to install additional Unicode fonts and Windows users are highly recommended to use and install piecepackr on "Ubuntu on Bash on Windows" if planning on using Unicode symbols from multiple fonts. The following bash commands will give you a good selection of fonts (Noto, Quivira, and Dejavu) on Ubuntu:

sudo apt install fonts-dejavu fonts-noto 
curl -O http://www.quivira-font.com/files/Quivira.otf
mv Quivira.otf $fonts_dir/
curl -O https://noto-website-2.storage.googleapis.com/pkgs/NotoEmoji-unhinted.zip
unzip NotoEmoji-unhinted.zip NotoEmoji-Regular.ttf
mv NotoEmoji-Regular.ttf $fonts_dir/
rm NotoEmoji-unhinted.zip

Note piecpackr works best if the version of R installed was compiled with support for Cairo and fortunately this is typically the case. One can confirm if this is true via R's capabilities function:

> capabilities("cairo")

Also although most users won't need them piecpackr contains utility functions that depend on the system dependencies ghostscript and poppler-utils:

  1. save_print_and_play will embed additional metadata into the pdf if ghostscript is available.
  2. get_embedded_font (a debugging helper function) needs pdffonts (usually found in poppler-utils)

You can install these utilities on Ubuntu with

sudo apt install ghostscript poppler-utils

Frequently Asked Questions

Where should I ask questions?

What is the package licence?

The code of this software package is released under a Creative Commons Attribution-ShareAlike 4.0 International license (CC BY-SA 4.0). This license is compatible with version 3 of the GNU Public License (GPL-3).

The graphical assets generated by configurations returned by piecepackr::game_systems() should be usable without attribution:

  1. Uses fonts which should allow you to embed them in images/documents without even requiring attribution.
  2. Does not embed any outside copyrighted images.1
  3. Only contains public domain game systems (which should not suffer from copyright / trademark issues).

However, third party game configurations may be encumbered by copyright / trademark issues.

Why does the package sometimes use a different font then the one I instructed it to use for a particular symbol?

Some of R's graphic devices (cairo_pdf, svg, bitmap devices like png) use Cairo which uses fontconfig to select fonts. fontconfig picks what it thinks is the 'best' font and sometimes it annoyingly decides that the font to use for a particular symbol is not the one you asked it to use. (although sometimes the symbol it chooses instead still looks nice in which case maybe you shouldn't sweat it). It is hard but not impossible to configure which fonts are dispatched by fontconfig. A perhaps easier way to guarantee your symbols will be dispatched would be to either make a new font and re-assign the symbols to code points in the Unicode "Private Use Area" that aren't used by any other font on your system or to simply temporarily move (or permanently delete) from your system font folders the undesired fonts that fontconfig chooses over your requested fonts:

# temporarily force fontconfig to use Noto Emoji instead of Noto Color Emoji in my piecepacks on Ubuntu 18.04
$ sudo mv /usr/share/fonts/truetype/noto/NotoColorEmoji.ttf ~/
## Make some piecepacks
$ sudo mv ~/NotoColorEmoji.ttf /usr/share/fonts/truetype/noto/

Also as a sanity check use the command-line tool fc-match (or the R function systemfonts::match_font) to make sure you specified your font correctly in the first place (i.e.Β fc-match "Noto Sans" on my system returns "Noto Sans" but fc-match "Sans Noto" returns "DejaVu Sans" and not "Noto Sans" as one may have expected). To help determine which fonts are actually being embedded you can use the get_embedded_font helper function:

fonts <- c('Noto Sans Symbols2', 'Noto Emoji', 'sans')
chars <- c('β™₯', 'β™ ', '♣', '♦', '🌞' ,'🌜' ,'꩜')
get_embedded_font(fonts, chars)
#     char      requested_font            embedded_font
# 1      β™₯ Noto Sans Symbols2 NotoSansSymbols2-Regular
# 2      β™  Noto Sans Symbols2 NotoSansSymbols2-Regular
# 3      ♣ Noto Sans Symbols2 NotoSansSymbols2-Regular
# 4      ♦ Noto Sans Symbols2 NotoSansSymbols2-Regular
# 5       🌞Noto Sans Symbols2                NotoEmoji
# 6       🌜Noto Sans Symbols2                NotoEmoji
# 7      ꩜ Noto Sans Symbols2     NotoSansCham-Regular
# 8      β™₯         Noto Emoji                NotoEmoji
# 9      β™          Noto Emoji                NotoEmoji
# 10     ♣         Noto Emoji                NotoEmoji
# 11     ♦         Noto Emoji                NotoEmoji
# 12      🌞        Noto Emoji                NotoEmoji
# 13      🌜        Noto Emoji                NotoEmoji
# 14     ꩜         Noto Emoji     NotoSansCham-Regular
# 15     β™₯               sans                    Arimo
# 16     β™                sans                    Arimo
# 17     ♣               sans                    Arimo
# 18     ♦               sans                    Arimo
# 19      🌞              sans                NotoEmoji
# 20      🌜              sans                NotoEmoji
# 21     ꩜               sans     NotoSansCham-Regular

  1. The outline for meeple shape used in the "meeples" configuration (also used in some face cards in the playing cards) was extracted (converted into a dataset of normalized x, y coordinates) from Meeple icon by Delapouite / CC BY 3.0. Since "simple shapes" nor data can be copyrighted under American law this meeple outline is not copyrightable in the United States. However, in other legal jurisdictions with stricter copyright laws you may need to give the proper CC BY attribution if you use any of the meeples.β†©οΈŽ