R/game_systems.R

Defines functions to_subpack to_hexpack joystick_pawn peg_doll_pawn reversi_piece shapes_cfg reversi playing_cards piecepack go chess checkers checker_piece dominoes go_stone morris marbles alquerque meeples cubes dice_numeral dice_d20 dice_d12 dice_d10_percentile dice_d10 dice_d8 dice_d4 dice_fudge dominoes_chinese dice game_systems_font color_list_fn game_systems

Documented in game_systems to_hexpack to_subpack

#' Standard game systems
#'
#' `game_systems()` returns a list of [pp_cfg()] objects
#' representing several game systems and pieces.
#' `to_subpack()` and `to_hexpack()` will attempt to generate matching (piecepack stackpack)
#'      subpack and (piecepack) hexpack [pp_cfg()] R6 objects respectively given a piecepack configuration.
#'
#' Contains the following game systems:\describe{
#' \item{alquerque}{Boards and pieces in nine color schemes for Alquerque}
#' \item{checkers1, checkers2}{Checkers and checkered boards in nine color schemes.
#'       Checkers are represented by a piecepackr \dQuote{bit}.
#'       The \dQuote{board} \dQuote{face} is a checkered board and the \dQuote{back} is a lined board.
#'       Color is controlled by suit and number of rows/columns by rank.
#'       `checkers1` has one inch squares and `checkers2` has two inch squares.}
#' \item{chess1, chess2}{Chess pieces, boards, and dice in nine color schemes.
#'       Chess pieces are represented by a \dQuote{bit} (face).
#'       The \dQuote{board} \dQuote{face} is a checkered board and the \dQuote{back} is a lined board.
#'       Color is controlled by suit and number of rows/columns by rank.
#'       `chess1` has one inch squares and `chess2` has two inch squares.
#'       Currently uses print-and-play style discs instead of 3D Staunton chess pieces.}
#' \item{cubes}{Cubes in six sizes and nine colors represented by a \dQuote{bit}.
#'              Color is controlled by suit and size of cube by rank.
#'              Sizes go from 8mm (rank 1) to 25mm (rank 6): 8mm, 10mm, 12mm, 15mm, 20mm, 25mm.}
#' \item{dice}{Traditional six-sided pipped dice in nine color schemes (color controlled by their suit).}
#' \item{dice_d4, dice_numeral, dice_d8, dice_d10, dice_d10_percentile, dice_d12, dice_d20}{
#'       Polyhedral dice most commonly used to play wargames, roleplaying games, and trading card games:\describe{
#'   \item{dice_d4}{Four-sided dice in nine color schemes (color controlled by their suit).
#'                  Tetrahedrons with the rank as a numeral at the top point.}
#'   \item{dice_numeral}{Six-sided dice with numerals instead of pips in nine color schemes (color controlled by their suit).}
#'   \item{dice_d8}{Eight-sided dice in nine color schemes (color controlled by their suit).
#'                  Octahedrons with the rank as a numeral at the top face.}
#'   \item{dice_d10}{Ten-sided dice in nine color schemes (color controlled by their suit).
#'                  Pentagonal trapezohedrons with the rank as a numeral at the top face.
#'                  The rank of ten is represented by a zero.}
#'   \item{dice_d10_percentile}{Ten-sided dice in nine color schemes (color controlled by their suit).
#'                  Pentagonal trapezohedrons with the rank as a numeral followed by a zero at the top face.
#'                  The rank of ten is represented by a zero.}
#'   \item{dice_d12}{Twelve-sided dice in nine color schemes (color controlled by their suit).
#'                  Dodecahedrons with the rank as a numeral at the top face.}
#'   \item{dice_d20}{Twenty-sided dice in nine color schemes (color controlled by their suit).
#'                   Icosahedrons with the rank as a numeral at the top face.}}}
#' \item{dice_fudge}{\dQuote{Fudge} dice in nine color schemes (color controlled by their suit).
#'                   \dQuote{Fudge} dice have three ranks "+", " ", and "-" repeated twice.}
#' \item{dominoes, dominoes_black, dominoes_blue, dominoes_green, dominoes_red, dominoes_white, dominoes_yellow}{
#'      Traditional pipped dominoes in six color schemes (`dominoes` and `dominoes_white` are the same).
#'      In each color scheme the number of pips on the \dQuote{top} of the domino is
#'      controlled by their \dQuote{rank} and on the \dQuote{bottom} by their \dQuote{suit}.
#'      Supports up to double-18 sets.}
#' \item{dominoes_chinese, dominoes_chinese_black}{`dominoes_chinese` has Asian-style six-sided pipped dice with
#'       white background and black and red pips.
#'       The \dQuote{tile}'s are Chinese dominoes (1" x 2.5") whose number of pips are controlled
#'       by both their \dQuote{rank} and their \dQuote{suit}. `dominoes_chinese_black` are like `dominoes_chinese` but the
#'       dice and dominoes have a black background and white and red pips.}
#' \item{go}{Go stones and lined boards in nine color schemes.
#'           Go stones are represented by a \dQuote{bit} and the board is a \dQuote{board}.
#'           Color is controlled by suit and number of rows/columns by rank.}
#' \item{marbles}{Marbles in nine sizes and nine colors represented by a \dQuote{bit}.
#'                Color is controlled by suit and size of marble by rank.
#'                Sizes go from 1/2" (rank 1) to 1" (rank 9) in 1/16" increments.
#'                There are also square holed boards spaced for 1" marbles: their color
#'                is controlled by suit and number of holes per row/column by rank.}
#' \item{meeples}{Standard 16mm x 16mm x 10mm \dQuote{meeples} in nine colors represented by a \dQuote{bit}.}
#' \item{morris}{Various morris aka mills aka merels games in nine colors.
#'               Color is controlled by suit and \dQuote{size} of morris board
#'               is controlled by rank e.g. \dQuote{Six men's morris} corresponds to a rank of 6 and
#'               \dQuote{Nine men's morris} corresponds to a rank of 9.
#'               Game pieces are represented by stones.}
#' \item{piecepack, dual_piecepacks_expansion, playing_cards_expansion, hexpack, subpack, piecepack_inverted}{
#'   The piecepack is a public domain game system invented by James "Kyle" Droscha.
#    There are also several public domain accessories and expansions:
#'   See \url{https://www.ludism.org/ppwiki} for more info about the piecepack and its accessories/expansions.
#'   \describe{
#'   \item{piecepack}{A standard piecepack.  The configuration also contains the following piecepack accessories:\describe{
#'     \item{piecepack dice cards}{An accessory proposed by John Braley.
#'                                 See \url{https://www.ludism.org/ppwiki/PiecepackDiceCards}.}
#'     \item{piecepack matchsticks}{A public domain accessory developed by Dan Burkey.
#'                                 See \url{https://www.ludism.org/ppwiki/PiecepackMatchsticks}.}
#'     \item{piecepack pyramids}{A public domain accessory developed by Tim Schutz.
#'                              See \url{https://www.ludism.org/ppwiki/PiecepackPyramids}.}
#'     \item{piecepack saucers}{A public domain accessory developed by Karol M. Boyle at Mesomorph Games.
#'              See \url{https://web.archive.org/web/20190719155827/http://www.piecepack.org/Accessories.html}.}}}
#' \item{piecepack_inverted}{The standard piecepack with its color scheme inverted.
#'           Intended to aid in highlighting special pieces in diagrams.}
#' \item{dual_piecepacks_expansion}{A companion piecepack with a special suit scheme.
#'               See \url{https://trevorldavis.com/piecepackr/dual-piecepacks-pnp.html}.}
#' \item{playing_cards_expansion}{A piecepack with the standard \dQuote{French} playing card suits.
#'                                See \url{https://www.ludism.org/ppwiki/PlayingCardsExpansion}.}
#' \item{hexpack}{A hexagonal extrapolation of the piecepack designed by Nathan Morse and Daniel Wilcox.
#'                See \url{https://boardgamegeek.com/boardgameexpansion/35424/hexpack}.}
#' \item{subpack}{A mini piecepack.  Designed to be used with the `piecepack` to make piecepack
#'               \dQuote{stackpack} diagrams.  See \url{https://www.ludism.org/ppwiki/StackPack}.}
#'}}
#' \item{playing_cards, playing_cards_colored, playing_cards_tarot}{
#'       Poker-sized \dQuote{card} components for various playing card decks:\describe{
#'        \item{playing_cards}{A traditional deck of playing cards with 4 suits
#'            and 13 ranks (A, 2-10, J, Q, K) plus a 14th \dQuote{Joker} rank.}
#'        \item{playing_cards_colored}{Like `playing_cards` but with five colored suits:
#'            red hearts, black spades, green clubs, blue diamonds, and yellow stars.}
#'        \item{playing_cards_tarot}{A (French Bourgeois) deck of tarot playing cards:
#'            first four suits are hearts, spades, clubs, and diamonds with
#'            14 ranks (ace through jack, knight, queen, king) plus a 15th \dQuote{Joker} rank
#'            and a fifth "suit" of 22 trump cards (1-21 plus an \dQuote{excuse}).}}}
#' \item{reversi}{Boards and pieces for Reversi.
#'    "board_face" provides lined boards with colored backgrounds.
#'    "board_back" provides checkered boards.
#'    "bit_face" / "bit_back" provides circular game tokens with differently colored sides:
#'    red paired with green, black paired with white, and blue paired with yellow.
#'}
#' }
#' @param font If `NULL` (the default) uses suit glyphs from the default \dQuote{sans} font.
#'             If `"dejavu"` it will use suit glyphs from the "DejaVu Sans" font
#'             (must be installed on the system).
#' @param round If `TRUE` the \dQuote{shape} of \dQuote{tiles} and \dQuote{cards} (and some boards)
#'              will be \dQuote{roundrect} instead of \dQuote{rect} (the default).
#' @param pawn If `"token"` (default) the piecepack pawn will be a two-sided token in a \dQuote{halma} outline,
#'             if `"peg-doll"` the piecepack pawn will be a \dQuote{peg doll} style pawn, and
#'             if `"joystick"` the piecepack pawn will be a \dQuote{joystick} style pawn.
#'             Note for the latter two pawn styles only `pawn_top` will work with [grid.piece()].
#' @param ... Must be empty
#' @param shading If `TRUE` add a shading effect to marbles and stones when drawn with [grid.piece()].
#' @param border If `TRUE` draw a black border line on piece edges.  Should normally be `TRUE` when
#'               drawing with `{grid}` graphics and `FALSE` when drawing with 3D graphic systems.
#' @param background_color Background color to use for certain pieces like boards and piecepack tiles.
#' @param edge_color Edge color to use for certain pieces like boards and piecepack tiles.
#' @examples
#' cfgs <- game_systems(pawn = "joystick")
#' names(cfgs)
#'
#' \donttest{# May take more than 5 seconds on CRAN servers
#' # standard dice, meeples, and joystick pawns
#' if (requireNamespace("grid", quietly = TRUE) && piecepackr:::device_supports_unicode()) {
#'
#'    opt <- options(piecepackr.at.inform = FALSE)
#'    grid::grid.newpage()
#'    dice <-  c("d4", "numeral", "d8", "d10", "d12", "d20")
#'    cfg <- paste0("dice_", dice)
#'    grid.piece("die_face", suit = c(1:6, 1), rank = 1:6,
#'               cfg = cfg, envir = cfgs, x = 1:6, y = 1,
#'               default.units = "in", op_scale = 0.5)
#'    grid.piece("die_face", rank=1:6, suit=1:6,
#'               x=1:6, y=2, default.units="in",
#'               op_scale=0.5, cfg=cfgs$dice)
#'    grid.piece("bit_face", suit=1:6,
#'               x=1:6, y=3, default.units="in",
#'               op_scale=0.5, cfg=cfgs$meeple)
#'    grid.piece("pawn_top", suit=1:6,
#'               x=1:6, y=4, default.units="in",
#'               op_scale=0.5, cfg=cfgs$piecepack)
#'    options(opt)
#' }
#' }
#' # dominoes
#' if (requireNamespace("grid", quietly = TRUE)) {
#'    grid::grid.newpage()
#'    colors <- c("black", "red", "green", "blue", "yellow", "white")
#'    cfg <- paste0("dominoes_", rep(colors, 2))
#'    grid.piece("tile_face",  suit=1:12, rank=1:12+1,
#'               cfg=cfg, envir=cfgs,
#'               x=rep(6:1, 2), y=rep(2*2:1, each=6),
#'               default.units="in", op_scale=0.5)
#' }
#' # piecepack "playing card expansion"
#' if (requireNamespace("grid", quietly = TRUE) && piecepackr:::device_supports_unicode()) {
#'    grid::grid.newpage()
#'    df_tiles <- data.frame(piece_side="tile_back",
#'                           x=0.5+c(3,1,3,1), y=0.5+c(3,3,1,1),
#'                           suit=NA, angle=NA, z=1/8,
#'                           stringsAsFactors=FALSE)
#'    df_coins <- data.frame(piece_side="coin_back",
#'                           x=rep(4:1, 4), y=rep(4:1, each=4),
#'                           suit=c(1,4,1,4,4,1,4,1,2,3,2,3,3,2,3,2),
#'                           angle=rep(c(180,0), each=8), z=1/4+1/16,
#'                           stringsAsFactors=FALSE)
#'    df <- rbind(df_tiles, df_coins)
#'    pmap_piece(df, cfg = cfgs$playing_cards_expansion, op_scale=0.5,
#'               default.units="in")
#' }
#' @seealso [pp_cfg()] for information about the [pp_cfg()] objects returned by `game_systems()`.
#' @export
game_systems <- function(
	font = NULL,
	round = FALSE,
	pawn = "token",
	...,
	shading = FALSE,
	border = TRUE,
	background_color = ifelse(border, "white", "burlywood"),
	edge_color = ifelse(border, "white", "black")
) {
	check_dots_empty()
	font <- game_systems_font(font)

	rect_shape <- ifelse(round, "roundrect", "rect")
	color_list <- color_list_fn(border, background_color, edge_color)

	cards <- playing_cards(font, rect_shape)
	packs <- piecepack(font, color_list, rect_shape, pawn, background_color, edge_color)

	list(
		alquerque = alquerque(1, color_list, shading),
		checkers1 = checkers(font, 1, color_list),
		checkers2 = checkers(font, 2, color_list),
		chess1 = chess(font, 1, color_list),
		chess2 = chess(font, 2, color_list),
		cubes = cubes(color_list),
		dice = dice(color_list, rect_shape),
		dice_d4 = dice_d4(font, color_list),
		dice_d8 = dice_d8(font, color_list),
		dice_d10 = dice_d10(font, color_list),
		dice_d10_percentile = dice_d10_percentile(font, color_list),
		dice_d12 = dice_d12(font, color_list),
		dice_d20 = dice_d20(font, color_list),
		dice_fudge = dice_fudge(color_list, rect_shape),
		dice_numeral = dice_numeral(font, color_list, rect_shape),
		dominoes = dominoes(color_list$suit_color[6], "black", color_list$border_color, rect_shape),
		dominoes_black = dominoes(
			color_list$suit_color[2],
			"white",
			color_list$border_color,
			rect_shape
		),
		dominoes_blue = dominoes(
			color_list$suit_color[4],
			"white",
			color_list$border_color,
			rect_shape
		),
		dominoes_green = dominoes(
			color_list$suit_color[3],
			"white",
			color_list$border_color,
			rect_shape
		),
		dominoes_red = dominoes(
			color_list$suit_color[1],
			"white",
			color_list$border_color,
			rect_shape
		),
		dominoes_white = dominoes(
			color_list$suit_color[6],
			"black",
			color_list$border_color,
			rect_shape
		),
		dominoes_yellow = dominoes(
			color_list$suit_color[5],
			"black",
			color_list$border_color,
			rect_shape
		),
		dominoes_chinese = dominoes_chinese(color_list, rect_shape),
		dominoes_chinese_black = dominoes_chinese(color_list, rect_shape, invert = TRUE),
		dual_piecepacks_expansion = packs$dpe,
		go = go(1, color_list, shading),
		hexpack = packs$hexpack,
		marbles = marbles(color_list, rect_shape, shading),
		meeples = meeples(color_list),
		morris = morris(1, color_list, shading),
		piecepack = packs$base,
		piecepack_inverted = packs$inverted,
		playing_cards = cards$base,
		playing_cards_colored = cards$color,
		playing_cards_tarot = cards$tarot,
		playing_cards_expansion = packs$pce,
		reversi = reversi(1, color_list),
		subpack = packs$subpack
	)
}

