From 7d964f0975fc493090f6e0f11ef7f128b6e43a8b Mon Sep 17 00:00:00 2001 From: Matt Dray Date: Tue, 3 Jan 2023 22:08:30 +0000 Subject: [PATCH] Add sfx, close #9, bump v0.1.0 (#34) --- DESCRIPTION | 13 +++++--- NEWS.md | 6 ++++ R/play.R | 26 +++++++++++---- R/utils-movement.R | 8 ++++- R/utils-print.R | 6 ++-- R/utils-sfx.R | 66 +++++++++++++++++++++++++++++++++++++++ README.md | 48 ++++++++++++++-------------- man/r.oguelike-package.Rd | 3 +- man/start_game.Rd | 8 +++-- 9 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 R/utils-sfx.R diff --git a/DESCRIPTION b/DESCRIPTION index e8a98ba..98bc416 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,12 +1,14 @@ Package: r.oguelike Title: Play a Roguelike 'Game' in the Console -Version: 0.0.0.9009 +Version: 0.1.0 Authors@R: person("Matt", "Dray", , "mwdray@gmail.com", role = c("aut", "cre")) -Description: Play a simple ASCII-only tile-based toy in the console, inspired - heavily by roguelike games like Rogue (1980). Proof of concept. +Description: Play with a simple ASCII-only tile-based toy in the console, + inspired heavily by roguelike games like Rogue (1980). Proof of concept. License: MIT + file LICENSE -URL: https://github.com/matt-dray/r.oguelike, https://matt-dray.github.io/r.oguelike/ +URL: https://github.com/matt-dray/r.oguelike, + https://matt-dray.github.io/r.oguelike/, + https://www.rostrum.blog/tags/r.oguelike/ BugReports: https://github.com/matt-dray/r.oguelike/issues Encoding: UTF-8 LazyData: true @@ -14,7 +16,8 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.1.2 Imports: crayon, - keypress + keypress, + sonify Suggests: covr, testthat (>= 3.0.0) diff --git a/NEWS.md b/NEWS.md index b6a37f0..6ca3151 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# r.oguelike 0.1.0 + +* Breaking: the `is_colour` argument to `start_game()` has been renamed with `has_colour`, which is a better verb for this purpose and matches the new `has_sfx` argument. +* Added sound effects (#9), including `has_sfx` argument to `start_game()`. +* Tweaked README given recent changes. + # r.oguelike 0.0.0.9009 * Fixed bug: ensured collecting one apple adds only one apple to the inventory (#31). diff --git a/R/play.R b/R/play.R index 576e8e5..8299237 100644 --- a/R/play.R +++ b/R/play.R @@ -20,8 +20,10 @@ #' @param is_organic Logical. Join the room start points with corridors before #' iterative growth steps (\code{TRUE}, the default), or after #' (\code{FALSE})? See details. -#' @param is_colour Logical. Should the characters in the output be coloured +#' @param has_colour Logical. Should the characters in the output be coloured #' using \code{\link[crayon]{crayon}} (\code{TRUE}, the default)? +#' @param has_sfx Logical. Should sound effects be used? Defaults to +#' \code{TRUE}. #' #' @details #' Use the WASD keys to move up, left, down and right. Use the '1' key to eat @@ -62,7 +64,8 @@ start_game <- function( iterations = 4L, is_snake = FALSE, is_organic = TRUE, - is_colour = TRUE + has_colour = TRUE, + has_sfx = TRUE ) { # Checks ---- @@ -84,9 +87,14 @@ start_game <- function( stop("Arguments 'n_row' and 'n_col' must be 10 or greater.", call. = FALSE) } - if (!is.logical(is_snake) | !is.logical(is_organic) | !is.logical(is_colour)) { + if ( + !is.logical(is_snake) | + !is.logical(is_organic) | + !is.logical(has_colour) | + !is.logical(has_sfx) + ) { stop( - "Arguments 'is_snake', 'is_organic' and 'is_colour' must be logical.", + "Arguments 'is_snake', 'is_organic', 'has_colour' and 'has_sfx' must be logical.", call. = FALSE ) } @@ -137,7 +145,7 @@ start_game <- function( if (supports_keypress) system2("clear") if (is_rstudio) cat("\014") - .cat_map(game_map, is_colour) + .cat_map(game_map, has_colour) .cat_stats(turns, hp, gold, food) message(status_msg) @@ -182,6 +190,7 @@ start_game <- function( food <- food - 1 hp <- hp + 1 status_msg <- "Ate apple (+1 HP)" + .sfx_apple_eat(has_sfx) } if (hp == max_hp) { @@ -194,7 +203,7 @@ start_game <- function( # Player actions ---- - game_map <- .move_player(game_map, kp, player_loc) + game_map <- .move_player(game_map, kp, player_loc, has_sfx) player_loc <- which(game_map == "@") if (kp %in% c("up", "down", "left", "right")) { @@ -204,11 +213,13 @@ start_game <- function( if (length(gold_loc) > 0 && player_loc == gold_loc) { gold <- gold + gold_rand status_msg <- paste0("Found gold (+", gold_rand, " $)") + .sfx_gold(has_sfx) } if (length(food_loc) > 0 && player_loc == food_loc) { food <- food + 1 status_msg <- "Collected apple (+1 a)" + .sfx_apple_collect(has_sfx) } if (length(enemy_loc) != 0 && player_loc == enemy_loc) { @@ -222,6 +233,7 @@ start_game <- function( if (enemy_hp == 0) { status_msg <- paste0("Enemy defeated! (-", start_hp - hp," HP)") + .sfx_enemy_defeat(has_sfx) enemy_loc <- NULL is_battle <- FALSE } @@ -262,6 +274,7 @@ start_game <- function( if (enemy_hp == 0) { status_msg <- paste0("Enemy defeated! (-", start_hp - hp," HP)") game_map[enemy_loc] <- "@" # overwrite position with player symbol + .sfx_enemy_defeat(has_sfx) is_battle <- FALSE } @@ -288,6 +301,7 @@ start_game <- function( if (turns == 0) { message("You died (max turns)! Try again.") + .sfx_end(has_sfx) is_alive <- FALSE } diff --git a/R/utils-movement.R b/R/utils-movement.R index 7c91087..97bb20e 100644 --- a/R/utils-movement.R +++ b/R/utils-movement.R @@ -4,11 +4,14 @@ #' 'right' to move, '1' to eat an apple, '0' to exit). #' @param player_loc Numeric. Matrix index of the tile occupied by the player #' character. +#' @param has_sfx Logical. Should sound effects be used? Defaults to +#' \code{TRUE}. #' @noRd .move_player <- function( room, kp = c("up", "down", "left", "right", "1", "0"), - player_loc + player_loc, + has_sfx ) { if (!inherits(room, "matrix")) { @@ -41,6 +44,9 @@ if (room[move_to] != "#") { player_loc <- move_to + .sfx_move(has_sfx) + } else { + .sfx_edge(has_sfx) } } diff --git a/R/utils-print.R b/R/utils-print.R index 6d9af24..d8bbb43 100644 --- a/R/utils-print.R +++ b/R/utils-print.R @@ -1,15 +1,15 @@ #' Concatenate And Print A Matrix Of The Game Map #' @param room Matrix. 2D map layout. -#' @param is_colour Logical. Should the characters in the output be coloured +#' @param has_colour Logical. Should the characters in the output be coloured #' using \code{\link[crayon]{crayon}} (\code{TRUE}, the default)? #' @noRd -.cat_map <- function(game_map, is_colour = TRUE) { +.cat_map <- function(game_map, has_colour) { if (!inherits(game_map, "matrix")) { stop("Argument 'game_map' must be a matrix.") } - if (is_colour) { + if (has_colour) { game_map[which(game_map == ".")] <- crayon::black(".") game_map[which(game_map == "#")] <- crayon::red("#") diff --git a/R/utils-sfx.R b/R/utils-sfx.R new file mode 100644 index 0000000..54684d8 --- /dev/null +++ b/R/utils-sfx.R @@ -0,0 +1,66 @@ +#' Sound Effect: Move Player +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_move <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 1, y = 1, duration = 0.001) + } +} + +#' Sound Effect: Bump Edge +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_edge <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 1, y = 1, duration = 0.01, flim = c(100, 110)) + } +} + +#' Sound Effect: Collect Apple +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_apple_collect <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 0:1, y = c(0, 1), duration = 0.05) + } +} + +#' Sound Effect: Eat Apple +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_apple_eat <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 0:1, y = c(1, 0), duration = 0.05) + } +} + +#' Sound Effect: Collect Gold +#' @noRd +.sfx_gold <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 1, y = 1, duration = 0.05, flim = c(800, 800)) + sonify::sonify(x = 1, y = 1, duration = 0.05, flim = c(1000, 1000)) + } +} + +#' Sound Effect: Defeat Enemy +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_enemy_defeat <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(600, 600)) + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(600, 600)) + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(800, 800)) + } +} + +#' Sound Effect: End Game +#' @param has_sfx Logical. Should sound effects be used? Defaults to \code{TRUE}. +#' @noRd +.sfx_end <- function(has_sfx) { + if (has_sfx) { + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(600, 600)) + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(600, 600)) + sonify::sonify(x = 0:1, y = rep(1, 2), duration = 0.1, flim = c(400, 400)) + } +} diff --git a/README.md b/README.md index df4ce52..0bf0abf 100644 --- a/README.md +++ b/README.md @@ -12,46 +12,46 @@ Binder](http://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/matt-dra -A toy demo of a tile-based [roguelike game](https://en.wikipedia.org/wiki/Roguelike) for R. +A tile-based [roguelike toy](https://en.wikipedia.org/wiki/Roguelike) for R's console. -Visit [the documentation website](https://matt-dray.github.io/r.oguelike/reference/start_game.html) or read more [in the inaugural blogpost](https://www.rostrum.blog/2022/04/25/r.oguelike-dev/). See [the issues](https://github.com/matt-dray/r.oguelike/issues) for future plans or to suggest improvements. +This is a proof-of-concept. Read more [in the blogposts](https://www.rostrum.blog/tags/r.oguelike/). Suggest improvements or fixes in [the issues](https://github.com/matt-dray/r.oguelike/issues). -You can install {r.oguelike} from GitHub via {remotes} (packages [{crayon}](https://github.com/r-lib/crayon) and [{keypress}](https://github.com/gaborcsardi/keypress) are also installed): +You can install {r.oguelike} from GitHub via [{remotes}](https://CRAN.R-project.org/package=remotes) (CRAN packages [{crayon}](https://CRAN.R-project.org/package=crayon), [{keypress}](https://CRAN.R-project.org/package=keypress) and [{sonify}](https://CRAN.R-project.org/package=sonify) are also installed): ``` r if (!require(remotes)) install.packages("remotes") -install_github("matt-dray/r.oguelike") +remotes::install_github("matt-dray/r.oguelike") ``` -You could also [launch an instance of RStudio in the browser](https://mybinder.org/v2/gh/matt-dray/play-r.oguelike/main?urlpath=rstudio), thanks to [Binder](https://mybinder.org/), with {r.oguelike} preinstalled. +You could also [launch an instance of RStudio in the browser](https://mybinder.org/v2/gh/matt-dray/play-r.oguelike/main?urlpath=rstudio) with {r.oguelike} preinstalled, thanks to [Binder](https://mybinder.org/). -Use `start_game()` to begin. You can adjust the default parameters; see `?start_game` or [visit the documentation website](https://matt-dray.github.io/r.oguelike/reference/start_game.html) for details. +Use `start_game()` to begin. See `?start_game` for details on how to adjust the starting parameters. ``` r r.oguelike::start_game( + max_turns = 25, iterations = 3, - n_row = 15, - n_col = 20, - n_rooms = 4, - max_turns = 25 + n_row = 15, + n_col = 20, + n_rooms = 4 ) ``` -The console will clear and you’ll see a map, with an inventory bar, status message and prompt for input. Output will appear in colour with the argument `colour = TRUE` (the default). +The console will clear and then you’ll see a dungeon map, an inventory bar, a status message and a prompt for player input. Output will appear in colour if your console supports it. ``` # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . # # # # # -# # # # # # # # . . . . $ . . # # # # # -# # # # # # # # . # # . . . . # # # # # -# . # # # # # # # # # . . . # # # # # # +# # # # # # . . . . . . $ . . # # # # # +# # # . . . . . . # # . . . . # # # # # +# . . . # # # # # # # . . . # # # # # # # . . # # # # # # # # . . . # # # # # # # . . . # # # # # # # . . . . . # . # # # . . # # # # # # # . . . . . . . . # # # . . # # # # # # # # . . . . . . # # # # . @ . . . # # . # # # # . . . . . # # # . . . . . . . . . . . . . . . . # # # -# . . . . . . . . . E . . . . . . # # # +# . . . . . . . . . E . # # . . . # # # # . . a . . . . . . . . . . . . # # # # # # . . . . # # . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # @@ -60,25 +60,25 @@ Press W, A, S or D then Enter to move, 1 to eat apple, 0 to exit Input: ``` -The dungeon map (`#` for walls and `.` for floor tiles) and placement of objects (`@` is the player, `E` is an enemy, `$` is gold and `a` is an apple) are randomised. [See the accompanying blogpost](https://www.rostrum.blog/2022/05/01/dungeon/) for more about how these dungeons are generated. +The dungeon map (`#` for walls and `.` for floor tiles) is [procedurally-generated](https://www.rostrum.blog/2022/05/01/dungeon/) and the placement of objects (`@` is the player, `E` is an enemy, `$` is gold and `a` is an apple) is randomised. -You can move the player character (`@`) with your arrow keys instead of the W, A, S or D keys if you’re using a terminal that supports [the {keypress} package](https://github.com/gaborcsardi/keypress) (RStudio doesn't). +To move the player character (`@`), type one of [WASD](https://en.wikipedia.org/wiki/Arrow_keys#WASD_keys) at the prompt and hit Enter. If your terminal supports [{keypress}](https://github.com/gaborcsardi/keypress) (RStudio doesn't) then you'll be able to type a single arrow key instead. -After pressing s then Enter (or the down arrow key, if supported), the player character moves one space down and the status message updates. Note the enemy (`E`) is also heading in your direction... +After instructing the player character to move down, for example, the screen refreshes to show `@` is in a new location and the status message has been updated. Simple sound effects will play depending on the outcome of the move. ``` # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . # # # # # -# # # # # # # # . . . . $ . . # # # # # -# # # # # # # # . # # . . . . # # # # # -# . # # # # # # # # # . . . # # # # # # +# # # # # # . . . . . . $ . . # # # # # +# # # . . . . . . # # . . . . # # # # # +# . . . # # # # # # # . . . # # # # # # # . . # # # # # # # # . . . # # # # # # # . . . # # # # # # # . . . . . # . # # # . . # # # # # # # . . . . . . . . # # # . . # # # # # # # # . . . . . . # # # # . . . . . # # . # # # # . . . . . # # # . @ . . . . . . . . . . . . . . # # # -# . . . . . . . . E . . . . . . . # # # +# . . . . . . . . E . . # # . . . # # # # . . a . . . . . . . . . . . . # # # # # # . . . . # # . # # # # . # # # # # # # # # # # # # # # # # # # # # # # # # # @@ -87,7 +87,9 @@ Moved down Input: ``` -Collect the gold (`$`). Auto-battle the randomly-moving enemy (`E`). Collect an apple (`a`) for your inventory, then eat it with a keypress input of `1`. You’ll die if you run out of `HP` or if you reach the maximum number of turns allowed (`T`). You can quit the game with `0`. +Also note that the enemy's (`E`) position has changed and it's [heading in your direction...](https://www.rostrum.blog/2022/06/10/basic-search/) + +What now? Collect the gold (`$`). Auto-battle the chasing enemy (`E`). Collect an apple (`a`) for your inventory, then eat it with a keypress input of `1` to replenish health. You’ll die if you run out of `HP` or if you reach the maximum number of turns allowed (`T`). You can quit the game with `0`. ## Code of Conduct diff --git a/man/r.oguelike-package.Rd b/man/r.oguelike-package.Rd index 4e65811..f9d96b9 100644 --- a/man/r.oguelike-package.Rd +++ b/man/r.oguelike-package.Rd @@ -8,13 +8,14 @@ \description{ \if{html}{\figure{logo.png}{options: align='right' alt='logo' width='120'}} -Play a simple ASCII-only tile-based toy in the console, inspired heavily by roguelike games like Rogue (1980). Proof of concept. +Play with a simple ASCII-only tile-based toy in the console, inspired heavily by roguelike games like Rogue (1980). Proof of concept. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/matt-dray/r.oguelike} \item \url{https://matt-dray.github.io/r.oguelike/} + \item \url{https://www.rostrum.blog/tags/r.oguelike/} \item Report bugs at \url{https://github.com/matt-dray/r.oguelike/issues} } diff --git a/man/start_game.Rd b/man/start_game.Rd index 70accfc..3c73af3 100644 --- a/man/start_game.Rd +++ b/man/start_game.Rd @@ -12,7 +12,8 @@ start_game( iterations = 4L, is_snake = FALSE, is_organic = TRUE, - is_colour = TRUE + has_colour = TRUE, + has_sfx = TRUE ) } \arguments{ @@ -37,8 +38,11 @@ random chance of becoming floor tiles themselves with each iteration.} iterative growth steps (\code{TRUE}, the default), or after (\code{FALSE})? See details.} -\item{is_colour}{Logical. Should the characters in the output be coloured +\item{has_colour}{Logical. Should the characters in the output be coloured using \code{\link[crayon]{crayon}} (\code{TRUE}, the default)?} + +\item{has_sfx}{Logical. Should sound effects be used? Defaults to +\code{TRUE}.} } \value{ Nothing. Clears the console and prints to it with