Skip to content

Commit

Permalink
feat: 'df_speedy_towers()' and ruleset
Browse files Browse the repository at this point in the history
* New starting board generators for specific games:

  - ``df_speedy_towers()`` (currently supports two players only)

* ``save_ruleset()`` / ``save_pamphlet()`` / ``save_pocketmod()`` now supports ruleset generation for:

  - "speedy towers"
  • Loading branch information
trevorld committed Jul 11, 2023
1 parent 10bf78a commit 346a426
Show file tree
Hide file tree
Showing 17 changed files with 571 additions and 920 deletions.
4 changes: 2 additions & 2 deletions DESCRIPTION
Expand Up @@ -2,7 +2,7 @@ Encoding: UTF-8
Package: ppgames
Type: Package
Title: Piecepack Games
Version: 0.8.0-18
Version: 0.8.0-19
Authors@R: c(person("Trevor L", "Davis", role=c("aut", "cre"),
email="trevor.l.davis@gmail.com",
comment = c(ORCID = "0000-0001-6341-4639")))
Expand Down Expand Up @@ -45,5 +45,5 @@ Suggests:
Remotes: xmpdf=trevorld/r-xmpdf
SystemRequirements: exiftool, pandoc, xelatex
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.1
RoxygenNote: 7.2.3
Config/testthat/edition: 3
1 change: 1 addition & 0 deletions NAMESPACE
Expand Up @@ -59,6 +59,7 @@ export(df_san_andreas)
export(df_shogi)
export(df_skyscrapers)
export(df_slides_of_action)
export(df_speedy_towers)
export(df_tablut)
export(df_the_in_crowd)
export(df_the_magic_bag)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Expand Up @@ -32,10 +32,12 @@ New features