color_list_fn <- function(border = TRUE, background_color = "white", edge_color = "white") {
	if (border) {
		list(
			suit_color = cb_suit_colors_impure,
			border_color = "black",
			border_lex = 4,
			background_color = background_color,
			edge_color.board = edge_color
		)
	} else {
		list(
			suit_color = cb_suit_colors_pure,
			border_color = "transparent",
			border_lex = 0,
			background_color = background_color,
			edge_color.board = edge_color
		)
	}
}

game_systems_font <- function(font = "sans") {
	fonts <- c("dejavu", "sans")
	if (!is.null(font) && is.na(match(font, fonts))) {
		abort(paste("Don't have a customized configuration for font", dQuote(font)))
	}

	if (is.null(font)) {
		font <- "sans"
	}

	if (grepl("dejavu", font) && !suppressWarnings(has_font("Dejavu Sans"))) {
		inform(sprintf("`font = \"%s\"` but `has_font(\"Dejavu Sans\")` is `FALSE`", font))
	}

	font
}

# Okabe-Ito palette i.e. `palette.colors(n = 8, palette = "Okabe-Ito", names=TRUE)` plus white
#                         vermillion black    bluishgreen    blue     yellow     white      skyblue reddishpurple orange
#                         red        black      green      blue       yellow     white      cyan       magenta    orange
cb_suit_colors_pure <- c(
	"#D55E00",
	"#000000",
	"#009E73",
	"#0072B2",
	"#F0E442",
	"#FFFFFF",
	"#56B4E9",
	"#CC79A7",
	"#E69F00"
)
cb_suit_colors_impure <- cb_suit_colors_pure
cb_suit_colors_impure[2L] <- "grey30" # a dark gray to contrast with black borders

