diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000000000000000000000000000000000000..bb7e5b3d975a4a77cf7e80f4590aee3938302e15 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,7 @@ +^soccer\.Rproj$ +^\.Rproj\.user$ +^LICENSE\.md$ +^docker$ +^docker-compose.yml$ +^README.Rmd$ +^temp$ diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000000000000000000000000000000000000..f0dad676f481565851988636a9b40767ceb6c377 --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,25 @@ +Package: socceR +Title: RoboCupSoccer Simulation 2D log analysis tools +Version: 0.0.0.9000 +Authors@R: + person(given = "Keisuke", + family = "ANDO", + role = c("aut", "cre"), + email = "ando@maslab.aitech.ac.jp") +Description: Functions to parse logs and to extract various information from log data. +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.2.1 +Imports: + dplyr, + fs, + jsonlite, + purrr, + readr, + rlang, + stringr, + tidyr +Suggests: + testthat (>= 3.0.0) +Config/testthat/edition: 3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..7a55a0f139f70844b1c49c112f11b8d18e9d0939 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2022 +COPYRIGHT HOLDER: Keisuke ANDO diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..ed2162560017d7a2f36af68beff7c455b98cb658 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2022 Keisuke ANDO + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000000000000000000000000000000000000..fee187e341cd97624aef7e36d2016e7c6d28a0d0 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,26 @@ +# Generated by roxygen2: do not edit by hand + +export(read_log) +export(read_rcg) +export(read_rcl) +importFrom(dplyr,as_tibble) +importFrom(dplyr,filter) +importFrom(dplyr,mutate) +importFrom(dplyr,rename_all) +importFrom(dplyr,select) +importFrom(dplyr,select_if) +importFrom(fs,path) +importFrom(fs,path_ext) +importFrom(jsonlite,parse_json) +importFrom(purrr,discard) +importFrom(purrr,map) +importFrom(purrr,map_chr) +importFrom(readr,read_file) +importFrom(readr,read_lines) +importFrom(rlang,.data) +importFrom(stringr,str_extract) +importFrom(stringr,str_remove) +importFrom(stringr,str_replace) +importFrom(stringr,str_split) +importFrom(stringr,str_trim) +importFrom(tidyr,unnest) diff --git a/R/read_log.R b/R/read_log.R index b7ade746f4099d58474d93019e168ecce2f10456..1ff68cc708632a82590258e133f47aa78686c8a2 100644 --- a/R/read_log.R +++ b/R/read_log.R @@ -2,10 +2,20 @@ #' #' @param path log file path #' @return Parsed tibble of log file in \code{path} +#' @importFrom fs path path_ext +#' @importFrom readr read_file read_lines +#' @importFrom jsonlite parse_json +#' @importFrom dplyr as_tibble filter select select_if rename_all mutate +#' @importFrom tidyr unnest +#' @importFrom purrr map map_chr discard +#' @importFrom stringr str_replace str_extract str_remove str_split str_trim +#' @importFrom rlang .data #' @export #' @examples +#' \dontrun{ #' rcg <- read_log("data/20220405162804-HELIOS_base_3-vs-enemy_2.rcg") #' rcl <- read_log("data/20220405162804-HELIOS_base_3-vs-enemy_2.rcl") +#' } read_log <- function(path) { ext <- path |> path() |> path_ext() @@ -19,19 +29,54 @@ read_log <- function(path) { stop("Not supported format: ", path) } +filter_rcg <- function(rcg, type) { + filtered <- rcg |> + filter(.data$type == (!!type)) |> + select_if(~ !all(is.na(.x)) && !all(is.null(unlist(.x)))) |> + select(!type) + + return(filtered) +} + #' @rdname read_log -#' @export +#' @export read_rcg <- function(path) { - rcg <- path |> + parsed <- path |> read_file() |> parse_json(simplifyVector = TRUE, flatten = TRUE) |> - as_tibble() |> - filter(type == "show") |> - select(time, stime, players, ball.x, ball.y, ball.vx, ball.vy) |> - unnest(players) |> - select(time:capacity, ball.x:ball.vy) |> - rename(step = time) |> - rename_with(str_replace, pattern = "\\.", replacement = "_") + as_tibble() + + rcg <- list( + header = parsed |> + filter_rcg("header") |> + as.list(), + + server_param = parsed |> + filter_rcg("server_param") |> + rename_all(str_remove, "params.") |> + as.list(), + + player_param = parsed |> + filter_rcg("player_param") |> + rename_all(str_remove, "params.") |> + as.list(), + + player_type = parsed |> + filter_rcg("player_type") |> + rename_all(str_remove, "params."), + + team = parsed |> + filter_rcg("team") |> + unnest(.data$teams), + + show = parsed |> + filter_rcg("show") |> + unnest(.data$players) |> + rename_all(str_replace, "[.]", "_"), + + msg = parsed |> + filter_rcg("msg") + ) return(rcg) } @@ -43,29 +88,30 @@ read_rcl <- function(path) { read_lines() |> as_tibble() |> mutate( - step = value |> str_extract("\\d+") |> as.numeric(), - agent = value |> str_extract("\\w+_([0-9]{1,2}|Coach)(?!\\))"), - team = agent |> str_remove("_([0-9]{1,2}|Coach)"), - unum = agent |> str_extract("([0-9]{1,2}|Coach)$"), - commands = value |> + time = .data$value |> str_extract("\\d+") |> as.numeric(), + agent = .data$value |> str_extract("\\w+_([0-9]{1,2}|Coach)(?!\\))"), + team = .data$agent |> str_remove("_([0-9]{1,2}|Coach)"), + unum = .data$agent |> str_extract("([0-9]{1,2}|Coach)$"), + commands = .data$value |> str_extract("\\(.+\\)$") |> - map(~ .x |> - str_split("\\(|\\)", simplify = TRUE) |> - str_trim() |> - discard(~ .x == "")), + map( + ~ .x |> + str_split("\\(|\\)", simplify = TRUE) |> + str_trim() |> + discard(~ .x == "")), ) |> - unnest(commands) |> + unnest(.data$commands) |> mutate( - commands = commands |> str_split("\\ ", n = 2), - command = commands |> map_chr(1), - args = commands |> map(~ .x[-1]), + splited = .data$commands |> str_split("\\ ", n = 2), + command = .data$splited |> map_chr(1), + args = .data$splited |> map(~ .x[-1]), ) |> select( - step, - team, - unum, - command, - args + .data$time, + .data$team, + .data$unum, + .data$command, + .data$args ) return(rcl) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3545fef84dc6cfbf0d8acf8918bb830282c92f35 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3' + +services: + rstudio: + build: './docker/prairielearn' + image: 'soccer/studio' + container_name: 'soccer_rstudio' + environment: + TZ: 'Asia/Tokyo' + DISABLE_AUTH: 'true' + ports: + - '8787:8787' + volumes: + - '.:/home/rstudio/workspace' diff --git a/docker/prairielearn/Dockerfile b/docker/prairielearn/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..17b1d4e5ffcfb09e5bd02dde861518684f5085e4 --- /dev/null +++ b/docker/prairielearn/Dockerfile @@ -0,0 +1,33 @@ +# Alternative Rocker compatible with Apple M1 +FROM prairielearn/workspace-rstudio:latest + +# Change root user +USER 0 + +# Update package list +RUN apt-get update + +# Install X11 library for ggplot2 +RUN apt-get install -y libxt6 + +# Install Japanese fonts and language +RUN apt-get install -y \ + fonts-ipaexfont \ + fonts-noto-cjk \ + language-pack-ja + +# Set language to Japanese +ENV LC_ALL=ja_JP.UTF-8 +ENV LANG=ja_JP.UTF-8 + +# Set locale to Japanese +RUN locale-gen ja_JP.UTF-8 + +# Install R packages +RUN /rocker_scripts/install_tidyverse.sh +RUN install2.r -e -s -n -1 \ + conflicted \ + && rm -rf /tmp/downloaded_packages/ /tmp/*.rds + +# Change rstudio user +USER rstudio diff --git a/man/read_log.Rd b/man/read_log.Rd new file mode 100644 index 0000000000000000000000000000000000000000..9455959df7b36e43c040cfe1910c558d29829bea --- /dev/null +++ b/man/read_log.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/read_log.R +\name{read_log} +\alias{read_log} +\alias{read_rcg} +\alias{read_rcl} +\title{Read log file} +\usage{ +read_log(path) + +read_rcg(path) + +read_rcl(path) +} +\arguments{ +\item{path}{log file path} +} +\value{ +Parsed tibble of log file in \code{path} +} +\description{ +Read log file +} +\examples{ +\dontrun{ +rcg <- read_log("data/20220405162804-HELIOS_base_3-vs-enemy_2.rcg") +rcl <- read_log("data/20220405162804-HELIOS_base_3-vs-enemy_2.rcl") +} +} diff --git a/socceR.Rproj b/socceR.Rproj index 8e3c2ebc99e2e337f7d69948b93529a437590b27..69fafd4b6dddad27500cfc67efb9fb16e86a96bd 100644 --- a/socceR.Rproj +++ b/socceR.Rproj @@ -1,7 +1,7 @@ Version: 1.0 -RestoreWorkspace: Default -SaveWorkspace: Default +RestoreWorkspace: No +SaveWorkspace: No AlwaysSaveHistory: Default EnableCodeIndexing: Yes @@ -11,3 +11,12 @@ Encoding: UTF-8 RnwWeave: Sweave LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/R/detect_foul.R b/temp/detect_foul.R similarity index 100% rename from R/detect_foul.R rename to temp/detect_foul.R diff --git a/R/make_data.R b/temp/make_data.R old mode 100755 new mode 100644 similarity index 100% rename from R/make_data.R rename to temp/make_data.R diff --git a/R/make_graphics.R b/temp/make_graphics.R similarity index 100% rename from R/make_graphics.R rename to temp/make_graphics.R diff --git a/R/make_soccer_map.R b/temp/make_soccer_map.R similarity index 100% rename from R/make_soccer_map.R rename to temp/make_soccer_map.R diff --git a/R/output_spadl.R b/temp/output_spadl.R similarity index 100% rename from R/output_spadl.R rename to temp/output_spadl.R diff --git a/temp/playground.R b/temp/playground.R new file mode 100644 index 0000000000000000000000000000000000000000..15dd104542cfe2fea2211358aa1a6f0825fd166f --- /dev/null +++ b/temp/playground.R @@ -0,0 +1,8 @@ +#' PLAYGROUND +#' +#' devtools::check() # check coding style and test functions +#' devtools::load_all() # load socceR + +# make your functions or test cases +rcl <- read_log("tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcl") +rcg <- read_log("tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcg") diff --git a/R/rcg_convert.R b/temp/rcg_convert.R similarity index 100% rename from R/rcg_convert.R rename to temp/rcg_convert.R diff --git a/R/rcl_convert.R b/temp/rcl_convert.R similarity index 100% rename from R/rcl_convert.R rename to temp/rcl_convert.R diff --git a/R/read_old_rcg.R b/temp/read_old_rcg.R similarity index 100% rename from R/read_old_rcg.R rename to temp/read_old_rcg.R diff --git a/R/read_rcg.R b/temp/read_rcg.R similarity index 100% rename from R/read_rcg.R rename to temp/read_rcg.R diff --git a/R/read_rcl.R b/temp/read_rcl.R similarity index 100% rename from R/read_rcl.R rename to temp/read_rcl.R diff --git a/data/spadl.csv b/temp/spadl.csv similarity index 100% rename from data/spadl.csv rename to temp/spadl.csv diff --git a/R/test.R b/temp/test.R similarity index 100% rename from R/test.R rename to temp/test.R diff --git a/R/test.rcg b/temp/test.rcg similarity index 100% rename from R/test.rcg rename to temp/test.rcg diff --git a/data/test.rcl b/temp/test.rcl similarity index 100% rename from data/test.rcl rename to temp/test.rcl diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000000000000000000000000000000000000..69b5a9338cf503e9201b465f8967ec6d2ed0cdfe --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/tests.html +# * https://testthat.r-lib.org/reference/test_package.html#special-files + +library(testthat) +library(socceR) + +test_check("socceR") diff --git a/data/20220405162804-HELIOS_base_3-vs-enemy_2.rcg b/tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcg similarity index 100% rename from data/20220405162804-HELIOS_base_3-vs-enemy_2.rcg rename to tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcg diff --git a/data/20220405162804-HELIOS_base_3-vs-enemy_2.rcl b/tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcl similarity index 100% rename from data/20220405162804-HELIOS_base_3-vs-enemy_2.rcl rename to tests/testthat/20220405162804-HELIOS_base_3-vs-enemy_2.rcl diff --git a/tests/testthat/not_log_file.txt b/tests/testthat/not_log_file.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/testthat/test-read-log.R b/tests/testthat/test-read-log.R new file mode 100644 index 0000000000000000000000000000000000000000..4c694d3294015a4520413202f91d7aaf4ee1d8e1 --- /dev/null +++ b/tests/testthat/test-read-log.R @@ -0,0 +1,22 @@ +test_that("Read rcl file without error", { + expect_no_error(read_log(test_path("20220405162804-HELIOS_base_3-vs-enemy_2.rcl"))) +}) + +test_that("Read rcg file without error", { + expect_no_error(read_log(test_path("20220405162804-HELIOS_base_3-vs-enemy_2.rcg"))) +}) + +test_that("All items in rcg file must be retained", { + rcg <- read_log(test_path("20220405162804-HELIOS_base_3-vs-enemy_2.rcg")) + expect_true("header" %in% names(rcg)) + expect_true("server_param" %in% names(rcg)) + expect_true("player_param" %in% names(rcg)) + expect_true("player_type" %in% names(rcg)) + expect_true("team" %in% names(rcg)) + expect_true("show" %in% names(rcg)) + expect_true("msg" %in% names(rcg)) +}) + +test_that("Do not read file that is not log file", { + expect_error(read_log(test_path("not_log_file.txt"))) +})