Little useless-useful R functions โ€“ Desk plant simulator

[This article was first published on R โ€“ TomazTsql, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This time, we will create a Desk plant simulator. And for that we need a set of different functions ๐Ÿ™‚

Yes, we will grow a R plant in a simulation game with lots of twerks and hidden gems and ASCII art ๐Ÿ™‚

And how this set of functions really work?

Storing the daily progress in an RDS. Before running set of functions, you will prepare a location to store the RDS file (environment variables and game play)

.plant_file <- path.expand("~/.r_desk_plant.rds")

ASCII art was done by GPT for all the stages and it is so nice ๐Ÿ™‚

plant_art <- list(    # Stage 0: Seed  seed = c(    "            ",    "            ",    "            ",    "            ",    "     .      ",    "    (.)     ",    "   -----    ",    "  |     |   ",    "  |~~~~~|   ",    "  |_____|   "  ),    # Stage 1: Sprout  sprout = c(    "            ",    "            ",    "            ",    "     ,      ",    "    (')     ",    "     |      ",    "   -----    ",    "  |     |   ",    "  |~~~~~|   ",    "  |_____|   "  ),    # Stage 2: Seedling  seedling = c(    "            ",    "            ",    "     \\|/    ",    "    \\|||/   ",    "     |||    ",    "     |||    ",    "   -----    ",    "  |     |   ",    "  |~~~~~|   ",    "  |_____|   "  ),    # Stage 3: Young plant  young = c(    "            ",    "    \\~~/    ",    "   \\\\|//   ",    "    \\|/     ",    "    |||     ",    "    |||     ",    "   -----    ",    "  |     |   ",    "  |~~~~~|   ",    "  |_____|   "  ),......

Then you will have helper and main functions available. Complete code is available on my Github repository.

To run the game you will need to plant a new plant, water it, check the plant status and many others.

PlantNew() # Start a new plant (choose a name!)WaterPlant() # Water your plant (once per day)CheckPlant() # Full status reportPlant() # Quick view (just the art)

Here is an excerpt from the code:

hline <- function(char = "โ•", n = 40) strrep(char, n)

# get current status of a plant / game play
get_plant <- function(file = .plant_file) {
  if (file.exists(file)) { readRDS(file) } 
}


# Save plant in RDS and all game play!
save_plant <- function(plant, file = .plant_file) {
  saveRDS(plant, file)
}


# get growth info
get_stage <- function(points) {
  stage_idx <- max(which(growth_stages$min_points <= points))
  growth_stages[stage_idx, ]
}


# get appropriate ASCII art plant
get_plant_art <- function(plant) {
  if (plant$health <= 0) {
    return(plant_art$dead)
  }
  if (plant$health < 30) {
    return(plant_art$wilted)
  }
  stage <- get_stage(plant$points)
  plant_art[[stage$art_name]]
}

days_since <- function(date) {
  as.integer(Sys.Date() - as.Date(date))
}


# Messages for encouragement - Done with help of chatGPT
get_encouragement <- function() {
  messages <- c(
    "Your plant appreciates you!",
    "Keep up the great work!",
    "You're a natural plant parent!",
    "Your R sessions make the plant happy!",
    "Photosynthesis in progress... ",
    "Growing strong, just like your R skills!",
    "The plant sends positive vibes!",
    "Another day, another leaf!",
    "Your dedication is blooming!",
    "Keep coding, keep growing!"
  )
  sample(messages, 1)
}


# General health
get_health_message <- function(health) {
  if (health >= 90) {
    return("Thriving! Your plant is radiantly healthy!")
  } else if (health >= 70) {
    return("Healthy! Looking good!")
  } else if (health >= 50) {
    return("Okay. Could use some attention.")
  } else if (health >= 30) {
    return("Struggling. Please water me!")
  } else if (health > 0) {
    return("Critical! Water immediately or I'll die!")
  } else {
    return("Your plant has died. Start a new one with PlantNew()")
  }
}


# ---- main functions

PlantNew <- function(name = NULL, file = .plant_file) {
  existing <- get_plant(file)
  
  if (!is.null(existing) && existing$health > 0) {
    cat("  You already have a plant named '", existing$name, "'!\n", sep = "")
    cat(" Health: ", existing$health, "% | Stage: ", 
        get_stage(existing$points)$name, "\n\n", sep = "")
    cat("  Are you sure you want to replace it? (yes/no): ")
    response <- tolower(readline())
    if (response != "yes") {
      cat("  Keeping your existing plant.  \n\n")
    }
  }
  
  
  # Get plant name
  if (is.null(name)) {
    suggestions <- c("Fernie Bennes", "Morgan Treeman", "Leaf Seinfeld",
                     "Plantonio Banderas", "Elvis Parsley", "Kramerofern",
                     "Snake Costanza", "Aloe NewmanVera", "Jungle Tribbiani")
    
    cat("\n")
    cat("    NEW PLANT!\n")
    cat("  Some name suggestions:\n")
    for (i in seq_along(suggestions)) {
      cat(sprintf("    %d. %s\n", i, suggestions[i]))
    }
    cat("\n  Enter a name (or number, or press Enter for random): ")
    input <- readline()
    
    if (input == "") {
      name <- sample(suggestions, 1)
    } else if (grepl("^[0-9]+$", input)) {
      idx <- as.integer(input)
      if (idx >= 1 && idx <= length(suggestions)) {
        name <- suggestions[idx]
      } else {
        name <- input
      }
    } else {
      name <- input
    }
  }
  
  # Create new plant
  plant <- list(
    name = name,
    species = "R-Plant (Programmus enthusiasticus)",
    planted_date = Sys.Date(),
    last_watered = Sys.Date(),
    last_visited = Sys.time(),
    points = 0,
    health = 100,
    times_watered = 0,
    sessions = 0,
    achievements = character(0)
  )
  
  save_plant(plant, file)

  cat("\n")
  cat("  โ•”", hline("โ•", 44), "โ•—\n", sep = "")
  cat("  โ•‘         NEW PLANT CREATED!               โ•‘\n")
  cat("  โ•š", hline("โ•", 44), "โ•\n", sep = "")
  cat("\n")
  
  art <- plant_art$seed
  for (line in art) {
    cat("        ", line, "\n", sep = "")
  }
  
  cat("\n")
  cat("Name:    ", name, "\n", sep = "")
  cat("Species: ", plant$species, "\n", sep = "")
  cat("Planted: ", format(Sys.Date(), "%B %d, %Y"), "\n", sep = "")
  cat("\n")
  cat("Tips:\n")
  cat(" --> Use WaterPlant() to water your plant\n")
  cat(" --> Use CheckPlant() to see its status\n")
  cat(" --> Visit often - your R sessions help it grow!\n")
  cat("\n")

}



# -- Water planting 
WaterPlant <- function(file = .plant_file) {
  plant <- get_plant(file)
  if (is.null(plant)) {
    cat("\n No plant found! Start one with PlantNew()\n\n")
  }
  
  if (plant$health <= 0) {
    cat("\n  Your plant has died. Start a new one with PlantNew()\n\n")
  }
  
  # Check if already watered today
  last_water_date <- as.Date(plant$last_watered)
  today <- Sys.Date()
  
  if (last_water_date == today) {
    cat("\n")
    cat("  Already watered today!\n")
    cat("  Your plant doesn't want to drown. \n")
    cat("  Come back tomorrow!\n\n")
    return(invisible(plant))
  }
  
  # Calculate bonus for consecutive days
  days_since_water <- days_since(plant$last_watered)
  water_points <- 15
  health_gain <- 20
  if (days_since_water == 1) {
    water_points <- water_points + 5  # Consecutive day bonus
    plant$achievements <- union(plant$achievements, "daily_waterer")
  }
  
  plant$last_watered <- today
  plant$points <- plant$points + water_points
  plant$health <- min(100, plant$health + health_gain)
  plant$times_watered <- plant$times_watered + 1
  plant$last_visited <- Sys.time()
  
  # Check for achievements
  if (plant$times_watered == 10 && !"10_waters" %in% plant$achievements) {
    plant$achievements <- c(plant$achievements, "10_waters")
  }
  if (plant$times_watered == 50 && !"50_waters" %in% plant$achievements) {
    plant$achievements <- c(plant$achievements, "50_waters")
  }
  
  save_plant(plant, file)
  
  # Get stage info
  old_stage <- get_stage(plant$points - water_points)
  new_stage <- get_stage(plant$points)
  leveled_up <- new_stage$stage > old_stage$stage
  
  # Display
  cat("\n")
  cat("  โ•”", hline("โ•", 44), "โ•—\n", sep = "")
  cat("  โ•‘            WATERING TIME!                 โ•‘\n")
  cat("  โ•š", hline("โ•", 44), "โ•\n", sep = "")
  cat("\n")

  cat("    Watering '", plant$name, "'...\n\n", sep = "")
  art <- get_plant_art(plant)
  for (line in art) {
    cat("        ", line, "\n", sep = "")
  }

  cat(sprintf("  +%d growth points! (Total: %d)\n", water_points, plant$points))
  cat(sprintf("  Health: %d%% %s\n", plant$health, strrep("โ–ˆ", plant$health %/% 10)))

  if (leveled_up) {
    cat("\n")
    cat("  โ•”", hline("โ•", 44), "โ•—\n", sep = "")
    cat("     LEVEL UP! Your plant is now: ", new_stage$emoji, " ", new_stage$name, "\n", sep = "")
    cat("  โ•”", hline("โ•", 44), "โ•—\n", sep = "")
  }
  cat("\n  ", get_encouragement(), "\n\n", sep = "")
}


# --  Check on your desk plant
CheckPlant <- function(file = .plant_file) {
  plant <- get_plant(file)
  if (is.null(plant)) {
    cat("\n    No plant found! Start one with PlantNew()\n\n")
  }
  
  days_without_water <- days_since(plant$last_watered)
  if (days_without_water > 1 && plant$health > 0) {
    # Lose health for each day without water (after first day)
    health_loss <- (days_without_water - 1) * 10
    plant$health <- max(0, plant$health - health_loss)
  }
  
  session_start <- Sys.getenv("R_SESSION_TMPDIR")  # Unique per session
  
  visit_points <- 2
  plant$points <- plant$points + visit_points
  plant$sessions <- plant$sessions + 1
  plant$last_visited <- Sys.time()

  if (plant$sessions == 100 && !"100_sessions" %in% plant$achievements) {
    plant$achievements <- c(plant$achievements, "100_sessions")
  }
  
  save_plant(plant, file)
  
  # Get stage info
  stage <- get_stage(plant$points)
  days_alive <- days_since(plant$planted_date)
  
  # Display
  cat("\n")
  cat("  โ•”", hline("โ•", 44), "โ•—\n", sep = "")
  cat("  โ•‘          PLANT STATUS REPORT              โ•‘\n")
  cat("  โ•š", hline("โ•", 44), "โ•\n", sep = "")
  cat("\n")
  
  # Show art
  art <- get_plant_art(plant)
  for (line in art) {
    cat("        ", line, "\n", sep = "")
  }
  
  cat("\n")
  cat("  ", hline("โ”€", 44), "\n", sep = "")
  cat(sprintf("  Name:        %s\n", plant$name))
  cat(sprintf("  Age:         %d day%s old\n", days_alive, if(days_alive != 1) "s" else ""))
  cat(sprintf("  Stage:       %s %s\n", stage$emoji, stage$name))
  cat(sprintf("  Points:      %d / %d (next stage)\n", 
              plant$points, 
              ifelse(stage$stage < 7, growth_stages$min_points[stage$stage + 2], "MAX")))
  cat("  ", hline("โ”€", 44), "\n", sep = "")
  
  # Health bar
  health_bar <- paste0(
    strrep("โ–ˆ", plant$health %/% 10),
    strrep("โ–‘", 10 - plant$health %/% 10)
  )
  cat(sprintf("  Health:      [%s] %d%%\n", health_bar, plant$health))
  cat(sprintf("  Status:      %s\n", get_health_message(plant$health)))
  cat("  ", hline("โ”€", 44), "\n", sep = "")
  
  # Water status
  days_since_water <- days_since(plant$last_watered)
  if (days_since_water == 0) {
    water_status <- " Watered today!"
  } else if (days_since_water == 1) {
    water_status <- " Watered yesterday"
  } else {
    water_status <- sprintf("   %d days without water!", days_since_water)
  }
  cat(sprintf("  Last water:  %s\n", water_status))
  cat("  ", hline("โ”€", 44), "\n", sep = "")

  # Stats
  cat(sprintf("  Waterings:   %d total\n", plant$times_watered))
  cat(sprintf("  Visits:      %d R sessions\n", plant$sessions))
  
  # Achievements
  if (length(plant$achievements) > 0) {
    cat("  ", hline("โ”€", 44), "\n", sep = "")
    cat("    Achievements:\n")
    achievement_names <- list(
      "daily_waterer" = "Daily Waterer - Watered on consecutive days",
      "10_waters" = "Hydration Helper - Watered 10 times",
      "50_waters" = "Water Master - Watered 50 times",
      "100_sessions" = "Dedicated Parent - 100 R sessions"
    )
    for (ach in plant$achievements) {
      if (ach %in% names(achievement_names)) {
        cat(sprintf("  %s\n", achievement_names[[ach]]))
      }
    }
  }
  
  if (days_since_water >= 1 && plant$health > 0) {
    cat("  Don't forget to WaterPlant()!\n\n")
  }
  
}


# Quick status - just show the plant
Plant <- function(file = .plant_file) {
  plant <- get_plant(file)
  
  if (is.null(plant)) {
    cat("\n No plant! Use PlantNew() to start.\n\n")
  }
  
  # Update health decay silently
  days_without_water <- days_since(plant$last_watered)
  if (days_without_water > 1 && plant$health > 0) {
    health_loss <- (days_without_water - 1) * 10
    plant$health <- max(0, plant$health - health_loss)
    save_plant(plant, file)
  }
  
  stage <- get_stage(plant$points)
  art <- get_plant_art(plant)
  
  cat("\n")
  for (line in art) {
    cat("        ", line, "\n", sep = "")
  }
  cat("\n")
  cat(sprintf("  %s %s | Day %d | %d%% health\n",
              stage$emoji, plant$name, 
              days_since(plant$planted_date),
              plant$health))
  cat("\n")
}


PlantDelete <- function(file = .plant_file, confirm = TRUE) {
  
  plant <- get_plant(file)
  
  if (is.null(plant)) {
    cat("\n  No plant to delete.\n\n")
    return(invisible(FALSE))
  }
  
  if (confirm) {
    cat("\n  Are you sure you want to delete '", plant$name, "'?\n", sep = "")
    cat("  This cannot be undone! (yes/no): ")
    response <- tolower(readline())
    if (response != "yes") {
      cat("  Keeping your plant safe.  \n\n")
    }
  }
  
  file.remove(file)
  cat("\n Goodbye, ", plant$name, "...\n", sep = "")
  cat(" Use PlantNew() to start fresh.\n\n")
}

#optional
.onAttach <- function() {
  plant <- get_plant()
  if (!is.null(plant) && plant$health > 0) {
    stage <- get_stage(plant$points)
    message(sprintf(" Your plant '%s' (%s) is waiting! Use CheckPlant() to visit.",
                    plant$name, stage$name))
  }
}

# Run welcome check
local({
  plant <- get_plant()
  if (!is.null(plant) && plant$health > 0) {
    days_without_water <- days_since(plant$last_watered)
    stage <- get_stage(plant$points)
    cat(sprintf("\n '%s' says hello! (%s, Day %d)\n",
                plant$name, stage$name, days_since(plant$planted_date)))
    if (days_without_water >= 2) {
      cat(sprintf("It's been %d days without water!\n", days_without_water))
    }
    cat("\n")
  }
})


PRO-tips:

  • Water daily for bonus points
  • Just running CheckPlant() gives small growth points
  • Donโ€™t forget to water or your plant will wilt!
  • Health below 30% = wilted, Health 0% = dead

As always, the complete code is available on GitHub in ย Useless_R_function repository. The Plant simulator isย hereย (filename:ย plant_tame.R).

Check the repository for future updates!

Stay healthy and happy R-coding!

Disclaimer: all ASCII art was done by GPT, as well as plant names and ASCII game progress.

To leave a comment for the author, please follow the link and comment on their blog: R โ€“ TomazTsql.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)