DICE_PIP_COLORS <- "white,white,white,white,black,black,black,black,white"
N_SUITS_COLOR <- 9L

dice <- function(color_list = color_list_fn(), rect_shape = "rect") {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 6L,
		rank_text = "1,2,3,4,5,6",
		width.die = 16 / 25.4, # 16 mm dice most common
		background_color = DICE_PIP_COLORS,
		invert_colors = TRUE,
		die_arrangement = "opposites_sum_to_5",
		shape.card = rect_shape,
		grob_fn.card_face = cardGrobFn(grob_type = "circle"),
		grob_fn.die = pippedGrobFn(0, "die")
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dominoes_chinese <- function(color_list = color_list_fn(), rect_shape = "rect", invert = FALSE) {
	dice_list <- list(
		n_suits = 6L,
		n_ranks = 6L,
		rank_text = "1,2,3,4,5,6",
		width.die = 16 / 25.4, # 16 mm dice most common
		background_color = ifelse(invert, color_list$suit_color[2], "white"),
		suit_color = ifelse(invert, "white", "black"),
		die_arrangement = "1,2,3,6>,5,4",
		grob_fn.die = pippedGrobFn(0, "die_chinese"),
		width.tile = 1,
		height.tile = 2.5,
		depth.tile = 0.5,
		gridline_color.tile_back = "transparent",
		gridline_color.tile_face = "transparent",
		gridline_lex.tile_face = 6,
		shape.tile = rect_shape,
		grob_fn.tile_face = dominoGrobFn(0, "domino_chinese"),
		shape.card = rect_shape,
		grob_fn.card_face = cardGrobFn(grob_type = "circle")
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_fudge <- function(color_list = color_list_fn(), rect_shape = "rect") {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 6,
		rank_text = "-, ,+,+, ,-",
		dm_text.die = "",
		ps_cex.die = 2,
		grob_fn.die = fudgeGrob,
		width.die = 16 / 25.4, # 16 mm dice most common
		background_color = DICE_PIP_COLORS,
		invert_colors = TRUE,
		die_arrangement = "opposites_sum_to_5",
		shape.card = rect_shape
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_d4 <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 4,
		rank_text.die = 1:4,
		dm_text.die = "",
		ps_cex.die = 1,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		grob_fn.die = d4Grob,
		op_grob_fn.die = d4TopGrob,
		obj_fn.die = save_d4_obj,
		width.die = (21 / 25.4) / 0.8660254, # if 21 mm vertex to vertex
		height.die = (21 / 25.4) / 0.8660254, # if 21 mm vertex to vertex
		depth.die = (sqrt(6) / 3) * (21 / 25.4),
		background_color = DICE_PIP_COLORS,
		shape.die_face = "convex3",
		shape_t.die = 90,
		invert_colors = TRUE
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_d8 <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 8,
		rank_text.die = 1:8,
		ps_cex.die = 1.15,
		ps_r.die = 0.03,
		ps_t.die = 90,
		dm_text.die = "",
		dm_text.r6.die = "\u2012",
		dm_r.die = ifelse(font == "dejavu", 0.14, 0.13),
		dm_t.die = 270,
		dm_cex.die = 1.5,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		op_grob_fn.die = d8TopGrob,
		obj_fn.die = save_d8_obj,
		width.die = (18 / 25.4) / 0.8660254, # if 18 mm vertex to vertex
		height.die = (18 / 25.4) / 0.8660254, # if 18 mm vertex to vertex
		depth.die = (sqrt(6) / 3) * (18 / 25.4), # inradius = sqrt(6) * a / 6
		background_color = DICE_PIP_COLORS,
		shape.die_face = "convex3",
		shape_t.die = 90,
		invert_colors = TRUE
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

# We'll use alpha-90-beta-90 angle kites
# See `utils-d10.R` for more notes on calculations
dice_d10 <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 10L,
		rank_text.die = c(1:9, 0),
		ps_cex.die = 1.15,
		ps_r.die = -0.08,
		ps_t.die = 90,
		dm_text.die = "",
		dm_text.r6.die = "\u2012",
		dm_text.r9.die = "\u2012",
		dm_r.die = ifelse(font == "dejavu", 0.29, 0.28),
		dm_t.die = 270,
		dm_cex.die = 1.5,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		op_grob_fn.die = d10TopGrob,
		obj_fn.die = save_d10_obj,
		width.die = 0.4913446110983896164548, # if kite height 5/8"
		height.die = 5 / 8,
		depth.die = 0.5621585749837085810299,
		background_color = DICE_PIP_COLORS,
		shape.die_face = "kite",
		shape_r.die = 0.5 - 0.1909830056250526320039,
		invert_colors = TRUE
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_d10_percentile <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 10L,
		rank_text.die = c(1:9, 0),
		dm_text.die = "0",
		dm_cex.die = 1.15,
		dm_r.die = -0.05,
		dm_t.die = 180,
		ps_cex.die = 1.00,
		ps_r.die = 0.20,
		ps_t.die = 180,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		op_grob_fn.die = d10TopGrob,
		obj_fn.die = save_d10_obj,
		width.die = 5 / 8,
		height.die = 0.4913446110983896164548,
		depth.die = 0.5621585749837085810299,
		background_color = DICE_PIP_COLORS,
		shape.die_face = "kite",
		shape_r.die = 0.5 - 0.1909830056250526320039,
		shape_t.die = 0,
		invert_colors = TRUE
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_d12 <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 12L,
		rank_text.die = 1:12,
		ps_cex.die = 1.15,
		ps_r.die = 0.01,
		ps_t.die = 90,
		dm_text.die = "",
		dm_text.r6.die = "\u2012",
		dm_text.r9.die = "\u2012",
		dm_r.die = ifelse(font == "dejavu", 0.25, 0.22),
		dm_t.die = 270,
		dm_cex.die = 1.5,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		op_grob_fn.die = d12TopGrob,
		obj_fn.die = save_d12_obj,
		width.die = (5 / 16) / 0.5877853, # if 5/16" vertex to vertex
		height.die = (5 / 16) / 0.5877853, # if 5/16" vertex to vertex
		depth.die = 2 * 1.113516364 * (5 / 16), # inradius = 1.113516364 * a
		background_color = DICE_PIP_COLORS,
		shape.die_face = "convex5",
		shape_t.die = 90,
		invert_colors = TRUE
	)
	for (i in 10:12) {
		dice_list[[str_glue("ps_cex.r{i}.die")]] <- 1.00
		# dice_list[[str_glue("ps_r.r{i}.die")]] <- 0.02
	}
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_d20 <- function(font = "sans", color_list = color_list_fn()) {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 20L,
		rank_text.die = 1:20,
		ps_cex.die = 0.65,
		ps_r.die = -0.02,
		ps_t.die = 90,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		dm_text.die = "",
		dm_text.r6.die = "\u2012",
		dm_text.r9.die = "\u2012",
		dm_r.die = ifelse(font == "dejavu", 0.12, 0.13),
		dm_t.die = 270,
		dm_cex.die = 1.0,
		op_grob_fn.die = d20TopGrob,
		obj_fn.die = save_d20_obj,
		width.die = 0.5 / 0.8660254, # if 1/2" vertex to vertex
		height.die = 0.5 / 0.8660254, # if 1/2" vertex to vertex
		depth.die = 2 * 0.5 * (3 * sqrt(3) + sqrt(15)) / 12,
		background_color = DICE_PIP_COLORS,
		shape.die_face = "convex3",
		shape_t.die = 90,
		invert_colors = TRUE
	)
	for (i in 1:9) {
		dice_list[[str_glue("ps_cex.r{i}.die")]] <- 0.72
		dice_list[[str_glue("ps_r.r{i}.die")]] <- 0.02
	}
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

dice_numeral <- function(font = "sans", color_list = color_list_fn(), rect_shape = "rect") {
	dice_list <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 6L,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		rank_text = 1:6,
		ps_cex.die = 1.8,
		dm_text.die = "",
		dm_text.r6.die = "\u2012",
		dm_r.die = 0.315,
		dm_t.die = 270,
		dm_cex.die = 2.4,
		width.die = 16 / 25.4, # 16 mm dice most common
		background_color = DICE_PIP_COLORS,
		invert_colors = TRUE,
		die_arrangement = "1<,2>,3>,6v,5,4",
		shape.card = rect_shape
	)
	dice <- pp_cfg(c(dice_list, color_list))
	dice$has_piecepack <- FALSE
	dice$has_dice <- TRUE
	dice
}

cubes <- function(color_list = color_list_fn()) {
	sizes_mm <- c(8, 10, 12, 15, 20, 25)
	cubes_list <- list(
		shape.bit = "rect",
		n_suits = N_SUITS_COLOR,
		n_ranks = 6L,
		ps_text.bit = "",
		dm_text.bit = "",
		invert_colors.bit = TRUE,
		background_color = "white"
	)
	for (i in seq_along(sizes_mm)) {
		size_in <- sizes_mm[i] / 25.4
		cubes_list[[str_glue("width.r{i}.bit")]] <- size_in
		cubes_list[[str_glue("height.r{i}.bit")]] <- size_in
		cubes_list[[str_glue("depth.r{i}.bit")]] <- size_in
	}
	cubes <- pp_cfg(c(cubes_list, color_list))
	cubes$has_piecepack <- FALSE
	cubes$has_bits <- TRUE
	cubes
}

meeples <- function(color_list = color_list_fn()) {
	meeples_list <- list(
		shape.bit_face = "meeple",
		shape.bit_back = "meeple",
		n_suits = N_SUITS_COLOR,
		width.bit = 16 / 25.4,
		height.bit = 16 / 25.4,
		depth.bit = 10 / 25.4,
		ps_text.bit = "",
		dm_text.bit = "",
		invert_colors.bit = TRUE,
		background_color = "white"
	)
	meeples <- pp_cfg(c(meeples_list, color_list))
	meeples$has_piecepack <- FALSE
	meeples$has_bits <- TRUE
	meeples
}

alquerque <- function(cell_width = 1, color_list = color_list_fn(), shading = FALSE) {
	alquerque <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 1L,
		width.board = 5 * cell_width,
		height.board = 5 * cell_width,
		grob_fn.board_face = alquerqueBoardGrobFn(),
		gridline_color.board_face = cb_suit_colors_pure,
		gridline_color.board_back = "transparent",
		gridline_lex.board = 4,
		suit_color = cb_suit_colors_impure,
		background_color = color_list$background_color,
		gridline_color.s6.board_face = "grey80"
	)
	if (shading) {
		alquerque[["edge_color.board"]] <- cheap_darken(color_list$background_color, 0.3)
	}
	alquerque <- pp_cfg(c(alquerque, go_stone(cell_width / 2, shading), color_list))
	alquerque$has_piecepack <- FALSE
	alquerque$has_boards <- TRUE
	alquerque$has_bits <- TRUE
	alquerque
}

marbles <- function(color_list = color_list_fn(), rect_shape = "rect", shading = FALSE) {
	margin <- 0.25
	marbles <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 9L,
		width.board = 4 + 2 * margin,
		invert_colors.bit = TRUE,
		invert_colors.board = TRUE,
		grob_fn.bit = ellipsoidGrobFn(shading),
		op_grob_fn.bit = basicEllipsoidFn(shading),
		obj_fn.bit = function(...) save_ellipsoid_obj(..., subdivide = 4),
		grob_fn.board = holedBoardGrobFn(4L, 4L, margin),
		op_grob_fn.board = basicHoledBoardFn(4L, 4L, margin),
		obj_fn.board = save_holed_board_obj_fn(4L, 4L, margin),
		shape.board = rect_shape,
		ps_text.bit = "",
		dm_text.bit = "",
		suit_color = cb_suit_colors_impure,
		background_color = color_list$background_color
	)
	if (shading) {
		marbles[["edge_color.board"]] <- cheap_darken(cb_suit_colors_impure, 0.3)
	} else {
		marbles[["edge_color.board"]] <- cb_suit_colors_impure
	}
	for (i in seq.int(9L)) {
		marbles[[str_glue("depth.r{i}.bit")]] <- (7 + i) / 16
		marbles[[str_glue("width.r{i}.bit")]] <- (7 + i) / 16
	}
	for (i in seq.int(2L, 12L)) {
		marbles[[str_glue("width.r{i}.board")]] <- i + 2 * margin
		marbles[[str_glue("height.r{i}.board")]] <- i + 2 * margin
		marbles[[str_glue("grob_fn.r{i}.board")]] <- holedBoardGrobFn(i, i, margin)
		marbles[[str_glue("op_grob_fn.r{i}.board")]] <- basicHoledBoardFn(i, i, margin)
		marbles[[str_glue("obj_fn.r{i}.board")]] <- save_holed_board_obj_fn(i, i, margin)
	}
	marbles <- pp_cfg(c(marbles, color_list))
	marbles$has_piecepack <- FALSE
	marbles$has_boards <- TRUE
	marbles$has_bits <- TRUE
	marbles
}

