Skip to content

Commit

Permalink
Implement probability distribution calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivanov1ch committed Apr 8, 2021
1 parent 4163c26 commit f964311
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 8 deletions.
7 changes: 0 additions & 7 deletions catan/balance/balance_functions.py

This file was deleted.

13 changes: 13 additions & 0 deletions catan/balance/calculate_balance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from catan.balance.probability_distribution import measure_probability_distribution
from catan.balance.resource_clustering import measure_resource_clustering
from catan.balance.resource_distribution import measure_resource_distribution


# The process behind each of these methods was inspired by Board Game Analysis's blog post:
# https://www.boardgameanalysis.com/what-is-a-balanced-catan-board/

def calculate_balance(board):
resource_distribution_score = measure_resource_distribution(board)
resource_clustering_score = measure_resource_clustering(board)
probability_distribution_score = measure_probability_distribution(board)
return resource_distribution_score
28 changes: 28 additions & 0 deletions catan/balance/probability_distribution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from catan.board.terrain_hexes import terrain_hex_distribution, TerrainType


def measure_probability_distribution(board):
# There are 58 total dots across all of the number tiles (the dots being those under the number)
total_num_dots = 58

# Calculates how many TerrainHexes there are, excluding the desert
total_num_hexes = sum(list(terrain_hex_distribution.values())) - terrain_hex_distribution['DESERT']

# Using this number and the terrain_hex_distribution, we can calculate the expected payout of each TerrainType
# (minus TerrainType.DESERT) based on how many TerrainHexes with that TerrainType are on the board
expected_payouts = {terrain_type: terrain_hex_distribution[terrain_type] * total_num_dots / total_num_hexes for
terrain_type in list(terrain_hex_distribution.keys()) if terrain_type != 'DESERT'}

# Count the total number of dots per resource
terrain_dot_counts = {terrain_type: 0 for terrain_type in list(expected_payouts.keys())}

# Iterate over every NumberToken (to do this we must iterate over every TerrainHex)
for terrain_hex_row in board.hexes:
for terrain_hex in terrain_hex_row:
if terrain_hex.terrain_type != TerrainType.DESERT:
number_token = terrain_hex.number_token
terrain_dot_counts[terrain_hex.terrain_type.name] += number_token.dot_count

# Calculate the sum of the squares of the differences between actual dots counts and the expected payout
return sum({terrain_type: (expected_payouts[terrain_type] - terrain_dot_counts[terrain_type]) ** 2
for terrain_type in list(expected_payouts.keys())}.values())
3 changes: 3 additions & 0 deletions catan/board/number_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ def __init__(self, number):
raise ValueError("Number tokens must have integer values between 2 and 12, inclusive, excluding 7!")

self.dot_count = number_token_dots[number]

def __str__(self):
return '{0} ({1} dots)'.format(self.number, self.dot_count)
2 changes: 1 addition & 1 deletion catan/board/terrain_hexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ def __init__(self, terrain_type, r, c, number_token=None):
self.col = c

def __str__(self):
return '({0}, {1}), {2}'.format(self.row, self.col, self.terrain_type)
return '({0}, {1}), {2}, {3}'.format(self.row, self.col, self.terrain_type, self.number_token)
33 changes: 33 additions & 0 deletions tests/known_layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,36 @@
'tile_numbers': beginner_layout['tile_numbers']
}, 100],
]

# Contains the four board layouts with known clustering scores, in an array with their known score
known_probability_distribution_layouts = [
# Very well distributed board, asserted value calculated manually
[{
'terrain_types': [TerrainType.FOREST, TerrainType.FIELD, TerrainType.FOREST, TerrainType.HILL,
TerrainType.MOUNTAIN,
TerrainType.FOREST, TerrainType.HILL, TerrainType.MOUNTAIN, TerrainType.HILL,
TerrainType.FIELD,
TerrainType.PASTURE, TerrainType.PASTURE, TerrainType.PASTURE, TerrainType.MOUNTAIN,
TerrainType.FIELD, TerrainType.DESERT, TerrainType.PASTURE, TerrainType.FOREST,
TerrainType.FIELD],
'tile_numbers': [5, 11, 9, 12, 3, 10, 8, 6, 4, 8, 4, 9, 5, 10, 2, 3, 11, 6]
}, 16 / 27],
# Decently distributed board, asserted value calculated manually
[{
'terrain_types': [TerrainType.MOUNTAIN, TerrainType.FOREST, TerrainType.PASTURE, TerrainType.FIELD,
TerrainType.FOREST, TerrainType.FIELD, TerrainType.MOUNTAIN, TerrainType.PASTURE,
TerrainType.DESERT, TerrainType.FOREST, TerrainType.HILL, TerrainType.HILL, TerrainType.FIELD,
TerrainType.FOREST, TerrainType.MOUNTAIN, TerrainType.PASTURE, TerrainType.PASTURE,
TerrainType.FIELD, TerrainType.HILL],
'tile_numbers': [4, 5, 4, 6, 11, 9, 3, 2, 3, 11, 6, 8, 12, 10, 9, 5, 8, 10]
}, 1516 / 27],
# Very badly distributed board, asserted value calculated manually
[{
'terrain_types': [TerrainType.FIELD, TerrainType.FOREST, TerrainType.FOREST, TerrainType.PASTURE,
TerrainType.FIELD, TerrainType.DESERT, TerrainType.FIELD, TerrainType.MOUNTAIN,
TerrainType.PASTURE, TerrainType.HILL, TerrainType.PASTURE, TerrainType.MOUNTAIN,
TerrainType.FIELD, TerrainType.HILL, TerrainType.HILL, TerrainType.MOUNTAIN,
TerrainType.FOREST, TerrainType.PASTURE, TerrainType.FOREST],
'tile_numbers': [6, 9, 5, 2, 5, 8, 4, 11, 10, 12, 10, 6, 3, 3, 4, 9, 11, 8]
}, 2950 / 27],
]
8 changes: 8 additions & 0 deletions tests/test_balance_functions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from catan.balance.probability_distribution import measure_probability_distribution
from catan.balance.resource_clustering import measure_resource_clustering
from catan.balance.resource_distribution import measure_resource_distribution
from catan.board.board import Board
Expand All @@ -15,3 +16,10 @@ def test_measure_resource_clustering():
for layout_data in known_clustered_layouts:
assert measure_resource_clustering(Board(terrain_types=layout_data[0]['terrain_types'],
tile_numbers=layout_data[0]['tile_numbers'])) == layout_data[1]


def test_measure_probability_distribution():
# Assert that the measured resource clustering of each known layout is correct
for layout_data in known_probability_distribution_layouts:
assert round(measure_probability_distribution(Board(terrain_types=layout_data[0]['terrain_types'],
tile_numbers=layout_data[0]['tile_numbers'])), 4) == round(layout_data[1], 4)

0 comments on commit f964311

Please sign in to comment.