My Racetrack is just that. A Racetrack. You can't race it (yet) because I had trouble with collision detection, but I wanted to share it anyway.
This is my first Python script using
argparse instead of
sys for argument handling. I'm especially interested in whether the way I implemented it is up to par.
The way it's structured is probably not the best either and as always I'm not too fond of my naming. I stuck to
-w for reading and writing because it seemed intuitive, but deeper in the code
save_state are used. I'm not sure this is acceptable and which of both I should stick with (if any of those at all).
Under the argument parsing I have a couple of
BOLD_SNAKE_CASE variables which are pseudo constants. There's probably a better way to do this. Some of those can be freely changed by the user, others shouldn't. I think it's self explanatory enough, but feel free to comment.
As said, it was supposed to be part of an actual Racetrack-game. So extendability is important. I like extra features, but those are a pain in the behind to implement if the code isn't modular.
import numpy as np import matplotlib.pyplot as plt import shapely.geometry as sg from argparse import ArgumentParser from descartes.patch import PolygonPatch from cPickle import load, dump # Argument handling parser = ArgumentParser(description='Racetrack') parser.add_argument( 'InnerAmplitude', type=float, help='For example 0.1' ) parser.add_argument( 'OuterAmplitude', type=float, help='For example 0.2, should be higher than InnerAmplitude' ) parser.add_argument( "-v", "--verbose", action="store_true", help="increase output verbosity" ) mutex = parser.add_mutually_exclusive_group() mutex.add_argument( "-r", "--read", action="store_true", help="read state from file" ) mutex.add_argument( "-w", "--write", action="store_true", help="write state from file" ) args = parser.parse_args() # Define size of inner and outer bounds, between those is the Racetrack INNER_AMPLITUDE = args.InnerAmplitude OUTER_AMPLITUDE = args.OuterAmplitude DIAMETER = OUTER_AMPLITUDE - INNER_AMPLITUDE SIZE = 1.5 OUTER_WIDTH = 2 MINIMUM_POINTS = 5 MAXIMUM_POINTS = 15 LOAD_FILE = "state_file" SAVE_FILE = LOAD_FILE def load_state(): with open(LOAD_FILE, "r") as file: np.random.set_state(load(file)) def save_state(): with open(SAVE_FILE, "w") as file: dump(np.random.get_state(), file) # Possibility to fix seed if args.verbose: print (np.random.get_state()) if args.read: load_state() elif args.write: save_state() # A function to produce a pseudo-random polygon def random_polygon(): nr_points = np.random.randint(MINIMUM_POINTS, MAXIMUM_POINTS) angle = np.sort(np.random.rand(nr_points) * 2 * np.pi) dist = 0.3 * np.random.rand(nr_points) + 0.5 return np.vstack((np.cos(angle)*dist, np.sin(angle)*dist)).T # Base polygon poly = random_polygon() # Create a shapely ring object from base polygon inner_ring = sg.LinearRing(poly) outer_ring_inside = inner_ring.parallel_offset(DIAMETER, 'right', join_style=2, mitre_limit=10.) outer_ring_outside = inner_ring.parallel_offset(OUTER_WIDTH * DIAMETER, 'right', join_style=2, mitre_limit=10.) # Revert the third ring. This is necessary to use it to produce a hole outer_ring_outside.coords = list(outer_ring_outside.coords)[::-1] # Inner and outer polygon inner_poly = sg.Polygon(inner_ring) outer_poly = sg.Polygon(outer_ring_inside, [outer_ring_outside]) # Create the figure fig, ax = plt.subplots(1) # Convert inner and outer polygon to matplotlib patches and add them to the axes ax.add_patch(PolygonPatch(inner_poly, facecolor=(0, 1, 0, 0.4), edgecolor=(0, 1, 0, 1), linewidth=3)) ax.add_patch(PolygonPatch(outer_poly, facecolor=(1, 0, 0, 0.4), edgecolor=(1, 0, 0, 1), linewidth=3)) # Finalization ax.set_aspect(1) plt.title("Racetrack") plt.axis([-SIZE, SIZE, -SIZE, SIZE]) plt.grid() plt.show()
python racetrack.py -w 0.1 0.25
Not all tracks are playable, since there is nothing checking whether the angles are too sharp. This isn't considered a problem at the moment.