morris <- function(cell_width = 1, color_list = color_list_fn(), shading = FALSE) {
	morris <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 15L,
		width.board = 7 * cell_width,
		height.board = 7 * cell_width,
		grob_fn.r1.board_face = morrisBoardGrobFn(12),
		gridline_color.board_face = cb_suit_colors_pure,
		gridline_color.board_back = "transparent",
		gridline_lex.board = 4,
		suit_color = cb_suit_colors_impure,
		background_color = color_list$background_color,
		gridline_color.s6.board_face = "grey80"
	)
	for (i in seq.int(2L, 15L)) {
		if (i < 5) {
			morris[[str_glue("width.r{i}.board")]] <- 3 * cell_width
			morris[[str_glue("height.r{i}.board")]] <- 3 * cell_width
		} else if (i < 8) {
			morris[[str_glue("width.r{i}.board")]] <- 5 * cell_width
			morris[[str_glue("height.r{i}.board")]] <- 5 * cell_width
		} else {
			morris[[str_glue("width.r{i}.board")]] <- 7 * cell_width
			morris[[str_glue("height.r{i}.board")]] <- 7 * cell_width
		}
		morris[[str_glue("grob_fn.r{i}.board_face")]] <- morrisBoardGrobFn(i)
	}
	if (shading) {
		morris[["edge_color.board"]] <- cheap_darken(color_list$background_color, 0.3)
	}
	morris <- pp_cfg(c(morris, go_stone(cell_width, shading), color_list))
	morris$has_piecepack <- FALSE
	morris$has_boards <- TRUE
	morris$has_bits <- TRUE
	morris
}