- ``df_cribbage()`` is a new alias for ``df_cribbage_board()``
- ``df_ludo()`` (#119)
- ``df_speedy_towers()`` (currently supports two players only)

* ``save_ruleset()`` / ``save_pamphlet()`` / ``save_pocketmod()`` now supports ruleset generation for:

- "ludo" (#119)
- "speedy towers"

* Improved rulesets for:

Expand Down
64 changes: 53 additions & 11 deletions R/game_starts.R
Expand Up @@ -104,6 +104,8 @@
#' See \url{https://www.ludism.org/ppwiki/Skyscrapers}.}
#' \item{Slides of Action}{An abstract connection game by Clark Rodeffer.
#' See \url{https://www.ludism.org/ppwiki/SlidesOfAction}.}
#' \item{Speedy Towers}{A real-time tower building game by Jessica Eccles.
#' See \url{https://ludism.org/ppwiki/SpeedyTowers}.}
#' \item{Tablut}{Traditional two-player abstract played by the Sámi people until at least the 1700s.
#' See \url{https://www.ludism.org/ppwiki/Tablut}.}
#' \item{The \dQuote{In} Crowd}{Piecepack game by Jeb Havens and Ian Schreiber.
Expand Down Expand Up @@ -138,8 +140,9 @@
#' @param pawns String of pawns layout
#' @param die_width Width of dice
#' @param max_tiles Maximum number of (piecepack) tiles available to build boards
#' @param n_players Number of players
#' @param suit_colors Character vector of the suit colors
#' @param variant Number of variant.
#' @param variant Number of variant
#' @rdname df_game
#' @name df_game
NULL
Expand Down Expand Up @@ -666,6 +669,16 @@ should_resample_relativity <- function(coins) {
all(diff(c(coins[6], coins[4], coins[3], coins[1])) == 0)
}

#' @rdname df_game
#' @export
df_san_andreas <- function() {
x <- 0.5+c(rep(c(1,3,5), 3), 2,4,6, 3,5,7, 4,6,8, 5,7,9, 7,9)
y <- 0.5+c(rep(c(15,13,11,9,7,5,3), each=3), 1, 1)
tibble(piece_side="tile_back", x=x, y=y,
suit = rep(1:4, each=6, length.out=23),
rank = rep(1:6, 4, length.out=23))
}

#' @rdname df_game
#' @export
df_skyscrapers <- function(seed = NULL, tiles = NULL) {
Expand All @@ -687,6 +700,45 @@ df_slides_of_action <- function() {
bind_rows(df_tiles, df_coins)
}

# 2 players = 24 tiles, 24 coins => 2 towers, 11 tiles each, 12 coins each
# 3 players = 48 tiles, 48 coins => 2 towers, 15 tiles each + 1 extra tile, 16 coins each
# 4 players = 48 tiles, 48 coins => 3 towers, 11 tiles each + 1 extra tile, 12 coins each
# 5 players = 72 tiles, 72 coins => 4 towers, 13 tiles each + 3 extra tiles, 16 coins each + 2 coins
# 6 players = 72 tiles, 72 coins => 5 towers, 11 tiles each + 1 extra tile, 12 coins each

#' @rdname df_game
#' @export
df_speedy_towers <- function(n_players = 2, seed = NULL) {
if (!is.null(seed)) withr::local_seed(seed)
stopifnot(n_players >= 2, n_players <= 6)
if (n_players == 2) {
df_tiles <- tibble(piece_side = "tile_back",
suit = rep(1:4, 6),
rank = rep(1:6, each = 4))[sample.int(24L), ]
df_tiles$x <- c(seq.int(2, by = 2, length.out = 11),
16, 10,
seq.int(24, by = -2, length.out = 11))
df_tiles$y <- c(rep_len(4, 11), 8, 8, rep_len(12, 11))
df_tiles$angle <- c(rep_len(0, 12), rep_len(180, 12))
df_coins <- tibble(piece_side = "coin_back",
suit = rep(1:4, 6),
rank = rep(1:6, each = 4))[sample.int(24L), ]
df_coins <- df_coins[c(order(df_coins$suit[1:12]),
12 + order(df_coins$suit[13:24])), ]
df_coins$x <- c(seq(2, by = 2, length.out = 12),
seq(24, by = -2, length.out = 12))
df_coins$y <- c(rep(2, 12), rep(14, 12))
df_coins$angle <- c(rep_len(0, 12), rep_len(180, 12))
df_pawns <- tibble(piece_side = "pawn_face",
suit = c(3, 1), rank = NA_integer_,
x = c(24, 2), y = c(4, 12),
angle = c(0, 180))
bind_rows(df_tiles, df_coins, df_pawns)
} else {
stop("We haven't been programmed for this case yet")
}
}

#' @rdname df_game
#' @export
df_the_in_crowd <- function() {
Expand All @@ -697,16 +749,6 @@ df_the_in_crowd <- function() {
bind_rows(df_t1, df_t2, df_t3)
}

#' @rdname df_game
#' @export
df_san_andreas <- function() {
x <- 0.5+c(rep(c(1,3,5), 3), 2,4,6, 3,5,7, 4,6,8, 5,7,9, 7,9)
y <- 0.5+c(rep(c(15,13,11,9,7,5,3), each=3), 1, 1)
tibble(piece_side="tile_back", x=x, y=y,
suit = rep(1:4, each=6, length.out=23),
rank = rep(1:6, 4, length.out=23))
}

#' @rdname df_game
#' @export
df_the_magic_bag <- function(seed = NULL) {
Expand Down
2 changes: 1 addition & 1 deletion R/rules.R
Expand Up @@ -386,7 +386,7 @@ game_credits <- function(game, game_info = NULL) {
items <- list()
if ("author" %in% names(info)) items$`Written by:` <- info$author
items$`Game design:` <- info$designer
license <- info$license %||% "CC-BY-SA-4.0"
license <- info$license %||% info$licence %||% "CC-BY-SA-4.0"
stopifnot(license %in% piecepackr::spdx_license_list$id)
license_name <- piecepackr::spdx_license_list[license, "name"]
license_url <- piecepackr::spdx_license_list[license, "url_alt"]
Expand Down
73 changes: 53 additions & 20 deletions R/save_promo_image.R
Expand Up @@ -30,22 +30,19 @@ save_promo_image <- function(game, gk = game_kit(), file = NULL) {
envir <- list(piecepack = cfg)
switch(game_name,
nine_mens_morris = {
df <- promo_morris_df(game_name)
df <- promo_morris_df()
wh <- render_piece(df, file = file, annotate = FALSE,
envir = envir,
op_scale = 0.5, trans = op_transform)
},
pass_the_food = {
df_tiles <- get_starting_df_from_name(game_name)
df_tiles$id <- NULL
df_tiles$cfg <- NULL
withr::local_seed(36)
df_coins <- tibble(piece_side = "coin_back",
rank = rep(1:6, 4), suit = rep(1:4, each=6),
x = stats::runif(24, -1.10, 8.70),
y = stats::runif(24, -1.20, 13.50),
angle = stats::runif(24, 0, 360))
df <- bind_rows(df_tiles, df_coins)
df <- promo_pass_the_food_df()
wh <- render_piece(df, file = file, annotate = FALSE,
envir = envir,
op_scale = 0.5, trans = op_transform)
},
speedy_towers = {
df <- promo_speedy_towers_df()
wh <- render_piece(df, file = file, annotate = FALSE,
envir = envir,
op_scale = 0.5, trans = op_transform)
Expand All @@ -58,7 +55,7 @@ save_promo_image <- function(game, gk = game_kit(), file = NULL) {

},
twelve_mens_morris = {
df <- promo_morris_df(game_name)
df <- promo_morris_df()
wh <- render_piece(df, file = file, annotate = FALSE,
envir = envir,
op_scale = 0.5, trans = op_transform)
Expand All @@ -72,12 +69,48 @@ save_promo_image <- function(game, gk = game_kit(), file = NULL) {
invisible(c(wh, list(file = file)))
}

promo_morris_df <- function(game_name) {
df_tiles <- df_nine_mens_morris()
df_coins <- tibble(piece_side = "coin_back",
x = c(3, 3, 5, 7, 7, 9, 9, 9, 11, 11, 11),
y = c(3, 7, 7, 1, 3, 5, 7, 9, 3, 7, 11),
suit = c(3, 4, 4, 1, 2, 2, 1, 1, 4, 3, 3),
rank = c(1, 1, 2, 1, 1, 2, 2, 3, 3, 2, 3))
bind_rows(df_tiles, df_coins)
promo_morris_df <- function() {
df_tiles <- df_nine_mens_morris()
df_coins <- tibble(piece_side = "coin_back",
x = c(3, 3, 5, 7, 7, 9, 9, 9, 11, 11, 11),
y = c(3, 7, 7, 1, 3, 5, 7, 9, 3, 7, 11),
suit = c(3, 4, 4, 1, 2, 2, 1, 1, 4, 3, 3),
rank = c(1, 1, 2, 1, 1, 2, 2, 3, 3, 2, 3))
bind_rows(df_tiles, df_coins)
}

promo_pass_the_food_df <- function() {
df_tiles <- df_pass_the_food()
withr::local_seed(36)
df_coins <- tibble(piece_side = "coin_back",
rank = rep(1:6, 4), suit = rep(1:4, each=6),
x = stats::runif(24, -1.10, 8.70),
y = stats::runif(24, -1.20, 13.50),
angle = stats::runif(24, 0, 360))
bind_rows(df_tiles, df_coins)
}

promo_speedy_towers_df <- function() {
withr::local_seed(72)
df_tiles <- tibble(piece_side = "tile_face",
suit = rep(1:4, 6),
rank = rep(1:6, each = 4))[sample.int(24L), ]
df_tiles$x <- c(5, rep(1, 10), rep(5, 12), 3)
df_tiles$y <- c(rep(1, 23), 7)
df_tiles$angle <- c(c(rep_len(0, 12), rep_len(180, 11))[sample.int(23L)], 180)

df_coins <- tibble(piece_side = "coin_back",
suit = rep(1:4, 6),
rank = rep(1:6, each = 4))[sample.int(24L), ]
df_coins$x <- c(rep(1, 10), rep(5, 12), 5, 6)
df_coins$y <- c(rep(1, 22), 7, 7)
df_coins$angle <- c(c(rep_len(0, 12), rep_len(180, 10))[sample.int(22L)], 180, 180)
# interleave coins
df_inter <- bind_rows(df_tiles[3:23, ], df_coins[1:22, ])[sample.int(43L), ]
# add pawns
df_pawns <- tibble(piece_side = "pawn_face",
suit = c(1, 3), rank = NA_integer_,
x = c(1, 5), y = c(7, 1), angle = c(180, 0))
df <- bind_rows(df_tiles[1:2, ], df_inter, df_pawns, df_tiles[24,, ], df_coins[23:24,, ])
df
}
4 changes: 2 additions & 2 deletions README.rst
Expand Up @@ -5,8 +5,8 @@ ppgames: Piecepack game diagrams and rules
:alt: R-CMD-check
:target: https://github.com/piecepackr/ppgames/actions

.. image:: https://img.shields.io/codecov/c/github/piecepackr/ppgames/master.svg
:target: https://codecov.io/github/piecepackr/ppgames?branch=master
.. image:: https://codecov.io/github/piecepackr/ppgames/branch/master/graph/badge.svg
:target: https://app.codecov.io/github/piecepackr/ppgames?branch=master
:alt: Coverage Status

.. image:: http://www.repostatus.org/badges/latest/wip.svg
Expand Down
33 changes: 21 additions & 12 deletions inst/extdata/game_info.yaml
Expand Up @@ -2,18 +2,6 @@
# license "CC-BY-SA-4.0"
# equipment "one standard piecepack"
# title inferred from list key
# original board games
pass_the_food:
title: "Pass the Food"
players: [1, 2, 3, 4]
length: 20
equipment: "one standard piecepack"
designer: "Trevor L. Davis"
version: "0.3"
version_date: "2021-01-15"
copyright: "© 2021 Trevor L. Davis. Some Rights Reserved."
ppwiki: "PassTheFood"
# historical board game adaptions
alice_chess:
players: 2
length: [30, 180]
Expand Down Expand Up @@ -177,6 +165,16 @@ nine_mens_morris:
- "Damian Gareth Walker, \\emph{A Book of Historic Board Games} (2014), 95--110"
- "David Pritchard, \\emph{Brain Games} (1982), 128--131"
copyright: "© 2022 Trevor L. Davis. Some Rights Reserved."
pass_the_food:
title: "Pass the Food"
players: [1, 2, 3, 4]
length: 20
equipment: "one standard piecepack"
designer: "Trevor L. Davis"
version: "0.3"
version_date: "2021-01-15"
copyright: "© 2021 Trevor L. Davis. Some Rights Reserved."
ppwiki: "PassTheFood"
shogi:
title: "Shogi (Japanese Chess)"
players: 2
Expand All @@ -191,6 +189,17 @@ shogi:
cyningstan: "155"
wikipedia: "Shogi"
copyright: "© 2022 Trevor L. Davis. Some Rights Reserved."
speedy_towers:
title: "Speedy Towers"
players: [2, 3, 4, 5, 6]
length: 5
equipment: "one piecepack per two players"
designer: "Jessica Eccles"
version: "0.7-tld"
version_date: "2023-07-11"
license: "CC0-1.0"
copyright: "Created in 2018 by Jessica Eccles and lightly edited and illustrated in 2023 by Trevor L. Davis. No Rights Reserved."
ppwiki: "SpeedyTowers"
tablut:
players: 2
length: 45
Expand Down
47 changes: 47 additions & 0 deletions inst/rules/speedy-towers.Rtex
@@ -0,0 +1,47 @@
%% begin.rcode opts, echo=FALSE
set.seed(12)
set_knitr_opts(game, output_ext, wd)
%% end.rcode

\subsection{Description}

\emph{Speedy Towers} is a real-time tower building game for the piecepack.

\subsection{Objective}

\begin{itemize}
\item The person who empties their pile first and sets their pawn on top of the highest tower without knocking it over wins.
\item In the case of a tie the speediest players share the win or play again.
\item If nobody can place any more pieces the person with the fewest pieces wins.
\end{itemize}

\subsection{Setup}

You need the piecepack tiles, coins and pawns. Shuffle the tiles face down and place 1 fewer than the number of players (but at least 2) in the middle of the table. These will form the foundations of the towers. Divide the rest evenly among the players.\footnote{Note unless there are exactly two players it is not possible to divide the tiles exactly evenly. One can push the extra tile(s) (and coins if there are five players) aside or give them to the winner of the last game.} Shuffle the coins and divide them among the players likewise. Give each player one pawn.

%% begin.rcode starting-diagram, fig.width=26, fig.height=15, out.width="1\\linewidth", out.height="0.5769231\\linewidth", fig.cap="Possible Speedy Towers setup for a game with two players"
cfg <- gk$get_piecepacks(1)[[1]]
pmap_piece(df_speedy_towers(), cfg=cfg, default.units="in")
%% end.rcode

\subsection{Play}

The fastest player (or whoever won the last game) turns over the foundation tiles in the middle of the table. As soon as the last foundation tile has been turned over players may turn over one of their tiles and start placing their pieces one at a time onto one of the foundations in the middle under the following rules:

\begin{itemize}
\item Players may only have one face up tile at a time. If the tile they have cannot be placed they may turn it face down and turn over a different tile.
\item Coins may be flipped to either side at any time.\footnote{If you sort your coins by suit you can tell what suit your coin is by the location you placed them (and/or alternatively directional mark orientation) without needing to flip them over heads up. It may be helpful to recall that the ``official'' ordering of piecpack suits is Suns, Moons, Crowns, and Arms.}
\item Players race to empty their piles following the following placement rules:
\begin{itemize}
\item A piece may be placed onto any piece of a lower value (Ace is worth 6) or of the same suit.
\item Nulls are both higher and lower than all other pieces. A Null can be placed on anything, and anything can be placed on a Null.
\item Coins only show a suit or a value therefore the hidden property is considered to be the same as the tile underneath. They may be stacked on tiles or other coins.
\item The pawn must be played as the last piece and must be placed on the highest tower.
\item Players may not touch the towers except with the new piece they are placing.
\end{itemize}
\item If a player(s) knocks over a tower while placing a piece they take a fallen piece and push the rest aside out of the game. Anything touching the playing surface except for the foundation is considered to be fallen.
\end{itemize}

\subsection{Variant}

Divide the pieces unevenly so that speedier players get more and everyone has a chance to win.

0 comments on commit 346a426

Please sign in to comment.