Darts in Bash: Complete Solution & Deep Dive Guide

a close-up of a pool table

Master Bash Scripting with the Darts Game: From Zero to Hero

Learn to solve the Darts scoring challenge in Bash by calculating the distance from the center of a target using the Pythagorean theorem. This guide covers scripting logic, floating-point arithmetic with bc, and conditional statements to assign points based on where a dart lands.


You've probably spent hours in the terminal, mastering commands and automating tasks. But have you ever tried to use Bash, the humble command-line shell, to solve a problem rooted in geometry and game logic? It sounds like a task for a "real" programming language like Python or Java, right? Many developers hit a wall when they need to perform even simple floating-point math in Bash, quickly abandoning it for something else.

This is where the real learning begins. By pushing a tool beyond its apparent limits, you uncover its true power and flexibility. This guide will walk you through solving the Darts scoring challenge, a classic problem from the kodikra.com learning path. We won't just give you the answer; we'll deconstruct the geometric principles, navigate Bash's mathematical limitations using powerful command-line utilities, and build a robust, elegant script from scratch. Prepare to see Bash in a whole new light.


What is the Darts Scoring Challenge?

The Darts challenge is a fantastic exercise designed to test your understanding of coordinate geometry, mathematical calculations, and conditional logic within a scripting environment. The premise is simple: you are given the Cartesian coordinates (x, y) of a dart's landing position on a 2D plane, and you must calculate the points awarded based on its distance from the center of the target (the origin, at (0,0)).

The scoring is determined by concentric circles, each with a specific radius:

  • Bullseye (Inner Circle): If the dart lands within a radius of 1 unit (inclusive), the player scores 10 points.
  • Middle Circle: If the dart lands outside the bullseye but within a radius of 5 units (inclusive), the player scores 5 points.
  • Outer Circle: If the dart lands outside the middle circle but within a radius of 10 units (inclusive), the player scores 1 point.
  • Miss (Outside the Target): If the dart lands outside the outer circle (a radius greater than 10 units), the player scores 0 points.

Our goal is to write a Bash script that accepts two arguments, x and y, and prints the corresponding score to the standard output. For example, a perfect bullseye at (0, 0) should yield 10 points, while a landing at (10, 2) would be a miss, yielding 0 points.


Why Use Bash for a Geometric Problem?

At first glance, Bash seems like an odd choice for this task. It's primarily a shell, a command language interpreter, designed for file manipulation, process control, and string processing. It famously lacks built-in support for floating-point arithmetic, a critical requirement for calculating distances accurately.

However, tackling this problem in Bash provides several invaluable learning opportunities that are central to becoming a proficient system administrator, DevOps engineer, or power user:

  • Mastering Core Scripting Fundamentals: The solution requires robust input validation (checking command-line arguments), variable assignment, and clear conditional logic (if/elif/else), which are the bedrock of any useful script.
  • Embracing the UNIX Philosophy: This problem forces you to think in terms of the UNIX philosophy: "Write programs that do one thing and do it well. Write programs to work together." Since Bash can't handle float math, we learn to delegate that specific task to another specialized tool, bc, and pipe the output back into our script. This composition of small, sharp tools is the essence of command-line power.
  • Problem-Solving Under Constraints: Working within limitations fosters creativity. By solving this in Bash, you learn how to find workarounds and leverage the full ecosystem of command-line utilities available in a POSIX-compliant environment. This skill is far more valuable than simply knowing the syntax of one "perfect" language.
  • Practical Application: While you might not be scripting dart games in a production environment, the underlying patterns are common. You might need to parse log files with coordinate data, calculate resource usage percentages, or process performance metrics—all of which can involve floating-point numbers and conditional logic.

This exercise from the kodikra.com Bash learning roadmap isn't just about finding a score; it's about learning how to orchestrate multiple command-line tools to build a solution greater than the sum of its parts.


How to Calculate the Dart's Position and Score?

The solution hinges on a single, fundamental mathematical concept: calculating the straight-line distance between two points on a Cartesian plane. Since the dartboard's center is at the origin (0,0), we just need to find the distance from the origin to the dart's landing point (x,y).

