# Building a command line tool to design a farm layout in Stardew Valley

By John Lekberg on February 26, 2020.

This week's post will cover a command line tool that helps you play the video game Stardew Valley.

Stardew Valley is a farming game (like Harvest Moon). You can manually water your crops, or you can use sprinklers to automate the process.

I wrote a Python script, `sprinkler-layout`

, that designs a layout of
sprinklers for me, for a given number of sprinklers (e.g. 10 sprinklers).
The goals of the layout are:

- water as much land as possible.
- have a reasonably small perimeter.

# Script source code

sprinkler-layout

```
#!/usr/bin/env python3
import itertools
class Layout:
"""A layout of sprinklers on a grid."""
def __init__(self):
self._sprinklers = set()
self._watered_squares = set()
@classmethod
def generate(cls, *, num_sprinklers, coordinates):
"""Generate a layout, given:
- how many sprinklers to place.
- which positions to attempt to place them at.
"""
layout = cls()
while layout.count_sprinklers() < num_sprinklers:
position = next(coordinates)
if layout.is_open(position):
layout.add_sprinkler(position)
return layout
def count_sprinklers(self):
"""The current number of placed sprinklers."""
return len(self._sprinklers)
def _watering_positions(self, sprinkler_position):
"""Generate positions watered by a sprinkler at a
given position."""
x, y = sprinkler_position
yield x - 1, y
yield x + 1, y
yield x, y - 1
yield x, y + 1
def is_open(self, position):
"""Check if a position is open for placing a
sprinkler.
A position is open if
- the set of the position and its watered squares
does not intersect with
- the set of already placed sprinklers and their
watered squares.
"""
new_positions = {position, *self._watering_positions(position)}
return not (
new_positions & (self._sprinklers | self._watered_squares)
)
def add_sprinkler(self, position):
"""Add a sprinkler at a position."""
self._sprinklers.add(position)
self._watered_squares.update(
self._watering_positions(position)
)
def print_report(self):
"""Print out a report of the current layout.
The report includes:
- The dimensions of the layout.
- The materials cost of the layout.
- A visualization of the layout.
"""
squares = self._sprinklers | self._watered_squares
X = [x for x, _ in squares]
Y = [y for _, y in squares]
span = lambda Z: range(min(Z), max(Z) + 1)
grid = [
[
"#" if (x, y) in self._sprinklers else "."
for x in span(X)
]
for y in span(Y)
]
width = len(span(X)) + 2
height = len(span(Y)) + 2
print(len(self._sprinklers), "sprinklers")
print(len(self._watered_squares), "watered squares")
print(width, "x", height, "squares, including perimeter wall")
print(2 * (width + height), "square perimeter")
block = 3
print(f"map of sprinklers ({block} by {block} blocks)")
for i, row in enumerate(grid):
if i % block == 0:
print()
for j, square in enumerate(row):
if j % block == 0:
print(end=" ")
print(square, end="")
print()
print()
def spiral_coordinates():
"""Generate positions along a spiral.
The first nine steps of the spiral look like this
7 6 5 | v < <
8 1 4 | v v ^
9 2 3 | v > ^
"""
yield 0, 0
for radius in itertools.count(start=1):
x, y = 1 - radius, radius
while x < radius:
yield x, y
x += 1
while y > -radius:
yield x, y
y -= 1
while x > -radius:
yield x, y
x -= 1
while y < radius:
yield x, y
y += 1
yield x, y
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description=
"generate a layout of sprinklers for Stardew Valley."
)
parser.add_argument(
"--sprinklers",
type=int,
required=True,
metavar="N",
help="the number of sprinklers to place",
)
args = parser.parse_args()
layout = Layout.generate(
num_sprinklers=args.sprinklers,
coordinates=spiral_coordinates()
)
layout.print_report()
```

`$ sprinkler-layout --help`

```
usage: sprinkler-layout [-h] --sprinklers N
generate a layout of sprinklers for Stardew Valley.
optional arguments:
-h, --help show this help message and exit
--sprinklers N the number of sprinklers to place
```

# Using the script to design a layout

I'm starting a new farming season in Stardew Valley and I have 25 sprinklers
available.
I use `sprinkler-layout`

to design a layout:

`$ sprinkler-layout --sprinklers 25`