go_stone <- function(cell_width, shading = FALSE) {
	list(
		width.bit = 0.8700787 * cell_width,
		depth.bit = 0.3937008 * cell_width,
		invert_colors.bit = TRUE,
		grob_fn.bit = ellipsoidGrobFn(shading),
		op_grob_fn.bit = basicEllipsoidFn(shading),
		obj_fn.bit = function(...) save_ellipsoid_obj(..., subdivide = 4),
		ps_text.bit = "",
		dm_text.bit = ""
	)
}

dominoes <- function(
	background_color = "white",
	suit_color = "black",
	border_color = "black",
	rect_shape = "rect",
	mat_width = 0
) {
	border_lex <- ifelse(border_color == "black", 4, 0)
	dominoes <- pp_cfg(list(
		n_suits = 18 + 1,
		n_ranks = 18 + 1,
		width.tile = 1,
		height.tile = 2,
		depth.tile = 0.25, # 3/8 professional, 1/2 jumbo
		width.die = 16 / 25.4,
		suit_color = suit_color,
		background_color = background_color,
		mat_width = mat_width,
		mat_color = suit_color,
		border_color = border_color,
		border_lex = border_lex,
		die_arrangement = "opposites_sum_to_5",
		grob_fn.die = pippedGrobFn(0, "die"),
		grob_fn.card_face = cardGrobFn(-1, "card", grob_type = "circle"),
		gridline_color.tile_back = "transparent",
		gridline_color.tile_face = suit_color,
		gridline_lex.tile_face = 6,
		shape.tile = rect_shape,
		shape.card = rect_shape,
		grob_fn.tile_face = dominoGrobFn(-1)
	))
	dominoes$has_piecepack <- FALSE
	dominoes$has_dice <- TRUE
	dominoes$has_tiles <- TRUE
	dominoes
}

checker_piece <- function(cell_width, ps_text.bit_face = "", ps_cex.bit_face = 1.3) {
	list(
		dm_text.bit = "",
		invert_colors.bit = TRUE,
		ps_text.bit_back = "",
		ps_text.bit_face = ps_text.bit_face,
		ps_cex.bit_face = cell_width * ps_cex.bit_face,
		width.bit = 0.75 * cell_width,
		background_color.s5.bit_face = "black",
		background_color.s6.bit_face = "black"
	)
}

checkers <- function(font = "sans", cell_width = 1, color_list = color_list_fn()) {
	checkers <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 12L,
		width.board = 8 * cell_width,
		height.board = 8 * cell_width,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		grob_fn.r1.board_face = checkeredBoardGrobFn(8, 8),
		grob_fn.r1.board_back = linedBoardGrobFn(8, 8),
		gridline_color.board_face = cb_suit_colors_impure,
		gridline_color.board_back = cb_suit_colors_pure,
		gridline_lex.board = 4,
		suit_color = cb_suit_colors_impure,
		background_color = "white",
		gridline_color.s6.board_face = "grey80",
		gridline_color.s6.board_back = "grey80"
	)
	for (i in seq.int(2L, 12L)) {
		checkers[[str_glue("width.r{i}.board")]] <- i * cell_width
		checkers[[str_glue("height.r{i}.board")]] <- i * cell_width
		checkers[[str_glue("grob_fn.r{i}.board_face")]] <- checkeredBoardGrobFn(i, i)
		checkers[[str_glue("grob_fn.r{i}.board_back")]] <- linedBoardGrobFn(i, i)
	}
	# \u25cb white circle \u25cf black circle \u265b black queen \u2605 black star
	# \u272a circled white star
	if (font == "sans") {
		bits <- checker_piece(cell_width, "\u25cb", 1.7)
	} else {
		bits <- checker_piece(cell_width, "\u272a", 1.3)
	}
	checkers <- pp_cfg(c(checkers, bits, color_list))
	checkers$has_piecepack <- FALSE
	checkers$has_boards <- TRUE
	checkers$has_bits <- TRUE
	checkers
}

