diff --git a/.gitignore b/.gitignore index 4af5b64..3bfb2e9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ SPL-Rules.pdf SPL-Challenges.pdf +SPL-Leaderboards.pdf diff --git a/SPL-Leaderboards.tex b/SPL-Leaderboards.tex new file mode 100644 index 0000000..79c595f --- /dev/null +++ b/SPL-Leaderboards.tex @@ -0,0 +1,131 @@ +% !TeX spellcheck = en_US +\documentclass[12pt]{article} + +\usepackage{times,fullpage,xspace,fancyhdr,url,color} +\usepackage[pdftex]{graphicx} +\usepackage[pdftex, + colorlinks=true, + urlcolor=black, + linkcolor=black, + citecolor=black, + bookmarksopen=false, + bookmarksnumbered=true, + pdfstartview=FitH]{hyperref} + +\pdfcompresslevel=9 +\newcommand{\leaguename}{RoboCup Standard Platform League (NAO) } +\hypersetup{ + pdftitle={\leaguename Challenge Leaderboards}, + pdfauthor={Technical Committee SPL}, +} +\usepackage{microtype} +\usepackage[utf8]{inputenc} +\usepackage{amsmath} +\usepackage{xargs} +\usepackage[colorinlistoftodos,prependcaption,textsize=tiny]{todonotes} +\usepackage{siunitx} +\usepackage[capitalize,noabbrev]{cleveref} +\usepackage[official]{eurosym} +\usepackage[useregional]{datetime2} +\usepackage{subcaption} +\usepackage{enumitem} +\usepackage{xcolor} +\DTMlangsetup[en-GB]{ord=raise,monthyearsep={,\space}} + +\newcommandx{\unsure}[2][1=]{\todo[linecolor=red,backgroundcolor=red!25,bordercolor=red,#1]{#2}} +\newcommandx{\change}[2][1=]{\todo[linecolor=blue,backgroundcolor=blue!25,bordercolor=blue,#1]{#2}} +\newcommandx{\info}[2][1=]{\todo[linecolor=green,backgroundcolor=green!25,bordercolor=green,#1]{#2}} +\newcommandx{\improvement}[2][1=]{\todo[linecolor=Plum,backgroundcolor=Plum!25,bordercolor=Plum,#1]{#2}} + +% comment 'disable' in to disable all the todo notes :) +\usepackage +[ +%disable +]{todonotes} + +\usepackage[theorems]{tcolorbox} +\newtcbtheorem[number within=section]{hintbox}{}% +{colback=red!10,colframe=red!45!black,fonttitle=\bfseries}{th} + +\include{common/spl_variables} +\include{common/spl_dates} + +\sloppy +\newcommand{\ie}{\mbox{i.\,e.}\xspace} +\newcommand{\eg}{\mbox{e.\,g.}\xspace} +%\newcommand{\cf}{\mbox{cf.}\xspace} +\newcommand{\cf}{see\xspace} +% \newcommand{\comment}[1]{\marginpar{\pdfannot width 4in height .5in depth 8pt {/Subtype /Text /Contents (#1)}}} +\newcommand{\inparagraph}[1]{\paragraph{#1\hspace{-1em} }} + + +% some colors +\definecolor{orange}{rgb}{1,0.5,0} +\definecolor{red}{rgb}{1,0,0} +\definecolor{green}{rgb}{0,1,0} + + +\title{\leaguename\\ Hall of Fame Leaderboards} +\author{RoboCup Technical Committee} +\date{(\RCYear leaderboard challenges, as of \today)} + +\setlength{\parindent}{0pt} +\setlength{\parskip}{12pt plus 6pt minus 3 pt} +\setcounter{tocdepth}{1} +\widowpenalty=10000 +\clubpenalty=10000 + +\pagestyle{fancy} +\lhead{} +\chead{} +\rhead{} +\lfoot{} +\cfoot{} +\rfoot{} + +\renewcommand{\headrulewidth}{0.4pt} +\renewcommand{\footrulewidth}{0.4pt} + +% needed to align an image and text correctly side by side +\newcommand{\imagebox}[1]{\raisebox{2ex}{\raisebox{-\height}{#1}}} + +\begin{document} + +\maketitle + +\begin{center} +Questions or comments on the leaderboard rules should be submitted via \url{https://github.com/RoboCup-SPL/Rules/issues}, to the \texttt{\#rule-book} channel on the SPL Discord server, or by mail to \url{rc-spl-tc@lists.robocup.org}. +\end{center} + +\newpage + +\tableofcontents +\setcounter{tocdepth}{3} + +\thispagestyle{fancy} + +\clearpage + +\cfoot{\thepage} +\setcounter{page}{1} + +\section{Introduction} +Starting in 2025, several leaderboards will be introduced to track and compare team performances across key skills +over multiple years. These leaderboards aim to motivate teams to improve in specific areas deemed valuable by the +league and to compile a record of the top-performing components and skills. By highlighting these individual skills, +the league encourages focused development in areas that contribute to overall team performance, and creates a showcase +of the best technical abilities in the league. + +\subsection{Code Publication} +Every team participating in a challenge must publish the corresponding code used in that competition according to Appendix A.7 of the SPL rule book, unless a specific challenge states otherwise. + +\subsection{Challenge Rules} +Each year, teams can choose to make an attempt at specific leaderboard challenges. The Technical Committee and Organizing +Committee will coordinate dedicated time slots at RoboCup for teams to complete three attempts at each leaderboard challenge. +Metrics from each attempt will be recorded and added to the appropriate leaderboard. Each leaderboard will be based +on unique metrics and procedures, detailed below. Teams should indicate their intention for a leaderboard attempt 2 weeks before +the first competition day. Scheduling attempts after this deadline may not be possible. + +\include{leaderboards/leaderboard_challenges} + +\end{document} diff --git a/common/spl_variables.tex b/common/spl_variables.tex index df66b53..d4fbbc3 100644 --- a/common/spl_variables.tex +++ b/common/spl_variables.tex @@ -22,4 +22,5 @@ \newcommand{\TeamMessageLimit}{1200} % Limit of number of packets available to one team during a game with two halves of 10 minutes. \newcommand{\TeamMessageLimitMinute}{60} % Limit for the average number of packets available to one team during a minute of gameplay. \newcommand{\MaxJerseyNumber}{20} % the highest allowed jersey number to wear by robot players -\newcommand{\MaxRobotsInFallback}{two} +\newcommand{\MaxTimePassingLeaderboard}{180} +\newcommand{\MaxRobotsInFallback}{two} \ No newline at end of file diff --git a/figs/leaderboards/control_leaderboard.pdf b/figs/leaderboards/control_leaderboard.pdf new file mode 100644 index 0000000..569a824 Binary files /dev/null and b/figs/leaderboards/control_leaderboard.pdf differ diff --git a/figs/leaderboards/generate_leaderboards.py b/figs/leaderboards/generate_leaderboards.py new file mode 100755 index 0000000..2a63ed0 --- /dev/null +++ b/figs/leaderboards/generate_leaderboards.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 + +# Usage: ./generate_leaderboards.py ../FieldGenerator/field_2020.json + +import json +import math +import cairo +import sys + +# read the field dimensions from the rules from a JSON file + +assert len(sys.argv) == 2 + +with open(sys.argv[1]) as f: + o = json.load(f) + +field_length = o['field']['length'] +field_width = o['field']['width'] +line_width = o['field']['lineWidth'] +penalty_mark_size = o['field']['penaltyMarkSize'] +goal_area_length = o['field']['goalAreaLength'] +goal_area_width = o['field']['goalAreaWidth'] +penalty_area_length = o['field']['penaltyAreaLength'] +penalty_area_width = o['field']['penaltyAreaWidth'] +penalty_mark_distance = o['field']['penaltyMarkDistance'] +center_circle_diameter = o['field']['centerCircleDiameter'] +border_strip_width = o['field']['borderStripWidth'] +goal_post_diameter = o['goal']['postDiameter'] +goal_height = o['goal']['height'] +goal_inner_width = o['goal']['innerWidth'] +goal_depth = o['goal']['depth'] + +# derive some often used constants from the field dimensions +x_border = field_length * 0.5 + border_strip_width +y_border = field_width * 0.5 + border_strip_width +x_goal_line = field_length * 0.5 +y_touchline = field_width * 0.5 +x_penalty_area = field_length * 0.5 - penalty_area_length +y_penalty_area = penalty_area_width * 0.5 +x_penalty_mark = field_length * 0.5 - penalty_mark_distance +x_goal_area = field_length * 0.5 - goal_area_length +y_goal_area = goal_area_width * 0.5 +line_width_2 = line_width * 0.5 +penalty_mark_size_2 = penalty_mark_size * 0.5 +center_circle_radius = center_circle_diameter * 0.5 +pi_2 = math.pi * 0.5 + +# SVG generation parameters +svg_fieldline_width = 0.05 +svg_dimensionline_width = 0.025 +svg_symmetryline_width = 0.01 +svg_font_size = 0.3 +svg_addnotes = False + +# outer lines +def draw_outer_lines(context): + context.move_to(-(x_goal_line), -(y_touchline)) + context.line_to((x_goal_line), -(y_touchline)) + context.line_to((x_goal_line), (y_touchline)) + context.line_to(-(x_goal_line), (y_touchline)) + context.close_path() + context.move_to(0, -(y_touchline)) + context.line_to(0, (y_touchline)) + +def draw_penalty_area(context, sign): + context.move_to(sign * (x_goal_line), -(y_penalty_area)) + context.line_to(sign * (x_penalty_area), -(y_penalty_area)) + context.line_to(sign * (x_penalty_area), (y_penalty_area)) + context.line_to(sign * (x_goal_line), (y_penalty_area)) + +def draw_goal_area(context, sign): + context.move_to(sign * (x_goal_line), -(y_goal_area)) + context.line_to(sign * (x_goal_area), -(y_goal_area)) + context.line_to(sign * (x_goal_area), (y_goal_area)) + context.line_to(sign * (x_goal_line), (y_goal_area)) + +def draw_center_circle(context): + context.move_to(-center_circle_radius, 0) + context.arc(0, 0, center_circle_radius, -math.pi, math.pi) + context.move_to(-penalty_mark_size_2, 0) + context.line_to(penalty_mark_size_2, 0) + +# penalty mark +def draw_penalty_mark(context, sign): + x_base = x_penalty_mark if sign else 0 + context.move_to(sign * (x_base - penalty_mark_size_2), 0) + context.line_to(sign * (x_base + penalty_mark_size_2), 0) + context.move_to(sign * (x_base), -penalty_mark_size_2) + context.line_to(sign * (x_base), penalty_mark_size_2) + +def draw_dimension_horizontal(context, x1, x2, y, height, bar=True, arrow=True, along_offset=-0.1, across_offset=-0.05, label=""): + if bar: + context.move_to(x1, y + height * 0.5) + context.line_to(x1, y - height * 0.5) + + context.move_to(x2, y + height * 0.5) + context.line_to(x2, y - height * 0.5) + + narrow = abs(x2 - x1) < 0.1 + if arrow: + context.move_to(x1 + height * (-0.5 if narrow else 0.5), y + height * 0.5) + context.line_to(x1, y) + context.line_to(x1 + height * (-0.5 if narrow else 0.5), y - height * 0.5) + + context.move_to(x2 - height * (-0.5 if narrow else 0.5), y + height * 0.5) + context.line_to(x2, y) + context.line_to(x2 - height * (-0.5 if narrow else 0.5), y - height * 0.5) + + if not narrow: + context.move_to(x1, y) + context.line_to(x2, y) + + context.move_to((((x1 + x2) * 0.5) if not narrow else (x2 + height * 0.5)) + along_offset, y + across_offset) + #context.show_text(label + str(round(abs(x2 - x1) * 1000))) + # context.show_text(label) + +def draw_dimension_vertical(context, y1, y2, x, width, bar=True, arrow=True, along_offset=0.1, across_offset=-0.05, label=""): + if bar: + context.move_to(x + width * 0.5, y1) + context.line_to(x - width * 0.5, y1) + + context.move_to(x + width * 0.5, y2) + context.line_to(x - width * 0.5, y2) + + narrow = abs(y2 - y1) < 0.3 + if arrow: + context.move_to(x + width * 0.5, y1 + width * (-0.5 if narrow else 0.5)) + context.line_to(x, y1) + context.line_to(x - width * 0.5, y1 + width * (-0.5 if narrow else 0.5)) + + context.move_to(x + width * 0.5, y2 - width * (-0.5 if narrow else 0.5)) + context.line_to(x, y2) + context.line_to(x - width * 0.5, y2 - width * (-0.5 if narrow else 0.5)) + + if not narrow: + context.move_to(x, y1) + context.line_to(x, y2) + + context.save() + context.move_to(x + across_offset, (((y1 + y2) * 0.5) if not narrow else (y1 - width * 0.5)) + along_offset) + context.rotate(-math.pi / 2) + #context.show_text(label + str(round(abs(y2 - y1) * 1000))) + # context.show_text(label) + context.restore() + +def draw_base_field(file_name): + ############################### + ## DRAW ENTIRE FIELD DRAWING ## + ############################### + + # additional padding to all four sides (in meters) + #padding = 0.5 + padding = 0.0 + + width_in_m = field_length + 2 * border_strip_width + 2 * padding + height_in_m = field_width + 2 * border_strip_width + 2 * padding + + width, height = 72 * 10 * (width_in_m / height_in_m), 72 * 10 + + surface = cairo.PDFSurface(file_name, width, height) + context = cairo.Context(surface) + + # fill the background with Green + context.set_source_rgb(0, 0.5, 0.125) + context.paint() + + # set the origin to the center of the field and scale to meters + context.translate(width / 2, height / 2) + context.scale(width / width_in_m, height / height_in_m) + context.set_line_width(svg_fieldline_width) + + # draw the contours of lines in White + context.set_source_rgb(1, 1, 1) + draw_outer_lines(context) + draw_center_circle(context) + draw_penalty_area(context, 1) + draw_penalty_area(context, -1) + draw_goal_area(context, 1) + draw_goal_area(context, -1) + draw_penalty_mark(context, 1) + draw_penalty_mark(context, -1) + context.stroke() + + return context + +def draw_dots(context, dots, colour): + for dot in dots: + context.new_path() + context.set_source_rgb(*colour) + context.arc(dot['x'], dot['y'], 0.1, 0, 2 * math.pi) + context.fill_preserve() + context.set_source_rgb(0, 0, 0) + context.set_line_width(0.03) + context.stroke() + if "label" in dot: + context.move_to(dot['x'] + dot['label_offset']['x'], dot['y'] + dot['label_offset']['y']) + context.show_text(dot["label"]) + +def generate_control_leaderboard(): + context = draw_base_field("control_leaderboard.pdf") + + # Draw active robot + active_robot = { + "x": x_penalty_mark, + "y": field_width/2 + 0.5 + } + draw_dots(context, [active_robot], (0,0,1)) + + # Draw obstacle robots + inactive_robots = [ + { + "x": field_length/2 - penalty_area_length, + "y": penalty_area_width/2, + "offset": -.5 + }, + { + "x": field_length/2 - goal_area_length, + "y": goal_area_width/2, + "offset": .5 + }, + { + "x": x_penalty_mark, + "y": 0, + "offset": -.5 + }, + { + "x": field_length/2 - goal_area_length, + "y": -goal_area_width/2, + "offset": .5 + } + ] + draw_dots(context,inactive_robots,(1,0,0)) + + # Draw goal + goal = { + "x": x_penalty_mark, + "y": -field_width/2 + } + + # Goal Line + context.set_source_rgb(0, 1, 0) + context.rectangle(-field_length/2 - line_width_2, -field_width/2 - line_width_2, field_length+.03, line_width+.03) + context.fill_preserve() + context.set_source_rgb(0, 0, 0) + context.set_line_width(0.03) + context.stroke() + + # Example Goal point + draw_dots(context, [goal], (0, 1, 0)) + + # Draw Ball + ball = { + "x": x_penalty_mark, + "y": field_width/2 + } + draw_dots(context, [ball], (1, 1, 0)) + + # Draw example path + path_points = [ball] + [ + {"x": robot["x"] + robot["offset"], "y": robot["y"]} for robot in inactive_robots + ] + [goal] + context.set_source_rgb(1, 0, 1) # Set color for path + context.set_line_width(0.05) + context.set_dash([0.15, 0.15]) + context.move_to(path_points[0]["x"], path_points[0]["y"]) + for i in range(1, len(path_points)): + context.line_to(path_points[i]["x"], path_points[i]["y"]) + context.stroke() + +def generate_kick_leaderboard(): + context = draw_base_field("kick_leaderboard.pdf") + active_robot = { + "x": field_length/2, + "y": 0 + } + draw_dots(context, [active_robot], (0,0,1)) + + ball = { + "x": field_length/2 - goal_area_length, + "y": 0 + } + draw_dots(context, [ball], (1, 0, 0)) + + goals = [ + { + "x": 0, + "y": 0, + "label": "A", + "label_offset": { + "x": -0.3, + "y": -0.3 + } + }, + { + "x": -x_penalty_mark, + "y": 0, + "label": "B", + "label_offset": { + "x": -0.3, + "y": -0.3 + } + }, + { + "x": -field_length/2, + "y": -field_width/2, + "label": "C", + "label_offset": { + "x": -0.3, + "y": -0.3 + } + } + ] + context.set_font_size(svg_font_size) + draw_dots(context, goals, (0,1,0)) + +def generate_passing_leaderboard(): + context = draw_base_field("passing_leaderboard.pdf") + active_robots = [ + { + "x": -x_penalty_mark, + "y": field_width/2 + }, + { + "x": -x_penalty_mark, + "y": -field_width/2 + } + ] + draw_dots(context, active_robots, (0, 0, 1)) + + ball = { + "x": -field_length/2 + penalty_area_length, + "y": penalty_area_width/2 + } + + draw_dots(context, [ball], (1,0,0)) + + + # Draw Rectangles + context.set_source_rgba(1, 0, 1, 0.5) + context.set_line_width(0.05) + context.move_to(-field_length/2, goal_inner_width/2) + context.line_to(+field_length/2, goal_inner_width/2) + context.line_to(+field_length/2, -goal_inner_width/2) + context.line_to(-field_length/2, -goal_inner_width/2) + context.line_to(-field_length/2, goal_inner_width/2) + context.fill() + + context.set_source_rgba(0, 1, 0, 0.5) + context.set_line_width(0.05) + context.move_to(field_length/2, penalty_area_width/2) + context.line_to(field_length/2 - penalty_area_length, penalty_area_width/2) + context.line_to(field_length/2 - penalty_area_length, -penalty_area_width/2) + context.line_to(field_length/2, -penalty_area_width/2) + context.line_to(field_length/2, penalty_area_width/2) + context.fill() + + context.stroke() + +def generate_walk_leaderboard(): + + context = draw_base_field("walk_leaderboard.pdf") + active_robot = { + "x": x_penalty_mark, + "y": field_width/2 + .5 + } + draw_dots(context, [active_robot], (0, 0, 1)) + + inactive_robots = [ + { + "x": field_length/2 - penalty_area_length, + "y": penalty_area_width/2, + "offset": -.5 + }, + { + "x": field_length/2 - goal_area_length, + "y": goal_area_width/2, + "offset": .5 + }, + { + "x": x_penalty_mark, + "y": 0, + "offset": -.5 + }, + { + "x": field_length/2 - goal_area_length, + "y": -goal_area_width/2, + "offset": .5 + } + ] + draw_dots(context, inactive_robots, (1, 0, 0)) + + goal = { + "x": x_penalty_mark, + "y": -field_width/2 + } + context.set_source_rgb(0, 1, 0) + context.rectangle(-field_length/2 - line_width_2, -field_width/2 - line_width_2, field_length+.03, line_width+.03) + context.fill_preserve() + context.set_source_rgb(0, 0, 0) + context.set_line_width(0.03) + context.stroke() + + draw_dots(context, [goal], (0, 1, 0)) + + path_points = [active_robot] + [ + {"x": robot["x"] + robot["offset"], "y": robot["y"]} for robot in inactive_robots + ] + [goal] + + context.set_source_rgb(1, 0, 1) + context.set_line_width(0.05) + context.set_dash([0.15, 0.15]) + context.move_to(path_points[0]["x"], path_points[0]["y"]) + for i in range(1, len(path_points)): + context.line_to(path_points[i]["x"], path_points[i]["y"]) + + context.stroke() + +def main(): + generate_control_leaderboard() + generate_kick_leaderboard() + generate_passing_leaderboard() + generate_walk_leaderboard() + +if __name__ == "__main__": + main() diff --git a/figs/leaderboards/kick_leaderboard.pdf b/figs/leaderboards/kick_leaderboard.pdf new file mode 100644 index 0000000..30afbb7 Binary files /dev/null and b/figs/leaderboards/kick_leaderboard.pdf differ diff --git a/figs/leaderboards/passing_leaderboard.pdf b/figs/leaderboards/passing_leaderboard.pdf new file mode 100644 index 0000000..4be6a6f Binary files /dev/null and b/figs/leaderboards/passing_leaderboard.pdf differ diff --git a/figs/leaderboards/walk_leaderboard.pdf b/figs/leaderboards/walk_leaderboard.pdf new file mode 100644 index 0000000..b2637a4 Binary files /dev/null and b/figs/leaderboards/walk_leaderboard.pdf differ diff --git a/leaderboards/leaderboard_challenges.tex b/leaderboards/leaderboard_challenges.tex new file mode 100644 index 0000000..dd859dc --- /dev/null +++ b/leaderboards/leaderboard_challenges.tex @@ -0,0 +1,158 @@ +% !TeX root = ../SPL-Leaderboards.tex +% !TeX spellcheck = en_US + +\section{Fastest Walk Leaderboard} +This leaderboard will measure how fast a NAO will be able to navigate along both a straight path and one with obstructions. + +\subsection{Setup} +This leaderboard challenge will take place on one half of a standard SPL field, and requires 1 active robot and 4 inactive robots. +Inactive robots will act as obstacles for half of the attempt and will be placed as follows (illustrated in \cref{fig:walk_leaderboard}): +\begin{itemize} + \item On the penalty mark + \item On both corners of the goal area that are not touching the field boundary + \item On the corner of the goal area closest to the competing robot's starting position +\end{itemize} + +The competing robot will be placed 50cm behind the touchline\footnote {Measured from the centre of the touchline to the front of the robots foot}, in line with the penalty mark. +The end goal is the touchline on the other side of the field. + +\begin{figure}[t] + \centerline{\includegraphics[width=\columnwidth]{figs/leaderboards/walk_leaderboard.pdf}} + \caption{Fastest Walk Leaderboard: Robot completing the attempt is shown in blue, target is shown in green, obstacles are shown in red. An example path is shown in magenta} + \label{fig:walk_leaderboard} +\end{figure} + +\subsection{Challenge Execution} +Each attempt will consist of two types of runs, one with obstacles and one without. +Participating teams can choose which order they wish to complete these runs. Code changes are not allowed during an attempt, +but are allowed between attempts. The participating robot must navigate autonomously and cannot be remote controlled during the challenge. + +\subsubsection{Obstacles} +Robots will be placed on the field as described above, the active robot will need walk from it's +starting position to the target touchline, while weaving around the obstacle robots. (see \cref{fig:walk_leaderboard} for an example) + +\subsubsection{Open Field} +Robot obstacles will be removed from the field and the active robot will just need to walk +from the starting position to the target touchline. + +\subsection{Scoring} +The head referee will start the timer once the robot touches the starting touchline and stops the timer once the target touchline is touched + +After all attempts, the shortest time to complete each type of run will be added together and recorded +on the leaderboard, times will be rounded to the nearest second. + +\section{Ball Control Leaderboard} +This leaderboard will measure how well a NAO can keep control of a ball, while moving it +to a target. + +\subsection{Setup} +This leaderboard challenge will take place on one half of a standard SPL field, and requires 1 active robot, 4 inactive robots and 1 SPL Soccer Ball. +Inactive robots will act as obstacles for the attempt and will be placed as follows (illustrated in \cref{fig:walk_leaderboard}): +\begin{itemize} + \item On the penalty mark + \item On both corners of the goal area that are not touching the field boundary + \item On the corner of the goal area closest to the competing robot +\end{itemize} +The ball is placed on the touchline in line with the penalty mark, with the competing +robot placed 50cm behind it.\footnote {Measured from the centre of the touchline to the front of the robots foot}. +The end goal is the touchline on the other side of the field. + +\begin{figure}[t] + \centerline{\includegraphics[width=\columnwidth]{figs/leaderboards/control_leaderboard.pdf}} + \caption{Ball Control Leaderboard: Robot completing the attempt is shown in blue, target is shown in green, obstacles are shown in red and the ball is shown in yellow. An example path is shown in magenta} + \label{fig:ball_control_leaderboard} +\end{figure} + +\subsection{Challenge Execution} +In each attempt, participating teams must start the robot behaviour of their competing robot via a single chest button +press. Participating robots will need to dribble the ball around the obstacles and across +the goal touchline in as fast a time as possible. + +Anytime the ball moves more than 75cm away from the competing robot or the ball comes in contact with +an obstacle, ``Penalty'' is called, and a 30 second penalty we be added to the teams score. + +\subsection{Scoring} +Time starts from when the chest button is pressed, to when to robot and ball entirely crosses the target touchline. + +The lowest time across all attempts will be recorded in the leaderboard, times will be rounded to the nearest second. + +\section{Best Kick Leaderboard} +This leaderboard will measure how far and how accurate a NAO will be able to kick a standard +SPL soccer ball. + +\subsection{Setup} +This leaderboard challenge will take place on a standard SPL field, and requires 1 active robot and 1 ball. +The ball will be placed on the centre of the edge of the goal area. +The competing robot will be placed in the centre of the goaline facing the ball +\begin{figure}[t] + \centerline{\includegraphics[width=\columnwidth]{figs/leaderboards/kick_leaderboard.pdf}} + \caption{Best Kick Leaderboard: Robot completing the attempt is shown in blue, targets are shown in green, ball placement is shown in red} + \label{fig:kick_leaderboard} +\end{figure} +\subsection{Challenge Execution} +Each attempt will consist of kicking towards each of the three targets (illustrated in \cref{fig:kick_leaderboard}): +\begin{itemize} + \item The centre mark in the centre circle + \item The penalty mark on the opposite side of the field + \item A corner on the opposite side of the field +\end{itemize} +In each attempt, for each kick, participating teams must start the robot behaviour of their competing robot via a single chest button +press. The robot then has \qty{\PenaltyShootoutKickTime}{\second} to kick the ball towards the current target. +The competing robot must not touch the ball a second time after the ball has clearly moved, +otherwise the attempt ends immediately without scoring. +Once the ball has come to a complete stop, the distance from the ball to the target is measured. + +Code changes are allowed when changing targets. +\subsection{Scoring} +After all attempts, the best score for each target will be added together and recorded in the leaderboard. +Distances are rounded to the closest half-centimetre. + +\section{Most Passes Leaderboard} +This leaderboard measures the highest number of successful passes a team can +complete in a given timeframe, emphasizing passing accuracy, speed, and coordination. + +\subsection{Setup} +This leaderboard challenge will take place on a standard SPL field and requires 2 competing robots, +a Game Controller and 1 standard SPL soccer ball. + +Competing robots will be placed on the touchlines on opposite sides of the same half of the field, +in line with the penalty mark. The ball will be placed on either corner of the penalty area. + +\begin{figure}[t] + \centerline{\includegraphics[width=\columnwidth]{figs/leaderboards/passing_leaderboard.pdf}} + \caption{Most Passes Leaderboard: Robots completing the attempt are shown in blue, target area is shown in green, exclusion area is shown in magents and ball placement is shown in red} + \label{fig:passing_leaderboard} +\end{figure} +\subsection{Challenge Execution} +In each attempt, a playing packet will be sent from the game controller to indicate the start of the attempt, +and will begin counting down the timer. +Teams will then have at most \qty{\MaxTimePassingLeaderboard}{\sec} to move the ball from the starting +position to the target area while completing as many passes as possible. The centre +1.5 metres of the field (Centre Cricle Diameter) is an exclusion zone, no robots are allowed within this area, +if a robot enters this area, or the ball comes to a complete stop +within this area, the run is ended immediately. If the ball is kicked off the field, +it is placed back on the touchline where it crossed. + +The ref will announce each time the ball crosses the exlusion zone, and whether this will +count as a successful pass. This is then captured by the Game Controller. + +Once a robot has made contact with the ball within the target area, the ref will announce that the +attempt has finished, then the finish signal will be sent from the game controller and the timer will stop. + + +\subsection{Scoring} +1 point is awarded every time the ball crosses the exlusion zone, +with an additional point awarded for a "successful pass". A "successful pass" is defined as when the +The ball stops in an 180◦ arc with radius 75cm in front of the receiving robot. + +No points are counted if the time runs out before a robot has posession of the ball +within the target area, the ball hasn't crossed the exclusion zone at least once +or if the run ends prematurely as per the above rules. + +A time bonus is calculated based on the points scored by passing and the percentage of time remaining, once the +challenge has ended. This is calculated using the following formula: +\[ +\text{Time Bonus Points} = \text{Base Points for Passes} \times \frac{\text{Remaining Time (seconds)}}{\text{Total Time (seconds)}} +\] + +The final score is the base points plus the time bonus points, rounded to 2 decimal places. The highest score of all attempts will be recorded in the leaderboard.