CLEARSCREEN <- function () {
rm(list = ls(envir = LOGO), envir = LOGO)
new_field(5)
}Logo with Gimmicks
Logo
Goal: Programming a simple language with R
Logo goes back much earlier than the Apple II, it was developed in 1967 by Cynthia Solomon, Wally Feurzig, and Seymour Papert. That’s five years before C and 24 years before Python! The three worked at Bolt, Beranek, and Newman (BBN), famous for all kinds of other computing history. BBN built the first Interface Message Processors (early routers) in 1968 for the ARPANET, which would evolve into the modern internet. …learn more
Basic Commands
Implementing the basic commands see “Hello Turtle”
CLEARSCREEN (CS)
Clear the screen and initializing the field
CS <- function () {
CLEARSCREEN()
}HIDETURTLE (HT)
Don’t show the turtle cursor.
HIDETURTLE <- function () {
LOGO$turtle <- FALSE
record_path(turtle = LOGO$turtle)
}HT <- function () {
HIDETURTLE()
}SHOWTURTLE (ST)
Show the turtle cursor.
SHOWTURTLE <- function () {
LOGO$turtle <- TRUE
record_path(turtle = LOGO$turtle)
}ST <- function () {
SHOWTURTLE()
}FORWARD (FD)
Move forward steps.
FORWARD <- function (step) {
angle <- LOGO$path$rad[LOGO$pos]
x0 <- LOGO$path$x[LOGO$pos]
y0 <- LOGO$path$y[LOGO$pos]
max_step <- 1
if (step > max_step) {
n_step <- abs(step)%/%(max_step * 1.5) + 1
Seq0 <- seq(from = 0, to = 1, by = 1/n_step)
Seq0 <- Seq0[-1]
steps <- qbeta(Seq0, shape1 = 0.15, shape2 = 0.2)
steps <- steps * step
} else {
steps <- step
}
x1 <- x0 + cos(angle) * steps
y1 <- y0 + sin(angle) * steps
record_path(x = x1, y = y1, path_color = LOGO$path_color)
}FD <- function (step) {
FORWARD(step)
}BACK (BK)
Move back steps.
BACK <- function (steps) {
FORWARD(-steps)
}BK <- function (steps) {
BACK(steps)
}LEFT (LT)
Turn left this many degrees. Negative degrees work too, they’ll turn it right.
LEFT <- function (degs) {
LOGO$heading <- LOGO$heading + degs
LOGO$heading <- LOGO$heading %% 360
record_path(rad = LOGO$heading * pi / 180)
}LT <- function (degs) {
LEFT(degs)
}RIGHT (RT)
Turn right this many degrees.
RIGHT <- function (degs) {
LEFT(-degs)
}RT <- function (degs) {
RIGHT(degs)
}SETHEADING (SH)
Turn to an absolute heading of degrees.
SETHEADING <- function (deg) {
LOGO$heading <- deg
record_path(rad = LOGO$heading * pi / 180)
}SH <- function (deg) {
SETHEADING(deg)
}SETPOS (SP)
Set the position to x, y coordinates. These are Cartesian, so 0,0 is the middle of the screen.
SETPOS <- function (x1, y1) {
record_path(x = x1, y = y1, path_color = "transparent")
}SP <- function (x1, y1) {
SETPOS(x1, y1)
}HOME
Move back to the home position.
HOME <- function () {
SETPOS(x1 = 0, y1 = 0)
}SETPOSX (SPX)
Set the horizontal position to x.
SETPOSX <- function (x1) {
SETPOS(x1, y1 = LOGO$path$y[LOGO$pos])
}SPX <- function (x1) {
SETPOSX(x1)
}SETPOSY (SPY)
Set the vertical position to y.
SETPOSY <- function (y1) {
SETPOS(x1 = LOGO$path$x[LOGO$pos], y1)
}SPY <- function (y1) {
SETPOSY(y1)
}Gimmicks
SETPATHCOLOR (SPC)
SETPATHCOLOR <- function (path_color) {
LOGO$path_color <- path_color
record_path(path_color = LOGO$path_color)
}SPC <- function (path_color) {
SETPATHCOLOR(path_color)
}SETTURTLECOLOR (STC)
SETTURTLECOLOR <- function (turtle_color) {
LOGO$turtle_color <- turtle_color
record_path(turtle_color = LOGO$turtle_color)
}STC <- function (turtle_color) {
SETTURTLECOLOR(turtle_color)
}SETNEWCHUNK (SNC)
SETNEWCHUNK <- function () {
LOGO$chunk <- LOGO$chunk + 1
record_path(chunk = LOGO$chunk)
}SNC <- function () {
SETNEWCHUNK()
}UNDOCHUNK (UNDO)
UNDOCHUNK <- function () {
last_chunk <- max(LOGO$path$chunk)
if (last_chunk > 1) {
LOGO$path <- LOGO$path[LOGO$path$chunk != last_chunk,]
} else {
CLEARSCREEN()
}
LOGO$chunk <- max(LOGO$path$chunk)
LOGO$pos <- nrow(LOGO$path)
LOGO$heading <- LOGO$path$rad[LOGO$pos] * 180 / pi
LOGO$turtle <- LOGO$path$turtle[LOGO$pos]
LOGO$turtle_color <- LOGO$path$turtle_color[LOGO$pos]
last_pos <- LOGO$pos
while (LOGO$path$path_color[last_pos] == "transparent" && last_pos > 0) {
last_pos <- last_pos - 1
}
if (last_pos == 0) {
LOGO$path_color <- "blue"
} else {
LOGO$path_color <- LOGO$path$path_color[last_pos]
}
REPLOT()
}UNDO <- function () {
UNDOCHUNK()
}SETSPEED (SPEED)
SETSPEED <- function (speed) {
speed <- pmax(speed, 1)
speed <- round(speed)
LOGO$speed <- speed
}SPEED <- function (speed) {
SETSPEED(speed)
}REPLOT
REPLOT <- function (chunk = FALSE) {
PLOT(chunk)
}SAVEPATH (SAVE)
SAVESCREEN <- function (name) {
name_gif <- paste0(name, ".gif")
REPLOT()
file.copy(from = file.path(tempdir(), "Logo_Output.gif"),
to = here("LOGO", name_gif),
overwrite = TRUE)
}SAVE <- function (name) {
SAVESCREEN(name)
}QUITLOGO (QUIT)
QUITLOGO <- function () {
if (exists("LOGO", envir = .GlobalEnv)) {
rm(LOGO, envir = .GlobalEnv)
}
}QUIT <- function () {
QUITLOGO()
}Parsing Prompts
Clean strings
clean_prompt <- function (Prompt) {
Prompt <- gsub("([^a-zA-Z0-9])", " \\1 ", Prompt)
Prompt <- gsub("\\s+", " ", Prompt)
Prompt <- gsub("-\\s+(?=\\d)", "-", Prompt, perl = TRUE)
Prompt <- gsub("\\s*\\.\\s*", ".", Prompt)
Prompt <- gsub("\\s*_\\s*", "_", Prompt)
Prompt <- trimws(Prompt)
Prompt
}Analyzing and splitting Code
breakup_String <- function(String) {
cl_String <- clean_prompt(String)
split_String <- strsplit(cl_String, " ")[[1]]
df <- data.frame(Code = split_String)
n_func <- 0
df$ID_Func <- rep(0, nrow(df))
n_par <- 0
df$ID_Par <- rep(0, nrow(df))
ID <- 1
df$ID_Repeat <- rep(0, nrow(df))
brakets <- data.frame(ID = ID,
Status = TRUE,
n = 1)
level <- brakets$ID[max(which(brakets$Status == TRUE))]
df$n_Repeat <- rep(1, nrow(df))
is_par_braket <- FALSE
i <- 1
imax <- nrow(df) + 1
while ( i < imax ) {
if (df$Code[i] == "[") {
n_par <- n_par + 1
is_par_braket <- TRUE
i <- i + 1
next
}
if (is_par_braket) {
if (df$Code[i] == "]") {
is_par_braket <- FALSE
i <- i + 1
next
}
if ( !grepl("[A-Za-z]", df$Code[i])) {
df$ID_Func[i] <- n_func
df$ID_Par[i] <- n_par
df$ID_Repeat[i] <- brakets$ID[brakets$ID == level]
df$n_Repeat[i] <- brakets$n[brakets$ID == level]
i <- i + 1
next
} else {
inLower <- tolower(df$Code[i])
if (inLower == "n") {
df$Code[i] <- paste0("(",inLower,")")
} else if (inLower %in% colors()) {
df$Code[i] <- paste0("'",inLower,"'")
} else if (exists(inLower)) {
df$Code[i] <- inLower
} else {
df$Code[i] <- paste0("'",df$Code[i],"'")
}
df$ID_Func[i] <- n_func
df$ID_Par[i] <- n_par
df$ID_Repeat[i] <- brakets$ID[brakets$ID == level]
df$n_Repeat[i] <- brakets$n[brakets$ID == level]
i <- i + 1
next
}
} else {
if (df$Code[i] == "]") {
brakets$Status[brakets$ID == level] <- FALSE
level <- brakets$ID[max(which(brakets$Status == TRUE))]
i <- i + 1
next
}
if ( !grepl("[A-Za-z]", df$Code[i]) ) {
n_par <- n_par + 1
df$ID_Func[i] <- n_func
df$ID_Par[i] <- n_par
df$ID_Repeat[i] <- brakets$ID[brakets$ID == level]
df$n_Repeat[i] <- brakets$n[brakets$ID == level]
i <- i + 1
next
} else {
inCaps <- toupper(df$Code[i])
is_LOGO_func <- exists(inCaps) && is.function(get(inCaps))
if (is_LOGO_func) {
n_func <- n_func + 1
df$Code[i] <- inCaps
}
is_repeat <- inCaps == "REPEAT"
if (is_repeat) {
n_func <- n_func + 1
ID <- ID + 1
i = i + 2
df$Code[i] <- inCaps
brakets <- rbind(brakets,
data.frame(ID = ID,
Status = TRUE,
n = round(as.numeric(df$Code[i - 1]))))
level <- brakets$ID[max(which(brakets$Status == TRUE))]
}
df$ID_Func[i] <- n_func
df$ID_Repeat[i] <- brakets$ID[brakets$ID == level]
df$n_Repeat[i] <- brakets$n[brakets$ID == level]
i <- i + 1
next
}
}
}
return(df[df$ID_Func != 0,])
}CodeLine <- "CS repeat 2 [fd [-pi/n]] sPc [black]"
cl_splited <-breakup_String(CodeLine)
cl_splited Code ID_Func ID_Par ID_Repeat n_Repeat
1 CS 1 0 1 1
4 REPEAT 2 0 2 2
5 FD 3 0 2 2
7 - 3 1 2 2
8 pi 3 1 2 2
9 / 3 1 2 2
10 (n) 3 1 2 2
13 SPC 4 0 1 1
15 'black' 4 2 1 1
Create Calls
create_Calls <- function (splited) {
Calls <- splited %>%
group_by(ID_Func, ID_Par) %>%
mutate(Func_Par = ifelse(ID_Par > 0,1,0),
Code = ifelse(first(ID_Par) > 0,
paste0(Code, collapse = ""),
Code)) %>%
ungroup() %>%
distinct(Code, ID_Func, ID_Par, Func_Par, .keep_all = TRUE) %>%
group_by(ID_Func, Func_Par) %>%
mutate(Code = ifelse(first(Func_Par) > 0,
paste0(Code, collapse = ", "),
Code)) %>%
ungroup() %>%
distinct(Code, ID_Func, Func_Par, .keep_all = TRUE) %>%
group_by(ID_Func) %>%
mutate(Code = ifelse(n() > 1,
paste0(Code, collapse = "("),
paste0(Code, "(")),
Code = paste0(Code, ")")) %>%
ungroup() %>%
distinct(Code, ID_Func, .keep_all = TRUE) %>%
rename(Call = Code, ID = ID_Repeat, n = n_Repeat ) %>%
select(Call,ID, n)
if ( !any(grepl("^UN|SPEED|SAVE|QUIT", Calls$Call)) ) {
first_call <- data.frame(Call = "SETNEWCHUNK()", ID = 1, n = 1)
last_call <- data.frame(Call = "PLOT()", ID = 1, n = 1)
Calls <- rbind(first_call, Calls, last_call)
}
Calls
}Callshort <- create_Calls(cl_splited)
Callshort Call ID n
1 SETNEWCHUNK() 1 1
2 CS() 1 1
3 REPEAT() 2 2
4 FD(-pi/(n)) 2 2
5 SPC('black') 1 1
6 PLOT() 1 1
Expand Repeats
expand_Repeats <- function (Calls) {
IDmax <- max(Calls$ID)
if (IDmax == 1) {
return(as.vector(Calls$Call))
}
while (IDmax > 1){
dfm <- data.frame(
Call = character(),
ID = integer(),
n = integer(),
stringsAsFactors = FALSE
)
rID <- range(which(Calls$ID == IDmax))
n <- Calls$n[rID[1]]
middle <- (rID[1] + 1):rID[2]
for (i in 1:n) {
dfm_sub <- Calls[middle, ] %>%
mutate(Call = gsub("\\(n\\)", i, Call))
dfm <- rbind(dfm,dfm_sub)
}
dfm$ID <- Calls$ID[rID[1]-1]
dfm$n <- Calls$n[rID[1]-1]
before <- 1:(rID[1] - 1)
dfb <- Calls[before, ]
after <- (rID[2] + 1):nrow(Calls)
dfa <- Calls[after, ]
Calls <- rbind(dfb,dfm,dfa)
IDmax <- max(Calls$ID)
}
return(as.vector(Calls$Call[Calls$Call != ""]))
}Calls <- expand_Repeats(Callshort)
Calls[1] "SETNEWCHUNK()" "CS()" "FD(-pi/1)" "FD(-pi/2)"
[5] "SPC('black')" "PLOT()"
Run Calls
run_prompt <- function (Prompt) {
Prompt_splited <- breakup_String(Prompt)
Callshort <- create_Calls(Prompt_splited)
Calls <- expand_Repeats(Callshort)
for (i in 1:length(Calls)){
eval(parse(text = as.character(Calls[i])))
}
}Helper Functions
New field
Initializing the game
new_field <- function (size) {
dir_path <- here("LOGO", "temp")
chunk <- 1
pos <- 2
heading <- 0
turtle <- TRUE
turtle_color <- "red"
path_color <- "blue"
speed <- 50
path <- data.frame(
chunk = rep(1, pos),
x = rep(0, pos),
y = rep(0, pos),
rad = rep(0, pos),
path_color = rep("transparent", pos),
turtle = c(FALSE, rep(turtle, pos - 1)),
turtle_color = rep(turtle_color, pos),
stringsAsFactors = FALSE
)
LOGO$field_name = "LOGO"
LOGO$field_dir = here()
LOGO$size = abs(size)
LOGO$chunk = chunk
LOGO$pos = pos
LOGO$heading = heading
LOGO$turtle = turtle
LOGO$turtle_color = turtle_color
LOGO$path_color = path_color
LOGO$speed = speed
LOGO$path = path
}Record Path
record_path <- function (chunk = NULL,
x = NULL,
y = NULL,
rad = NULL,
path_color = NULL,
turtle = NULL,
turtle_color = NULL) {
n <- max(length(x),1)
template <- LOGO$path[LOGO$pos, ]
if (n > 1) {
newrows <- template[rep(1,n), ]
new_pos <- LOGO$pos + (1:n)
} else {
newrows <- template
new_pos <- LOGO$pos + 1
}
if (!is.null(chunk)) newrows$chunk <- chunk
if (!is.null(x)) newrows$x <- x
if (!is.null(y)) newrows$y <- y
if (!is.null(rad)) newrows$rad <- rad
if (!is.null(path_color)) newrows$path_color <- path_color
if (!is.null(turtle)) newrows$turtle <- turtle
if (!is.null(turtle_color)) newrows$turtle_color <- turtle_color
LOGO$path[new_pos,] <- newrows
LOGO$pos <- nrow(LOGO$path)
}Plot Functions
Field
plot_field <- function (stat) {
maxpath <- ceiling(max(abs(LOGO$path[1:stat,c("x","y")])) * 1.11)
LOGO$size <- max(c(LOGO$size, maxpath), na.rm = TRUE)
par(mar = c(0, 0, 0, 0),
xaxs = "i", yaxs = "i")
plot.new()
plot.window(xlim = c(-LOGO$size, LOGO$size),
ylim = c(-LOGO$size, LOGO$size),
asp = 1)
box()
}Path
plot_path <- function (stat) {
if (stat > 1) {
path_stat <- LOGO$path[1:stat,]
segments(x0 = path_stat$x[-stat],
y0 = path_stat$y[-stat],
x1 = path_stat$x[-1],
y1 = path_stat$y[-1],
col = path_stat$path_color[-1],
lwd = 2)
}
}Turtle
plot_turtle <- function (stat) {
show <- LOGO$path$turtle[stat]
if (show) {
pos_x <- LOGO$path$x[stat]
pos_y <- LOGO$path$y[stat]
symbols(x = pos_x,
y = pos_y,
circles = 0.02 * LOGO$size,
inches = FALSE,
fg = NA,
bg = LOGO$path$turtle_color[stat],
add = TRUE)
arrow_length <- 0.1 * LOGO$size
angle <- LOGO$path$rad[stat]
arrows(pos_x,
pos_y,
pos_x + cos(angle) * arrow_length,
pos_y + sin(angle) * arrow_length,
col = LOGO$path$turtle_color[stat],
code = 2,
length = 0.1,
angle = 20,
lwd = 2)
}
}Stat
plot_stat <- function (stat = NULL) {
if(is.null(stat)) stat <- nrow(LOGO$path)
tmpfile <- tempfile(fileext = ".png")
png(filename = tmpfile, width = 600, height = 600, res = 150)
plot_field(stat)
plot_path(stat)
plot_turtle(stat)
dev.off()
img <- image_read(tmpfile)
unlink(tmpfile)
img
}PLOT (Animation)
PLOT <- function (chunk = TRUE) {
invisible(NULL)
if (chunk) {
Range <- range(which(LOGO$path$chunk == max(LOGO$chunk)))
rmin <- max(2,Range[1])
rmax <- Range[2]
} else {
rmin <- 2
rmax <- nrow(LOGO$path)
}
step <- LOGO$speed/(rmax-rmin)
step <- pmin(pmax(step, 1/200), 1)
Seq0 <- seq(from = 0, to = 1, by = step)
plot_seq <- qbeta(Seq0, shape1 = 0.15, shape2 = 0.2)
plot_seq <- (rmax - rmin) * plot_seq + rmin
plot_seq <- round(plot_seq)
plot_seq <- unique(plot_seq)
img_list <- lapply(plot_seq, function(i) plot_stat(i))
Logo_Output <- image_animate(image_join(img_list),
fps = 10, loop = 1,
optimize = TRUE)
gif_path <- file.path(tempdir(), "Logo_Output.gif")
image_write(Logo_Output, path = gif_path)
print(Logo_Output)
}Application
run_LOGO <- function(string) {
if (!exists("LOGO", envir = .GlobalEnv)) {
assign("LOGO", new.env(), envir = .GlobalEnv)
LOGO <- get("LOGO", envir = .GlobalEnv)
new_field(5)
} else {
LOGO <- get("LOGO", envir = .GlobalEnv)
}
run_prompt(string)
}Run Game
Example from the homepage
with some fancy shadow and color effects
run_LOGO("CS SP [-pi/5] [-Sin(PI/2)/2] sPc [black]")
run_LOGO("REPEAT 20 [RepeaT 180 [FD 1 RT 2] RT 18]")
run_LOGO("SPEED 100")
run_LOGO("Home SpC [gREEN] StC [blue]")
run_LOGO("REPEaT 20.1 [rEPEAT 180 [fD 1 RT 2] RT 18]")
run_LOGO("UndoChunk")
run_LOGO("SpC [blue] StC [green]")
run_LOGO("REPEaT 5 [rEPEAT 180 [fD 1 RT 2] RT 72]")
run_LOGO("SpC [green] StC [red] rt 18")
run_LOGO("REPEaT 5 [rEPEAT 180 [fD 1 RT 2] RT 72]")
run_LOGO("SpC [yellow] rt 18")
run_LOGO("REPEaT 5 [rEPEAT 180 [fD 1 RT 2] RT 72]")
run_LOGO("SpC [red] rt 18 ht")
run_LOGO("REPEaT 5 [rEPEAT 360 [fD 0.5 RT 1] RT 72]")Saved Output
you can also save the output (Plot) for showing it around later
run_LOGO("SPEED 1 saVe [Logo01]")
Other Exercises
Since n is also used as a loop variable in my program, handling many other exercises doesn’t pose any issues.
https://softwareprogramming4kids.com/loops-in-logo/
Exercise 1:
run_LOGO("CS")
run_LOGO("REPEAT 80 [FD [N*2] RT 90]")
run_LOGO("undo")
run_LOGO("REPEAT 80 [FD [N*2^(N/40)] RT 90]")run_LOGO("SPEED 1 saVe [Logo02]")
Exercise 2:
run_LOGO("CS REPEAT 50 [ FD [n * 5] RIGHT 144 ]")run_LOGO("SPEED 1 saVe [Logo03]")
Exercise 3:
run_LOGO("CS REPEAT 150 [ FD [n * 2] RT 91 ]")run_LOGO("SPEED 1 saVe [Logo04]")
Quit LOGO
run_LOGO("QUIT")