chess <- function(font = "sans", cell_width = 1, color_list = color_list_fn()) {
	if (font == "sans") {
		rank_cex_die <- 1.3
		black_chess_ranks <- c("p", "n", "b", "r", "q", "k")
		white_chess_ranks <- c("P", "N", "B", "R", "Q", "K")
	} else if (font == "dejavu") {
		rank_cex_die <- 1.5
		black_chess_ranks <- c("\u265f", "\u265e", "\u265d", "\u265c", "\u265b", "\u265a")
		white_chess_ranks <- c("\u2659", "\u2658", "\u2657", "\u2656", "\u2655", "\u2654")
	}
	chess <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 12L,
		width.board = 8 * cell_width,
		height.board = 8 * cell_width,
		width.bit = 0.75 * cell_width,
		ps_text.bit_back = "",
		dm_text.bit = "",
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		grob_fn.r1.board_face = checkeredBoardGrobFn(8, 8),
		grob_fn.r1.board_back = linedBoardGrobFn(8, 8),
		grob_fn.s5.bit_face = basicPieceGrobFn(fill_stroke = TRUE),
		grob_fn.s5.die_face = basicPieceGrobFn(fill_stroke = TRUE),
		grob_fn.s7.bit_face = basicPieceGrobFn(fill_stroke = TRUE),
		grob_fn.s7.die_face = basicPieceGrobFn(fill_stroke = TRUE),
		grob_fn.s8.bit_face = basicPieceGrobFn(fill_stroke = TRUE),
		grob_fn.s8.die_face = basicPieceGrobFn(fill_stroke = TRUE),
		# grob_fn.s6.bit_face = basicPieceGrob,
		gridline_color.board_face = cb_suit_colors_impure,
		gridline_color.board_back = cb_suit_colors_pure,
		gridline_lex.board = 4,
		suit_text = "",
		rank_cex.bit = 1.4 * cell_width,
		rank_cex.die = rank_cex_die,
		rank_text = black_chess_ranks,
		rank_text.s6 = white_chess_ranks,
		suit_color = cb_suit_colors_pure,
		suit_color.s6 = "black",
		background_color = "white",
		edge_color.bit = color_list$edge_color.board,
		gridline_color.s6.board_face = "grey80",
		gridline_color.s6.board_back = "grey80"
	)
	for (i in seq.int(2L, 12L)) {
		chess[[str_glue("width.r{i}.board")]] <- i * cell_width
		chess[[str_glue("height.r{i}.board")]] <- i * cell_width
		chess[[str_glue("grob_fn.r{i}.board_face")]] <- checkeredBoardGrobFn(i, i)
		chess[[str_glue("grob_fn.r{i}.board_back")]] <- linedBoardGrobFn(i, i)
	}
	chess <- pp_cfg(c(chess, color_list))
	chess$has_piecepack <- FALSE
	chess$has_boards <- TRUE
	chess$has_bits <- TRUE
	chess$has_dice <- TRUE
	chess
}

go <- function(cell_width = 1, color_list = color_list_fn(), shading = FALSE) {
	go <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 19L,
		width.board = 19 * cell_width,
		height.board = 19 * cell_width,
		grob_fn.board_back = basicPieceGrob,
		grob_fn.r1.board_face = linedBoardGrobFn(18L, 18L, 0.5),
		gridline_color.board_face = cb_suit_colors_pure,
		gridline_color.board_back = "transparent",
		gridline_lex.board = 4,
		suit_color = cb_suit_colors_impure,
		background_color = color_list$background_color,
		gridline_color.s6.board_face = "grey80"
	)
	if (shading) {
		go[["edge_color.board"]] <- cheap_darken(color_list$background_color, 0.3)
	}
	for (i in seq.int(2L, 19L)) {
		go[[str_glue("width.r{i}.board")]] <- i * cell_width
		go[[str_glue("height.r{i}.board")]] <- i * cell_width
		go[[str_glue("grob_fn.r{i}.board_face")]] <- linedBoardGrobFn(i - 1L, i - 1L, 0.5)
	}
	go <- pp_cfg(c(go, go_stone(cell_width, shading), color_list))
	go$has_piecepack <- FALSE
	go$has_boards <- TRUE
	go$has_bits <- TRUE
	go
}

piecepack <- function(
	font = "sans",
	color_list = color_list_fn(),
	rect_shape = "rect",
	pawn = "token",
	background_color = "white",
	edge_color = "white"
) {
	if (font == "sans") {
		piecepack_suit_text <- list(suit_text = "\u263c,\u25d8,\u0238,\u03ee,\u2202", n_suits = 4L)
		pce_suit_text <- list(suit_text = "\u2665,\u2660,\u2663,\u2666,\u2202", n_suits = 4L)
		dpe_suit_text <- list(
			suit_text = "\u2665,\u2660,\u2663,\u2666,*,\u2202",
			suit_cex.s5 = 1.3,
			n_suits = 5L
		)
	} else if (font == "dejavu") {
		piecepack_suit_text <- list(
			suit_text = "\u2742,\u25d0,\u265b,\u269c,\u0ed1",
			suit_cex.s2 = 0.9,
			dm_cex.coin = 0.5,
			n_suits = 4L
		)
		pce_suit_text <- list(suit_text = "\u2665,\u2660,\u2663,\u2666,\u0ed1", n_suits = 4L)
		dpe_suit_text <- list(
			suit_text = "\u2665,\u2660,\u2663,\u2666,\u2605,\u0ed1",
			suit_cex.s5 = 0.92,
			n_suits = 5L,
			grob_fn.s5 = basicPieceGrobFn(fill_stroke = TRUE),
			grob_fn.s5.card_face = cardGrobFn(-1, fill_stroke = TRUE)
		)
	}

	piecepack_base <- list(
		depth.coin = 0.25,
		invert_colors.matchstick = TRUE,
		ps_cex.r2.matchstick = 0.7,
		dm_r.r1.matchstick = 0,
		dm_cex.r1.matchstick = 1.5,
		suit_color.s2.matchstick = "grey30",
		suit_color.s2.bit = "grey30",
		mat_color.tile_back = background_color,
		mat_width.tile_back = 0.05,
		fontfamily = ifelse(font == "dejavu", "DejaVu Sans", "sans"),
		background_color.die = background_color,
		background_color.pyramid = background_color,
		background_color.matchstick = background_color,
		edge_color.tile = edge_color,
		edge_color.coin = edge_color,
		suit_color.unsuited = "black",
		invert_colors.bit = TRUE,
		rank_text = ",a,2,3,4,5",
		use_suit_as_ace = TRUE,
		rank_text.pyramid = LETTERS[1:6],
		suit_text.saucer_back = "",
		suit_cex.saucer_face = 0.9,
		suit_cex.pyramid_face = 0.5,
		ps_r.pyramid_face = -0.08,
		dm_cex.pyramid_face = 4.0,
		dm_text.pyramid_face = "|",
		dm_r.pyramid_face = -0.22,
		ps_cex.pyramid_left = 0.7,
		ps_r.pyramid_left = -0.00,
		ps_cex.pyramid_right = 0.7,
		ps_r.pyramid_right = -0.00,
		grob_fn.card_face = cardGrobFn(-1),
		use_suit_as_ace.pyramid = FALSE,
		shape.tile = rect_shape,
		shape.card = rect_shape
	)
	shapes <- shapes_cfg(color_list)
	pawn <- switch(
		pawn,
		"peg-doll" = peg_doll_pawn(shapes),
		"joystick" = joystick_pawn(shapes),
		NULL
	)
	piecepack <- c(pawn, piecepack_suit_text, color_list, piecepack_base)

	playing_cards_expansion <- c(pce_suit_text, piecepack)
	playing_cards_expansion$suit_color <- "#D55E00,#000000,#000000,#D55E00,#F0E442"
	playing_cards_expansion$suit_color.s3.matchstick <- "grey30"
	playing_cards_expansion$suit_color.s3.bit <- "grey30"

	hexpack <- c(
		piecepack,
		list(
			shape.tile_face = "convex6",
			shape.tile_back = "convex6",
			border_lex = 3,
			shape_t.tile = "60",
			dm_t.tile_face = -90,
			width.tile = 4 / sqrt(3),
			height.tile = 4 / sqrt(3),
			shape.coin_face = "convex3",
			shape.coin_back = "convex3"
		)
	)

	dpe_base <- list(
		invert_colors.suited = TRUE,
		border_color.s2.die = "black",
		border_color.s2.pawn = "black",
		suit_color.s2.board_face = "black"
	)

	dual_piecepacks_expansion <- c(dpe_suit_text, dpe_base, piecepack)

	pi_base <- list(
		invert_colors = TRUE,
		invert_colors.matchstick = FALSE,
		suit_color.s2.matchstick = "black",
		suit_color.s2.board_face = "black",
		suit_color.card_back = "grey30",
		suit_color.coin_face = "grey30",
		edge_color.coin = "white",
		edge_color.tile = "white"
	)
	piecepack_inverted <- c(pi_base, piecepack)

	piecepack <- c(list(suit_color = cb_suit_colors_pure), piecepack)

	base <- pp_cfg(piecepack)
	dpe <- pp_cfg(dual_piecepacks_expansion)
	hexpack <- to_hexpack(piecepack)
	inverted <- pp_cfg(piecepack_inverted)
	pce <- pp_cfg(playing_cards_expansion)
	subpack <- to_subpack(piecepack)

	base$has_cards <- TRUE
	dpe$has_cards <- TRUE
	inverted$has_cards <- TRUE
	pce$has_cards <- TRUE

	base$has_matchsticks <- TRUE
	dpe$has_matchsticks <- TRUE
	inverted$has_matchsticks <- TRUE
	pce$has_matchsticks <- TRUE

	base$has_pyramids <- TRUE
	dpe$has_pyramids <- TRUE
	inverted$has_pyramids <- TRUE
	pce$has_pyramids <- TRUE

	base$has_saucers <- TRUE
	dpe$has_saucers <- TRUE
	inverted$has_saucers <- TRUE
	pce$has_saucers <- TRUE

	list(
		base = base,
		dpe = dpe,
		hexpack = hexpack,
		inverted = inverted,
		pce = pce,
		subpack = subpack
	)
}

