Initial version of a Catan game board generator
If you have 3D printed your own version of Settlers of Catan, with 3D terrain and little sheep and grain siloes and everything, it is fairly difficult to properly randomise the tile selection during the game setup. I assume most EMFgoers have encountered this problem, so this app provides a solution!sammachingprs
parent
b44cc3596b
commit
7ac28fc73e

@ 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 (prerandomised to combat the notvery random number


# generator) and dice rolls (these have a strict order) for 24 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()

Loading…
Reference in New Issue