```
25 sprinklers
100 watered squares
16 x 17 squares, including perimeter wall
66 square perimeter
map of sprinklers (3 by 3 blocks)
... ... ... ... ..
... ... ... ... #.
..# ..# ... ... ..
... ... .#. ..# ..
... #.. ... #.. ..
.#. ..# ... ... ..
... ... ..# ..# ..
... #.. #.. ... ..
.#. ... ... .#. ..
... ..# ..# ... ..
... #.. ... ... #.
... ... #.. #.. ..
..# ... ... ..# ..
... .#. .#. ... ..
... ... ... ... ..
```

Then:

- I construct the required materials (66 squares of walls for the perimeter).
- I clear out enough space for the layout (a grid of 16 by 17 squares).
- I place sprinklers as shown in the layout (
`#`

). - I place the walls around the perimeter.

# How the script works

I use a custom class, `Layout`

, to represent a sprinkler layout.
`Layout`

manages the internal state of:

- Where sprinklers have been placed.
- Which positions are watered by the placed sprinklers.

`Layout`

has a class method, `generate`

, that
attempts to position sprinklers by choosing from given positions.
`generate`

uses a greedy strategy to place the sprinklers:

- Loop until I have placed enough sprinklers:
- Get the next position to try.
- If I can place a sprinkler at this position, do it.

I check if I can place a sprinkler by using sets of coordinates and checking that these sets are disjoint:

- the set of the new sprinkler and its watered squares.
- the set of already placed sprinklers and their watered squares.

I have a generator function, `spiral_coordinates`

, the generates positions in a
spiral that looks like this: (starting from the center)

```
v < < < < < <
v v < < < < ^
v v v < < ^ ^
v v v v ^ ^ ^
v v v > ^ ^ ^
v v > > > ^ ^
v > > > > > ^
> ...
```

I use this technique because it designs good enough layouts for me.
`spiral_coordinates`

is simple to implement and keeps the overall perimeter of
the layout small.

The report function, `print_report`

, computes a bounding box that encloses:

- the sprinklers that have been placed.
- the squares that are watered by the placed sprinklers.

Then, I take into account a 1 square thick perimeter wall and report:

- The dimensions of the bounding box.
- The perimeter of the bounding box.

The report generates a map of the placed sprinklers and partitions it into chunks:

```
... ... ... ... ..
... ... ... ... #.
..# ..# ... ... ..
... ... .#. ..# ..
... #.. ... #.. ..
.#. ..# ... ... ..
... ... ..# ..# ..
... #.. #.. ... ..
.#. ... ... .#. ..
... ..# ..# ... ..
... #.. ... ... #.
... ... #.. #.. ..
..# ... ... ..# ..
... .#. .#. ... ..
... ... ... ... ..
```

I find the map harder to read without the partitioning:

```
..............
............#.
..#..#........
.......#...#..
...#.....#....
.#...#........
........#..#..
...#..#.......
.#........#...
.....#..#.....
...#........#.
......#..#....
..#........#..
....#..#......
..............
```

# In conclusion...

This week's post covered a Python script that assists people playing Stardew Valley by designing a layout of sprinklers. You learned about:

- Using Python classes to manage internal state.
- Using Python sets to check if two sets of positions are disjoint.
- Using a simple greedy strategy to make decisions (placing the sprinklers).

My challenge to you:

Create a different way to generate coordinates than

`spiral_coordinates`

.For example, here's what a placement of 8 sprinklers looks like with

`spiral_coordinates`

:`Layout.generate( num_sprinklers = 8, coordinates = spiral_coordinates() ).print_report()`

`8 sprinklers 32 watered squares 11 x 10 squares, including perimeter wall 42 square perimeter map of sprinklers (3 by 3 blocks) ... ... ... .#. ... .#. ... #.. ... ... ... #.. .#. .#. ... ... ... ... ... #.. #.. ... ... ...`

And here's a placement of 8 sprinklers that tries positions only in a horizontal line:

`from itertools import count Layout.generate( num_sprinklers = 8, coordinates = ((i, 0) for i in count()) ).print_report()`

`8 sprinklers 32 watered squares 26 x 5 squares, including perimeter wall 62 square perimeter map of sprinklers (3 by 3 blocks) ... ... ... ... ... ... ... ... .#. .#. .#. .#. .#. .#. .#. .#. ... ... ... ... ... ... ... ...`

If you enjoyed this week's post, share it with your friends and stay tuned for next week's post. See you then!