The Core Concept: The Pythagorean Theorem

The distance formula is a direct application of the Pythagorean theorem, which states that in a right-angled triangle, the square of the hypotenuse (the side opposite the right angle, c) is equal to the sum of the squares of the other two sides (a and b).

Formula: a² + b² = c²

When we plot a point (x,y), the x value represents the horizontal distance from the origin, and the y value represents the vertical distance. These two distances form the two shorter sides of a right-angled triangle. The hypotenuse is the direct distance (let's call it d) from the origin to the point (x,y).

Therefore, we can adapt the formula:

x² + y² = d²

To find the distance d, we simply take the square root of both sides:

d = sqrt(x² + y²)

This formula is the heart of our script. Once we calculate d, the rest is just a matter of comparing it to the radii of the scoring circles (1, 5, and 10).

Navigating Bash's Mathematical Limitation with bc

If you try to perform this calculation in Bash directly, you'll run into trouble. Bash's arithmetic expansion $((...)) only handles integers.

# This will fail or give an incorrect integer result in Bash
x=0.5
y=0.5
# The following line will throw an error
distance=$(( sqrt(x*x + y*y) ))

To solve this, we use bc, the "basic calculator" (which is actually an arbitrary-precision calculator language). When invoked with the -l flag, it loads a standard math library, giving us access to functions like sqrt().

We can construct a string containing our mathematical expression and pipe it into bc for evaluation:

x=-3.5
y=3.5

# The 'scale' variable sets the number of decimal places for precision
distance=$(echo "scale=10; sqrt($x^2 + $y^2)" | bc -l)

echo "The distance is: $distance"
# Output: The distance is: 4.9497474683

This command is the engine of our solution. It takes the x and y values, builds the formula as a string, and lets bc do the heavy lifting.

The Logical Flow

Our script will follow a clear, sequential process. This can be visualized as a decision tree, where each step narrows down the possibilities until a final score is determined.

  ● Start
  │
  ▼
┌────────────────┐
│ Get (x, y)     │
│ coordinates    │
└───────┬────────┘
        │
        ▼
╭───────────────────╮
│ Calculate distance: │
│ d = sqrt(x² + y²) │
╰─────────┬─────────╯
          │
          ▼
    ◆ d <= 1 ?
   ╱          ╲
 Yes           No
 ╱              ╲
▼                ▼
[10 pts]       ◆ d <= 5 ?
               ╱          ╲
             Yes           No
             ╱              ╲
            ▼                ▼
          [5 pts]         ◆ d <= 10 ?
                           ╱          ╲
                         Yes           No
                         ╱              ╲
                        ▼                ▼
                      [1 pt]           [0 pts]
                        │                │
                        └───────┬────────┘
                                │
                                ▼
                          ┌────────────┐
                          │ Output Score │
                          └────────────┘
                                │
                                ▼
                                ● End

This flow diagram illustrates the logic perfectly. We perform the single most complex operation (the distance calculation) once at the beginning. Then, we use a series of simple comparisons to determine the score. It's crucial that these comparisons happen in order from the smallest radius to the largest to ensure correctness. If we checked for d <= 10 first, a bullseye would incorrectly be scored as 1 point.


Where is this Logic Implemented? The Complete Bash Script

Now, let's assemble all these concepts into a final, production-quality Bash script. This solution is well-commented, includes robust error handling for incorrect input, and clearly implements the logic we've discussed.

The Solution Code

#!/usr/bin/env bash

# Darts Scoring Script from the kodikra.com learning path
#
# This script calculates the score for a dart throw given its x and y coordinates.
# It uses the Pythagorean theorem to find the distance from the origin (0,0)
# and assigns points based on predefined scoring circles.

# --- Main Execution Block ---
main() {
  # Input validation: Ensure exactly two arguments are provided.
  if [[ $# -ne 2 ]]; then
    echo "Usage: $0 <x-coordinate> <y-coordinate>" >&2
    exit 1
  fi

  # Assign command-line arguments to variables for clarity.
  local x="$1"
  local y="$2"

  # Regular expression to validate if input is a valid number (integer or float).
  local number_regex='^-?[0-9]+([.][0-9]+)?$'
  if ! [[ $x =~ $number_regex ]] || ! [[ $y =~ $number_regex ]]; then
    echo "Error: Both coordinates must be numeric." >&2
    exit 1
  fi

  # Calculate the square of the distance (x^2 + y^2).
  # We calculate the square of the distance first to potentially compare against
  # the square of the radii, which can sometimes avoid floating point square roots.
  # However, for clarity and directness, we will proceed with the square root.
  local distance_squared
  distance_squared=$(echo "$x^2 + $y^2" | bc -l)

  # Calculate the actual distance from the origin using the square root.
  # 'bc -l' is used for floating-point arithmetic and access to the math library (sqrt).
  # 'scale=10' sets the precision to 10 decimal places to avoid rounding errors.
  local distance
  distance=$(echo "scale=10; sqrt($distance_squared)" | bc -l)

  # Conditional logic to determine the score.
  # The comparisons must be ordered from the smallest radius to the largest.
  # We delegate the floating-point comparison back to 'bc'.
  # 'bc' will output '1' for true and '0' for false.
  if (( $(echo "$distance <= 1" | bc -l) )); then
    echo 10
  elif (( $(echo "$distance <= 5" | bc -l) )); then
    echo 5
  elif (( $(echo "$distance <= 10" | bc -l) )); then
    echo 1
  else
    echo 0
  fi
}

# Pass all script arguments to the main function.
# This makes the script testable and follows best practices.
main "$@"

Step-by-Step Code Walkthrough

Let's dissect the script piece by piece to understand its inner workings.

1. Shebang and Script Purpose

#!/usr/bin/env bash

The script starts with a shebang, telling the system to execute it using the bash interpreter found in the user's environment path. This is more portable than hardcoding /bin/bash. The initial comments explain the script's purpose, a crucial practice for maintainability.

2. The main Function Wrapper

main() {
  # ... script logic ...
}

main "$@"

All the logic is wrapped in a main function. This is a best practice in Bash scripting that improves readability, prevents global variable pollution, and makes it easier to test individual functions. The final line, main "$@", executes the function, passing along all command-line arguments ($@) that were given to the script.

3. Input Validation

if [[ $# -ne 2 ]]; then
  echo "Usage: $0 <x-coordinate> <y-coordinate>" >&2
  exit 1
fi

local number_regex='^-?[0-9]+([.][0-9]+)?$'
if ! [[ $x =~ $number_regex ]] || ! [[ $y =~ $number_regex ]]; then
  echo "Error: Both coordinates must be numeric." >&2
  exit 1
fi

This is the guard clause of our script. Before any calculations, it checks two things:

  • $# -ne 2: Checks if the number of arguments ($#) is not equal to 2. If the user provides too few or too many arguments, it prints a helpful usage message to standard error (>&2) and exits with a non-zero status code to indicate failure.
  • ! [[ $x =~ $number_regex ]]: It then uses a regular expression to ensure both inputs are valid numbers. This prevents errors if a user inputs text like "hello".

4. The Core Calculation

local x="$1"
local y="$2"

local distance
distance=$(echo "scale=10; sqrt($x^2 + $y^2)" | bc -l)

Here, we assign the first and second arguments to descriptive local variables, x and y. Then, we execute the key command. The string "scale=10; sqrt($x^2 + $y^2)" is constructed and piped to bc -l. The result of this calculation is captured and stored in the distance variable using command substitution $(...).

5. Conditional Scoring Logic

if (( $(echo "$distance <= 1" | bc -l) )); then
  echo 10
elif (( $(echo "$distance <= 5" | bc -l) )); then
  echo 5
elif (( $(echo "$distance <= 10" | bc -l) )); then
  echo 1
else
  echo 0
fi

This is the brain of the scoring operation. Since Bash's [[ ... ]] or (( ... )) constructs can't reliably compare floating-point numbers, we delegate the comparison itself back to bc.

  • echo "$distance <= 1" | bc -l: This command will output 1 if the condition is true, and 0 if it's false.
  • if (( ... )): The arithmetic evaluation construct ((...)) treats a result of 1 as true (and exit code 0) and 0 as false (and exit code 1). This provides a robust and reliable way to perform float comparisons in a shell script.
The if/elif/else structure ensures that only one block is executed, in the correct priority order (bullseye first, then middle ring, then outer ring).

How to Run the Script

1. Save the code above into a file named darts.sh. 2. Make the script executable from your terminal:

chmod +x darts.sh

3. Run it with different coordinates:

# Bullseye
$ ./darts.sh 0.5 -0.5
10

# Middle ring
$ ./darts.sh -3.5 3.5
5

# Outer ring
$ ./darts.sh 0 10
1

# A miss
$ ./darts.sh -9 9
0

# Invalid input
$ ./darts.sh 10
Usage: ./darts.sh <x-coordinate> <y-coordinate>

$ ./darts.sh foo bar
Error: Both coordinates must be numeric.

When Should You Consider Alternative Approaches?

While our Bash and bc solution is elegant and educational, it's essential to understand its context and limitations. For this specific, simple problem, it's perfectly adequate. However, in a high-performance or more complex computational environment, other tools might be more suitable.

Alternative 1: Using awk

awk is a powerful text-processing utility that also happens to be a full-fledged programming language with built-in support for floating-point arithmetic. An awk-based solution can be more concise and potentially faster because it doesn't require launching a separate process (bc) for each calculation and comparison.

#!/usr/bin/env bash

# Darts solution using awk for self-contained floating-point math

if [[ $# -ne 2 ]]; then
  echo "Usage: $0 <x-coordinate> <y-coordinate>" >&2
  exit 1
fi

# Pass the coordinates to an awk script
echo "$1 $2" | awk '{
  x = $1
  y = $2
  distance = sqrt(x*x + y*y)

  if (distance <= 1) {
    print 10
  } else if (distance <= 5) {
    print 5
  } else if (distance <= 10) {
    print 1
  } else {
    print 0
  }
}'

Here, the Bash script acts as a simple wrapper to validate input and pipe it to a short, self-contained awk program that handles all the logic internally.

Alternative 2: Using a General-Purpose Language (e.g., Python)

For applications involving heavy mathematics, complex data structures, or extensive libraries, a language like Python is the superior choice. The logic is more direct, and the code is often easier for developers from other backgrounds to read.

import sys
import math

def get_dart_score(x, y):
    distance = math.sqrt(x**2 + y**2)
    if distance <= 1:
        return 10
    elif distance <= 5:
        return 5
    elif distance <= 10:
        return 1
    else:
        return 0

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <x> <y>", file=sys.stderr)
        sys.exit(1)
    
    try:
        x_coord = float(sys.argv[1])
        y_coord = float(sys.argv[2])
        print(get_dart_score(x_coord, y_coord))
    except ValueError:
        print("Error: Coordinates must be numeric.", file=sys.stderr)
        sys.exit(1)

Pros and Cons of the Bash + bc Approach

Aspect Pros Cons
Portability Extremely high. Bash and bc are available on virtually every Linux, macOS, and UNIX-like system by default. Slightly less portable than pure POSIX shell, but still ubiquitous.
Performance Adequate for single executions and simple scripts. High overhead. Each call to bc forks a new process, which is slow in a loop or for large-scale data processing.
Readability The logic is clear, but the syntax for piping to bc for comparisons can be verbose for newcomers. Can become complex and hard to read with more intricate calculations.
Dependencies Relies on bc, an external program. While standard, bc is technically an external dependency. A minimal Docker container might not include it.
Learning Value Excellent for learning the UNIX philosophy of composing small tools to solve bigger problems. Does not teach native floating-point handling, which is a key concept in most other programming languages.

This comparison highlights that the "best" tool always depends on the context: the scale of the problem, the environment it will run in, and the primary goal (e.g., learning vs. raw performance).

Approach 1: Bash + bc (Process Composition)
┌──────────────┐      ┌──────────┐      ┌───────────────┐
│ Bash Script  ├─────→│ bc (math)│←─────┤ Bash Script   │
│(Orchestrator)│      └──────────┘      │(Conditionals) │
└───────┬──────┘                        └───────┬───────┘
        │                                       │
        └─────────────────┬─────────────────────┘
                          │
                          ▼
                      [Score]

Approach 2: awk (Self-Contained)
┌──────────────────────────────────┐
│ awk Script                       │
│   - Reads input                  │
│   - Performs float math (sqrt)   │
│   - Handles conditionals         │
└─────────────────┬────────────────┘
                  │
                  ▼
              [Score]

Frequently Asked Questions (FAQ)

1. Why can't Bash handle floating-point numbers directly?
Bash was designed primarily as a command interpreter and for string/file manipulation. Its arithmetic capabilities were built for simple integer operations common in scripting, like counting loops, checking file sizes, or calculating exit codes. Full IEEE 754 floating-point support would add significant complexity to the shell's parser and internals, which goes against its core design philosophy.
2. What is bc and why is the -l flag important?
bc stands for "basic calculator," but it's an arbitrary-precision numeric processing language. By default, it works with integers. The -l flag is crucial because it pre-loads a standard math library, which gives you access to functions like sqrt() (square root), s() (sine), c() (cosine), and others. It also sets the default scale (number of decimal places) to 20, ensuring high precision.
3. How can I make the script handle non-numeric input more gracefully?
Our script includes a regular expression check: if ! [[ $x =~ $number_regex ]]. This is a robust way to validate input before passing it to bc. It checks if the input string starts with an optional minus sign, followed by one or more digits, and then an optional decimal part. This prevents bc from throwing its own syntax errors on non-numeric input like "hello".
4. Is the Pythagorean theorem the only way to solve this?
Yes, for calculating the Euclidean distance on a 2D Cartesian plane, the Pythagorean theorem (or its derivative, the distance formula) is the standard and most direct method. An interesting optimization, not used in our final script for clarity, is to compare the square of the distance (x² + y²) with the square of the radii (1, 25, 100). This avoids the computationally more expensive sqrt() operation entirely.
5. What does scale=10 do in the bc command?
The special variable scale in bc determines the number of digits that appear after the decimal point in your results. We set it to 10 to ensure high precision for the distance calculation, which prevents subtle rounding errors that could lead to an incorrect score for darts landing exactly on the edge of a circle.
6. Could this logic be adapted for a 3D target?
Absolutely! The Pythagorean theorem extends naturally to three dimensions. For a point (x, y, z), the distance d from the origin is d = sqrt(x² + y² + z²). You would simply modify the script to accept three coordinate arguments and update the calculation string passed to bc. The conditional scoring logic would remain the same, comparing the 3D distance to spherical scoring shells.
7. Where can I learn more advanced Bash scripting?
This exercise is a great stepping stone. To continue your journey from scripting basics to advanced automation and tool-building, you can explore our comprehensive guides and tutorials. For more in-depth material, check out our complete guide to Bash scripting, which covers topics from process management to advanced function design.

Conclusion: More Than Just a Game

Successfully scripting the Darts game challenge in Bash is a significant milestone. It proves that you can move beyond simple commands and begin to solve complex, logic-based problems by creatively combining the tools at your disposal. You've validated user input, orchestrated a separate process (bc) for specialized calculations, captured its output, and used robust conditional logic to make decisions—all core competencies of an effective scripter.

What you've learned here—delegating tasks, handling data pipelines, and working within constraints—is directly applicable to real-world DevOps, system administration, and data processing tasks. The next time you face a problem that seems ill-suited for a shell script, remember this exercise and the power of the UNIX philosophy.

Disclaimer: The solution provided has been tested and verified on Bash version 5.x and with GNU bc 1.07.x. While it uses highly portable POSIX-compliant features, behavior may vary slightly on older or non-standard shell environments.


Published by Kodikra — Your trusted Bash learning resource.