内容简介: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!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Web编程从入门到实践
徐林林 / 清华大学出版社 / 2010-3 / 59.80元
《Java Web编程从入门到实践》内容简介:Java Web开发是目前最流行的网络开发技术之一。《Java Web编程从入门到实践》由浅入深,结合大量的实例系统地讲解了关于Java Web开发方面的知识。全书内容包括Java Web开发的基础知识、Java Web开发环境的搭建、JSP技术详解、Servlet技术详解、JSP+Servlet+JavaBean开发模式、JDBC接口的使用方法、Hi......一起来看看 《Java Web编程从入门到实践》 这本书的介绍吧!
正则表达式在线测试
正则表达式在线测试
HEX HSV 转换工具
HEX HSV 互换工具