内容简介:By John Lekberg on February 26, 2020.This week's post will cover a command line tool that helps you play the video gameStardew Valley is a farming game (like
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:
#
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!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Starfish and the Spider
Ori Brafman、Rod A. Beckstrom / Portfolio Hardcover / 2006-10-05 / USD 24.95
Understanding the amazing force that links some of today's most successful companies If you cut off a spider's leg, it's crippled; if you cut off its head, it dies. But if you cut off a st......一起来看看 《The Starfish and the Spider》 这本书的介绍吧!