playing_cards <- function(font = "sans", rect_shape = "rect") {
	if (font == "sans") {
		face_labels <- c("", "\u050a", "\u046a", "\u0238")
		fool_text <- "*"
		pc_suit_text <- list(suit_text = "\u2665,\u2660,\u2663,\u2666,*", suit_cex.s5 = 1.3)
	} else if (font == "dejavu") {
		face_labels <- c("", "\u265e", "\u265b", "\u265a")
		fool_text <- "\u2605"
		pc_suit_text <- list(suit_text = "\u2665,\u2660,\u2663,\u2666,\u2605", suit_cex.s5 = 0.92)
	}

	playing_cards_list <- list(
		n_ranks = 14,
		rank_text = "A,2,3,4,5,6,7,8,9,10,J,Q,K,\n\n\n\nJ\nO\nK\nE\nR",
		grob_fn.card_face = cardGrobFn(),
		grob_fn.r11.card_face = faceCardGrobFn(face_labels[1]),
		grob_fn.r12.card_face = faceCardGrobFn(face_labels[3]),
		grob_fn.r13.card_face = faceCardGrobFn(face_labels[4]),
		grob_fn.r14.card_face = jokerCardGrobFn(TRUE),
		shape.card = rect_shape,
		border_color = "black",
		border_lex = 4
	)
	playing_cards_list$n_suits <- 4
	playing_cards_list$suit_color <- "#D55E00,#000000,#000000,#D55E00"

	playing_cards <- c(playing_cards_list, pc_suit_text)
	playing_cards$grob_fn.s3.r14.card_face <- jokerCardGrobFn(FALSE)
	playing_cards$grob_fn.s4.r14.card_face <- jokerCardGrobFn(FALSE)
	playing_cards$grob_fn.s5.card_face <- cardGrobFn(fill_stroke = TRUE)
	playing_cards$grob_fn.s5.r11.card_face <- faceCardGrobFn(face_labels[1], fill_stroke = TRUE)
	playing_cards$grob_fn.s5.r12.card_face <- faceCardGrobFn(face_labels[3], fill_stroke = TRUE)
	playing_cards$grob_fn.s5.r13.card_face <- faceCardGrobFn(face_labels[4], fill_stroke = TRUE)
	playing_cards$grob_fn.s5.r14.card_face <- jokerCardGrobFn(TRUE, fill_stroke = TRUE)

	playing_cards <- pp_cfg(playing_cards)
	playing_cards$has_piecepack <- FALSE
	playing_cards$has_cards <- TRUE

	playing_cards_colored <- c(playing_cards_list, pc_suit_text)
	playing_cards_colored$n_suits <- 5
	playing_cards_colored$suit_color <- cb_suit_colors_pure
	playing_cards_colored$grob_fn.s5.card_face <- cardGrobFn(fill_stroke = TRUE)
	playing_cards_colored$grob_fn.s5.r11.card_face <- faceCardGrobFn(
		face_labels[1],
		fill_stroke = TRUE
	)
	playing_cards_colored$grob_fn.s5.r12.card_face <- faceCardGrobFn(
		face_labels[3],
		fill_stroke = TRUE
	)
	playing_cards_colored$grob_fn.s5.r13.card_face <- faceCardGrobFn(
		face_labels[4],
		fill_stroke = TRUE
	)
	playing_cards_colored$grob_fn.s5.r14.card_face <- jokerCardGrobFn(TRUE, fill_stroke = TRUE)

	playing_cards_colored <- pp_cfg(playing_cards_colored)
	playing_cards_colored$has_piecepack <- FALSE
	playing_cards_colored$has_cards <- TRUE

	playing_cards_tarot <- playing_cards_list
	playing_cards_tarot$rank_text <- "A,2,3,4,5,6,7,8,9,10,J,C,Q,K,\n\n\n\nJ\nO\nK\nE\nR"
	playing_cards_tarot$grob_fn.r12.card_face <- faceCardGrobFn(face_labels[2], "low")
	playing_cards_tarot$grob_fn.r13.card_face <- faceCardGrobFn(face_labels[3])
	playing_cards_tarot$grob_fn.r14.card_face <- faceCardGrobFn(face_labels[4])
	playing_cards_tarot$grob_fn.r15.card_face <- jokerCardGrobFn(TRUE)
	playing_cards_tarot$grob_fn.s3.r15.card_face <- jokerCardGrobFn(FALSE)
	playing_cards_tarot$grob_fn.s4.r15.card_face <- jokerCardGrobFn(FALSE)
	playing_cards_tarot$n_suits <- 5
	playing_cards_tarot$n_ranks <- 22

	tarot_suit_text <- "\u2665,\u2660,\u2663,\u2666,"
	playing_cards_tarot$suit_text <- tarot_suit_text
	playing_cards_tarot$suit_text.r14 <- tarot_suit_text
	playing_cards_tarot$suit_color <- "#D55E00,#000000,#000000,#D55E00,#000000"
	playing_cards_tarot$rank_text.s5 <- c(1:21, fool_text)
	playing_cards_tarot$grob_fn.s5.card_face <- cardGrobFn(has_pips = FALSE, fill_stroke = FALSE)
	for (i in 11:15) {
		name <- str_glue("grob_fn.s5.r{i}.card_face")
		playing_cards_tarot[[name]] <- cardGrobFn(has_pips = FALSE, fill_stroke = FALSE)
	}

	playing_cards_tarot <- pp_cfg(playing_cards_tarot)
	playing_cards_tarot$has_piecepack <- FALSE
	playing_cards_tarot$has_cards <- TRUE

	list(base = playing_cards, color = playing_cards_colored, tarot = playing_cards_tarot)
}

