diff --git a/settlers/main.py b/settlers/main.py new file mode 100644 index 0000000..5dc7372 --- /dev/null +++ b/settlers/main.py @@ -0,0 +1,187 @@ +"""Settlers of Catan game board generator""" + +___name___ = "settlers" +___license___ = "MIT" +___dependencies___ = ["ugfx_helper", "sleep"] +___categories___ = ["Games"] +___bootstrapped___ = False + +import random, ugfx, ugfx_helper, math, time, buttons +from app import App, restart_to_default +from tilda import Buttons + +ugfx_helper.init() +ugfx.clear(ugfx.BLACK) + +""" +This was an experiment in drawing hexagons. Some notes: + +Screen coords are x,y values that locate pixels on the physical display: + +0,0 → → 240,0 + ↓ ↓ +0,320 → 240,320 + +Hex coords are x,y,z values that locate the relative positions of hexagons: + + 0,1,-1 +-1,1,0 ↖ ↑ ↗ 1,0,-1 + 0,0,0 +-1,0,1 ↙ ↓ ↘ 1,-1,0 + 0,-1,1 + +Converting between the two systems can be done by multiplying the x and y +coordinates against a matrix. When converting to hex coords, the z value +can be computed from the new x and y values because x + y + z must always +equal zero. + +""" + +class Hex: + # Constant matrix used to convert from hex coords to screen coords + matrix = [3.0 * 0.5, 0.0, math.sqrt(3.0) * 0.5, math.sqrt(3.0)] + + # The screen coordinate to use as the origin for hex coordinates, + # the centre of hex 0,0,0 will be at this coordinate + origin = [math.ceil(ugfx.width() / 2), math.ceil(ugfx.height() / 2)] + + # Size in pixels of the hex, from the centre point to each corner + size = 22 + + # Possible kinds of resource and the colour it should be rendered + kinds = { + 0: ugfx.html_color(0xd4e157), # Sheep + 1: ugfx.html_color(0xffc107), # Wheat + 2: ugfx.html_color(0x993300), # Wood + 3: ugfx.html_color(0xff0000), # Brick + 4: ugfx.html_color(0x757575), # Ore + 5: ugfx.html_color(0xffee55), # Desert (nothing) + } + + # Transformations for how to get to the neighbouring hexes + directions = { + 0: [-1, 1, 0], # South West + 1: [0, 1, -1], # South + 2: [1, 0, -1], # South East + 3: [1, -1, 0], # North East + 4: [0, -1, 1], # North + 5: [-1, 0, 1], # North West + } + + def __init__(self, coords, kind, number, robber): + """Create a new hex at the given hex coordinates, of the given kind of resource""" + # Validate coords + assert len(coords) == 3, 'Invalid number of hexagon coordinates' + assert coords[0] + coords[1] + coords[2] == 0, 'Invalid hexagon coordinate values' + self.coords = coords + + # The kind of resource hosted by this hex + self.kind = kind + + # The dice roll required to win this resource + self.number = number + + # Whether this hex contains the robber + self.robber = robber + + # Compute the screen coordinates of the centre of the hex + self.centre = Hex.to_screen_coords(self.coords[0], self.coords[1]) + + # Generate screen coordinates for each of the corners of the hex + self.corners = [] + for i in range(0, 6): + angle = 2.0 * math.pi * (0 - i) / 6 + offset = [Hex.size * math.cos(angle), Hex.size * math.sin(angle)] + self.corners.append([round(self.centre[0] + offset[0]), round(self.centre[1] + offset[1])]) + + @staticmethod + def to_screen_coords(x, y): + """Returns screen coordinates computed from the given hex coordinates""" + newX = (Hex.matrix[0] * x + Hex.matrix[1] * y) * Hex.size + newY = (Hex.matrix[2] * x + Hex.matrix[3] * y) * Hex.size + return [newX + Hex.origin[0], newY + Hex.origin[1]] + + @staticmethod + def get_neighbouring_hex_coords(coords, direction): + return [a + b for a, b in zip(coords, Hex.directions[direction])] + + def draw(self): + """Draw the hexagon to the screen""" + ugfx.fill_polygon(0, 0, self.corners, Hex.kinds[self.kind]) + text_offset = Hex.size * 0.5 + if self.robber: + ugfx.text(round(self.centre[0] - text_offset), round(self.centre[1] - text_offset), "Rb ", ugfx.BLACK) + else: + if self.kind != 5: + ugfx.text(round(self.centre[0] - text_offset), round(self.centre[1] - text_offset), "{} ".format(self.number), ugfx.BLACK) + + def clear(self): + ugfx.fill_polygon(0, 0, self.corners, ugfx.BLACK) + + +def board_setup(resources, numbers): + """Generate a random game board""" + + # Two rings of hexes around the centre + radius = 2 + # Choose a starting hex on the outermost ring of hexes + choice = random.randrange(0, 6) + coords = [0, 0, 0] + for i in range(radius): + coords = [a + b for a, b in zip(coords, Hex.directions[choice])] + + # Copy lists so we can edit them with impunity + r_copy = resources.copy() + n_copy = numbers.copy() + + hexes = [] + while radius > 0: + # From the starting hex, go radius hexes in each of the 6 directions + for i in list(range((choice + 2) % 6, 6)) + list(range(0, (choice + 2) % 6)): + for j in range(radius): + # The resources are picked at random from the list + resource = r_copy.pop(random.randrange(0, len(r_copy))) + # But the dice roll numbers are picked in order, unless it's + # the desert in which case that is always 7 + if resource == 5: + number = 7 + else: + number = n_copy.pop(0) + hexes.append(Hex(coords, resource, number, number == 7)) + coords = Hex.get_neighbouring_hex_coords(coords, i) + + # Go into the next ring of hexes (opposite direction of starting choice) + coords = Hex.get_neighbouring_hex_coords(coords, (choice + 3) % 6) + radius = radius - 1 + resource = r_copy.pop() + if resource == 5: + number = 7 + else: + number = n_copy.pop(0) + hexes.append(Hex(coords, resource, number, number == 7)) + return hexes + + +# List of resources (pre-randomised to combat the not-very random number +# generator) and dice rolls (these have a strict order) for 2-4 player games +resources = [4, 0, 1, 4, 4, 2, 5, 3, 2, 1, 2, 2, 1, 0, 3, 0, 3, 1, 0] +numbers = [5, 2, 6, 3, 8, 10, 9, 12, 11, 4, 8, 10, 9, 4, 5, 6, 3, 11] + +def draw(): + hexes = board_setup(resources, numbers) + for h in hexes: + h.clear() + time.sleep_ms(100) + h.draw() + +ugfx.text(5, 5, 'Press A to generate another ', ugfx.WHITE) +draw() + +# Main Loop +while True: + if buttons.is_triggered(tilda.Buttons.BTN_A): + draw() + elif buttons.is_triggered(tilda.Buttons.BTN_Menu): + break + time.sleep_ms(5) +restart_to_default()