This vignette shows how to build a Pacman game board and animate the sprites within to produce a system which displays the movement in realtime.
This vignette is just a demo of the graphical framework for a game using the nara package. It is not meant to be a playable game.
Many things be needed to make this into an actual game. Listed in a rough order of importance:
{eventloop}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Helper function: Reverse the characters in a string
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
str_rev <- function(x) {
paste(rev(strsplit(x, '')[[1]]), collapse="")
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Helper function: Swap 2 characters in a string.
# Dodgy implementation. Good enough for now.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
str_swap <- function(x, s1, s2) {
x <- gsub(s1 , 'x', x)
x <- gsub(s2 , s1, x)
x <- gsub('x', s2, x)
x
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define the left half of the board
# Board is symmetrical.
# Full board = 31 x 28
#
# Define "obstruction" pieces. e.g. "1" represents a rounded quarter-circle
# corner in the top-left of a square
#
# 1 2 3
# 4 5 6
# 7 8 9
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
left <- c(
"12222222222223",
"4............6",
"4.1223.12223.6",
"4.6554.45556.6",
"4.7889.78889.7",
"4.............",
"4.1223.13.1222",
"4.7889.46.7883",
"4......46....4",
"788883.47883.4",
"555556.41229.7",
"555556.46.....",
"555556.46.1222",
"555556.79.4555",
"555556....4555", # Middle
"555556.13.4555",
"555556.46.7888",
"555556.46.....",
"555556.46.1222",
"122229.79.7883",
"4............6",
"4.1223.12223.6",
"4.7834.78889.7",
"4...46........",
"783.46.13.1222",
"129.79.46.7883",
"4......46....6",
"4.1222297223.6",
"4.7888888889.7",
"4.............",
"78888888888888"
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Mirror the left of the board and update the tile references
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
right <- purrr::map_chr(left, str_rev)
right <- str_swap(right, '1', '3')
right <- str_swap(right, '4', '6')
right <- str_swap(right, '7', '9')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Rearracnge board into a 31*28 character matrix
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
board <- purrr::map2_chr(left, right, ~paste0(.x, .y, collapse = ""))
board <- unlist(stringr::str_split(board, ''))
board <- matrix(board, nrow = 31, ncol = 28, byrow = TRUE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Load the maze parts
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
im <- png::readPNG("image/game-maze.png")
#> Warning in png::readPNG("image/game-maze.png"): libpng warning: iCCP: known
#> incorrect sRGB profile
dim(im)
#> [1] 248 368 3
grid.raster(im)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Parse out the pacman sprites
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
s <- vector('list', 9)
row <- 1; col <- 7; s[[1]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <- 5; s[[2]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <- 6; s[[3]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <- 8; s[[4]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 2; col <-12; s[[5]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <- 9; s[[6]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <-11; s[[7]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <- 5; s[[8]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
row <- 1; col <-10; s[[9]] <- im[28 + row*9 + 0:7, 226 + col*9 + 0:7,]
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convert from array to nativeraster
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
s <- lapply(s, nara::array_to_nr)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a blank native raster for the board
# 31 squares high. 28 squares wide
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
blank_board_nr <- nr_new(width = 28 * 8, height = 31 * 8, fill = 'black')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Copy the appropriate maze piece into the board
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (row in 1:31) {
for (col in 1:28) {
val <- board[31 + 1 - row, col]
if (val != '.') {
idx <- as.integer(val)
nr_blit(blank_board_nr, s[[idx]], (col - 1) * 8 + 1, (row - 1) * 8 + 1)
}
}
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Draw the blank board
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
grid.newpage()
grid.raster(blank_board_nr, interpolate = FALSE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dots <- arrayInd(which(board == '.'), dim(board))
dots[,1] <- 32 - dots[,1] # flip y
dots <- as.data.frame(dots)
names(dots) <- c('y', 'x')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a 2x2 pixel nativeraster to represented the dot onscreen
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dot_mat <- matrix(rep('white', 4), 2, 2)
dot_nr <- nara::raster_to_nr(dot_mat)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test drawing the dots over the blank board
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
nr <- nr_duplicate(blank_board_nr)
nr_blit(nr, dot_nr, (dots$x - 0.5) * 8, (dots$y - 0.5) * 8)
grid.newpage()
grid.raster(nr, interpolate = FALSE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Load the spritemap for pacman and the ghosts
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
spritemap <- png::readPNG("image/game-sprites.png")
dim(spritemap)
#> [1] 248 680 3
grid.raster(spritemap)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Extract a sprite from the spritemap
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
extract <- function(row, col) {
sprite <- spritemap[1 + (row-1)*16 + 0:15, 457 + (col-1)*16 + 0:15,]
alpha <- sprite[,,1] == 1 | sprite[,,2] == 1 | sprite[,,3] == 1
alpha[] <- as.numeric(alpha)
new <- c(sprite, alpha)
d <- dim(sprite)
d[3] <- 4
dim(new) <- d
nara::array_to_nr(new)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Extract pacman sprites
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pacman <- list(
right = list(extract(1, 1), extract(1, 2), extract(1, 3), extract(1, 2)),
left = list(extract(2, 1), extract(2, 2), extract(1, 3), extract(2, 2)),
up = list(extract(3, 1), extract(3, 2), extract(1, 3), extract(3, 2)),
down = list(extract(4, 1), extract(4, 2), extract(1, 3), extract(4, 2))
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Extract ghost sprites
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ghost1 <- list(
right = list(extract(5, 1), extract(5, 2)),
left = list(extract(5, 3), extract(5, 4)),
up = list(extract(5, 5), extract(5, 6)),
down = list(extract(5, 7), extract(5, 8))
)
ghost2 <- list(
right = list(extract(6, 1), extract(6, 2)),
left = list(extract(6, 3), extract(6, 4)),
up = list(extract(6, 5), extract(6, 6)),
down = list(extract(6, 7), extract(6, 8))
)
ghost3 <- list(
right = list(extract(7, 1), extract(7, 2)),
left = list(extract(7, 3), extract(7, 4)),
up = list(extract(7, 5), extract(7, 6)),
down = list(extract(7, 7), extract(7, 8))
)
ghost4 <- list(
right = list(extract(8, 1), extract(8, 2)),
left = list(extract(8, 3), extract(8, 4)),
up = list(extract(8, 5), extract(8, 6)),
down = list(extract(8, 7), extract(8, 8))
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Combine all ghosts
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ghost <- list(
ghost1, ghost2, ghost3, ghost4
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test plot of a single sprite
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
grid.raster(ghost4$right[[1]], interpolate = FALSE)
Generate a realtime animation of pacman and ghosts running across the screen.
library(grid)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Open a a double-buffered x11() device.
# - turn antialiasing off (don't need it for pixel rendering)
# - inibit storaget of a displaylist
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x11(type = 'dbcairo', antialias = 'none')
dev.control('inhibit')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# In-memory rendering buffer
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
nr <- nr_new(100, 30, fill = 'black')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Loop and display
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in c((-20):180, (-20):180)) {
nr_fill(nr, 'black')
nr_blit(nr, pacman$right[[(bitwShiftR(i, 1) %% 4) + 1]], i , 5)
nr_blit(nr, ghost1$right[[(bitwShiftR(i, 1) %% 2) + 1]], i - 20, 5)
nr_blit(nr, ghost2$right[[(bitwShiftR(i, 1) %% 2) + 1]], i - 40, 5)
nr_blit(nr, ghost3$right[[(bitwShiftR(i, 1) %% 2) + 1]], i - 60, 5)
nr_blit(nr, ghost4$right[[(bitwShiftR(i, 1) %% 2) + 1]], i - 80, 5)
if (i < 20 + 0 * 11) nr_text(nr, "#", 20 + 0 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 1 * 11) nr_text(nr, "R", 20 + 1 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 2 * 11) nr_text(nr, "s", 20 + 2 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 3 * 11) nr_text(nr, "t", 20 + 3 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 4 * 11) nr_text(nr, "a", 20 + 4 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 5 * 11) nr_text(nr, "t", 20 + 5 * 11, 9, colour = 'white', fontsize = 11)
if (i < 20 + 6 * 11) nr_text(nr, "s", 20 + 6 * 11, 9, colour = 'white', fontsize = 11)
dev.hold()
grid.raster(nr, interpolate = FALSE)
dev.flush()
Sys.sleep(0.02)
}
Junctions in the game are where the character could change directions.
Ghosts (and the auto-driving pacman) should only change directions at a junction where there is a choice in direcctions. This will remove arbitrary reversals in direction.
# Movement map
roll_down <- rbind(board[-1, ], rep(NA, 28))
roll_up <- rbind(rep(NA, 28), board[-nrow(board), ])
roll_right <- cbind(board[,-1], rep(NA, 31))
roll_left <- cbind(rep(NA, 31), board[,-ncol(board)])
move_left <- board == '.' & roll_left == '.'
move_right <- board == '.' & roll_right == '.'
move_up <- board == '.' & roll_up == '.'
move_down <- board == '.' & roll_down == '.'
moves <- list(
left = move_left ,
right = move_right,
up = move_up ,
down = move_down
)
junction <- (move_left | move_right) & (move_up | move_down) #&
# (move_left + move_right + move_up + move_down > 2)
mode(move_left) <- 'integer'
mode(move_right) <- 'integer'
mode(move_up) <- 'integer'
mode(move_down) <- 'integer'
mode(junction) <- 'integer'
# Plot junction locations where ghost/pacman movements may be changed
coords <- arrayInd(which(junction == 1), dim(junction))
junction_nr <- nr_duplicate(blank_board_nr)
nr_rect(junction_nr, coords[,2]*8 - 3, (32 - coords[,1])*8 - 3, 2, 2, 'white')
grid.raster(junction_nr, interpolate = FALSE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Open a a double-buffered x11() device.
# - turn antialiasing off (don't need it for pixel rendering)
# - inibit storage of a displaylist. not needed.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x11(type = 'dbcairo', antialias = 'none')
dev.control(displaylist = 'inhibit')
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Given the row and column pick a random direction to move in
# Ensure that the direction chosen isn't the reverse of the current
# direction
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
choose_direction <- function(row, col, current) {
dirs <- c('left', 'right', 'up', 'down')
reverse <- c('right', 'left', 'down', 'up')
remove <- which(reverse == current)
idxs <- setdiff(1:4, remove)
for (i in sample(idxs)) {
if (moves[[i]][32 - row, col]) break
}
dirs[i]
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 'pacman' state information
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pacdir <- ""
dx <- 0
dy <- 0
row <- 2
col <- 2
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Current dots in the scene
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cdots <- dots
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Ghost state information. This will be updated during the game
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gh <- list(
list(row = 2, col = 2, dx = 0, dy = 0, dir = "down"),
list(row = 2, col = 27, dx = 0, dy = 0, dir = "down"),
list(row = 30, col = 2, dx = 0, dy = 0, dir = "down"),
list(row = 30, col = 27, dx = 0, dy = 0, dir = "down")
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create working buffer where the board will be rendered
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
board_nr <- nr_duplicate(blank_board_nr)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in 1:100) {
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Eat the dot where the pacman is
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
matches <- (cdots$y == row & cdots$x == col)
cdots <- cdots[!matches,, drop = FALSE]
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# If pacman at a junction, then decide new direction
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (junction[32 - row, col]) {
dir <- choose_direction(row, col, pacdir)
pacdir <- dir
if (dir == 'left' ) {dx = -1; dy = 0} else
if (dir == 'right') {dx = 1; dy = 0} else
if (dir == 'up' ) {dx = 0; dy = 1} else
if (dir == 'down' ) {dx = 0; dy = -1} else
{dx = 0; dy = 0}
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# For each ghost:
# - if at a junction, choose a random new direction
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in seq_along(gh)) {
if (junction[32 - gh[[i]]$row, gh[[i]]$col]) {
dir <- choose_direction(gh[[i]]$row, gh[[i]]$col, gh[[i]]$dir)
gh[[i]]$dir <- dir
if (dir == 'left' ) {gh[[i]]$dx = -1; gh[[i]]$dy = 0} else
if (dir == 'right') {gh[[i]]$dx = 1; gh[[i]]$dy = 0} else
if (dir == 'up' ) {gh[[i]]$dx = 0; gh[[i]]$dy = 1} else
if (dir == 'down' ) {gh[[i]]$dx = 0; gh[[i]]$dy = -1} else
{gh[[i]]$dx = 0; gh[[i]]$dy = 0}
}
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Move pacman/ghosts from one location to the next in 8 steps
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (step in 1:8) {
# Copy blank board into our working buffer
nr_copy_into(board_nr, blank_board_nr)
# Blit current dots into board
nr_blit(board_nr, dot_nr, (cdots$x - 0.5) * 8, (cdots$y - 0.5) * 8)
# Blit ghosts into board
for (i in seq_along(gh)) {
nr_blit(
board_nr,
ghost[[i]][[gh[[i]]$dir]][[ bitwShiftR(step, 1L) %% 2 + 1L]],
x = gh[[i]]$col * 8 - 11 + step * gh[[i]]$dx,
y = gh[[i]]$row * 8 - 11 + step * gh[[i]]$dy
)
}
# Blit pacman into board
nr_blit(
board_nr,
pacman[[pacdir]][[ bitwShiftR(step, 1L) %% 4 + 1L]],
x = col * 8 - 11 + step * dx,
y = row * 8 - 11 + step * dy
)
# Draw everything to screen
dev.hold()
grid.raster(board_nr)
dev.flush()
# regulate drawing speed. Replace with {eventloop} for actual game
Sys.sleep(0.01)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Advance pacman location to new row/col
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
row <- row + dy
col <- col + dx
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Advance ghosts to the next row/col
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in seq_along(gh)) {
gh[[i]]$row <- gh[[i]]$row + gh[[i]]$dy
gh[[i]]$col <- gh[[i]]$col + gh[[i]]$dx
}
}