reversi <- function(cell_width = 1, color_list = color_list_fn()) {
	reversi <- list(
		n_suits = 8L, # N_SUITS_COLOR?
		n_ranks = 12L,
		width.board = 8 * cell_width,
		height.board = 8 * cell_width,
		grob_fn.r1.board_face = linedBoardGrobFn(8, 8),
		grob_fn.r1.board_back = checkeredBoardGrobFn(8, 8),
		background_color.board_face = cb_suit_colors_impure,
		gridline_color.board_face = "black",
		gridline_lex.board = 4,
		suit_color = cb_suit_colors_impure,
		background_color = "white",
		gridline_color.s6.board_back = "grey80"
	)
	for (i in seq.int(2L, 12L)) {
		reversi[[str_glue("width.r{i}.board")]] <- i * cell_width
		reversi[[str_glue("height.r{i}.board")]] <- i * cell_width
		reversi[[str_glue("grob_fn.r{i}.board_face")]] <- linedBoardGrobFn(i, i)
		reversi[[str_glue("grob_fn.r{i}.board_back")]] <- checkeredBoardGrobFn(i, i)
	}
	reversi <- pp_cfg(c(reversi, reversi_piece(cell_width, color_list), color_list))
	reversi$has_piecepack <- FALSE
	reversi$has_boards <- TRUE
	reversi$has_bits <- TRUE
	reversi
}

shapes_cfg <- function(color_list = color_list_fn()) {
	shapes <- list(
		n_suits = N_SUITS_COLOR,
		n_ranks = 4,
		invert_colors = TRUE,
		ps_text = "",
		dm_text = "",
		background_color = "white",
		width = 1,
		height = 1,
		depth = 1,
		shape.r1.bit_face = "circle",
		shape.r1.bit_back = "circle",
		shape.r2.bit = "circle",
		# don't shade joystick sphere at top if rest of joystick isn't shaded?
		grob_fn.r2.bit = ellipsoidGrobFn(FALSE),
		op_grob_fn.r2.bit = basicEllipsoidFn(FALSE),
		obj_fn.r2.bit = function(...) save_ellipsoid_obj(..., subdivide = 4)
	)
	pp_cfg(c(shapes, color_list))
}

reversi_piece <- function(cell_width = 1, color_list = color_list_fn()) {
	shapes_top <- shapes_cfg(color_list)
	color_list$suit_color <- color_list$suit_color[c(7L, 6L, 8L, 5L, 4L, 2L, 1L, 3L)]
	shapes_bot <- shapes_cfg(color_list)
	envir <- list(shapes_top = shapes_top, shapes_bot = shapes_bot)

	df_reversi <- tibble(
		piece_side = "bit_back",
		rank = 1,
		width = 1,
		height = 1,
		depth = c(0.5, 0.5),
		x = 0.5,
		y = 0.5,
		z = c(0.25, 0.75),
		cfg = c("shapes_bot", "shapes_top")
	)

	reversi_piece <- CompositePiece$new(df_reversi, envir = envir, ref_side = "face")

	list(
		width.bit = 0.75 * cell_width,
		height.bit = 0.70 * cell_width,
		depth.bit = 0.25 * cell_width,
		grob_fn.bit = reversi_piece$grob_fn,
		obj_fn.bit = reversi_piece$obj_fn,
		op_grob_fn.bit = reversi_piece$op_grob_fn
	)
}

peg_doll_pawn <- function(shapes) {
	pegdoll_depth <- c(0.55, 1.0 * 0.75 / 1.5)
	df_pegdoll <- tibble(
		piece_side = "bit_back",
		rank = c(1, 2),
		width = 1,
		height = 1,
		depth = pegdoll_depth,
		x = 0.5,
		y = 0.5,
		z = c(0.5 * pegdoll_depth[1], 1 - 0.5 * pegdoll_depth[2]),
		cfg = "shapes"
	)

	pegdoll <- CompositePiece$new(df_pegdoll, envir = list(shapes = shapes))

	list(
		width.pawn = 0.75,
		depth.pawn = 0.75,
		height.pawn = 1.5,
		edge_color.pawn = cb_suit_colors_pure,
		background_color.belt_face = "white",
		mat_color.belt_face = "transparent",
		suit_cex.belt_face = 1.5,
		obj_fn.pawn = save_peg_doll_obj,
		grob_fn.pawn = pegdoll$grob_fn,
		op_grob_fn.pawn = pegdoll$op_grob_fn
	)
}

joystick_pawn <- function(shapes) {
	jd <- c(0.15, 0.6, 0.5 * 5 / 8)
	df_joystick <- tibble(
		piece_side = "bit_back",
		rank = c(1, 1, 2),
		width = c(1, 0.3, 0.5),
		height = c(1, 0.3, 0.5),
		depth = jd,
		x = rep(0.5, 3),
		y = rep(0.5, 3),
		z = c(0.5 * jd[1], jd[1] + 0.5 * jd[2], 1 - 0.5 * jd[3]),
		cfg = "shapes"
	)

	joystick <- CompositePiece$new(df_joystick, envir = list(shapes = shapes))

	list(
		grob_fn.pawn = joystick$grob_fn,
		obj_fn.pawn = joystick$obj_fn,
		op_grob_fn.pawn = joystick$op_grob_fn,
		width.pawn = 5 / 8,
		height.pawn = 1.0,
		depth.pawn = 5 / 8
	)
}

#' @rdname game_systems
#' @inheritParams pp_cfg
#' @export
to_hexpack <- function(cfg = getOption("piecepackr.cfg", pp_cfg())) {
	cfg <- as_pp_cfg(cfg)
	hexpack <- as.list(cfg)
	hexpack$shape.tile_face <- "convex6"
	hexpack$shape.tile_back <- "convex6"
	hexpack$border_lex <- 3
	hexpack$shape_t.tile <- 60
	hexpack$dm_t.tile_face <- -90
	hexpack$width.tile <- 4 / sqrt(3)
	hexpack$height.tile <- 4 / sqrt(3)
	hexpack$shape.coin_face <- "convex3"
	hexpack$shape.coin_back <- "convex3"
	pp_cfg(hexpack)
}

#' @rdname game_systems
#' @inheritParams pp_cfg
#' @export
to_subpack <- function(cfg = getOption("piecepackr.cfg", pp_cfg())) {
	cfg <- as_pp_cfg(cfg)
	subpack <- as.list(cfg)
	subpack$width.tile <- (3 / 8) * cfg$get_width("tile")
	subpack$width.coin <- 0.4 * cfg$get_width("coin")
	subpack$width.die <- 0.6 * cfg$get_width("die")
	subpack$width.pawn <- 0.4 * cfg$get_width("pawn")
	subpack$height.pawn <- 0.4 * cfg$get_height("pawn")
	subpack$width.saucer <- 0.4 * cfg$get_width("saucer")
	subpack$cex <- 0.4
	pp_cfg(subpack)
}

Try the piecepackr package in your browser

Any scripts or data that you put into this service are public.

piecepackr documentation built on May 12, 2026, 9:07 a.m.