Homeworlds Openings

A sojourn on star situations.
Published

May 3, 2023

Introduction

Homeworlds is an abstract space battle game designed by John Cooper and published by Looney Labs. It’s played with multipurpose game pieces called Looney pyramids, three of each of combination of three sizes (small, medium, and large) and four colours (green, yellow, blue, and red) for 36 in total. A pyramid lying down is a space ship and a pyramid standing up is a star. Each colour is associated with an ability.

At the beginning of the game, each player chooses two stars to be their homeworld. Star systems are connected if they have no sizes in common.

Homeworlds star connection rule (from Looney Labs)

Expert players actively debate Homeworlds’ opening theory. One question is which combination of star sizes should be selected by the first player, and how the second player should respond.

Some homeworld options (from Looney Labs)

When players’ homeworlds are one step away from one another (i.e. connected), this is known as a “microverse”. If they are two steps away, that’s a “small universe”, and if they are three steps away, that’s a “large universe”.

In addition to their homeworld stars, each player selects a pyramid to be their first ship. The rest of the pieces become the bank from which new stars and ships are taken and to which destroyed stars and ships are returned.

Data

Babamots compiled information from 4928 games of Homeworlds played on BoardGameArena and provided a first player win rate and a count of observations for each combination of first and second player homeworld sizes. Draws were counted as half a win for each player. Only games that lasted at least eight turns for each player were included.

read_csv("homeworlds_openings.csv") -> 
  homeworlds_openings

homeworlds_openings |>
  gt() |> 
  cols_label(p1_wr = "First Player Win Rate",
             n = "Number of Games",
             p1_hw = "First Player",
             p2_hw = "Second Player") |>
  tab_spanner(label = md("**Homeworld Sizes**"), columns = 3:4) |>
  cols_align(align = "center", columns = everything()) |>
  tab_header(title = "Homeworlds Openings Data",
             subtitle = "Source: Scraped from BGA by Babamots") |>
  tab_options(container.height = "500px") |>
  opt_row_striping()
Homeworlds Openings Data
Source: Scraped from BGA by Babamots
First Player Win Rate Number of Games Homeworld Sizes
First Player Second Player
0.175 20 SS SM
0.200 10 SS ML
0.250 4 SS LL
0.292 12 SS SL
0.385 13 SL MM
0.393 28 ML MM
0.408 71 SM MM
0.409 22 ML SS
0.441 34 ML LL
0.449 476 SL SM
0.472 724 ML SM
0.481 27 SL LL
0.486 590 SM SL
0.491 53 LL SL
0.495 92 LL LL
0.495 813 SM ML
0.500 3 SS MM
0.502 430 SL ML
0.510 52 SM LL
0.538 398 ML SL
0.550 30 MM MM
0.571 7 SS SS
0.599 292 ML ML
0.604 130 SL SL
0.625 8 MM LL
0.631 84 LL ML
0.635 52 MM SM
0.643 300 SM SM
0.667 3 SM SS
0.667 6 SL SS
0.667 6 MM SS
0.679 56 MM ML
0.723 47 LL SM
0.750 20 MM SL
0.833 6 LL SS
0.889 9 LL MM

We’ll also create some helper vectors and functions that will be convenient later.

# helper vectors
#c("SS", "SM", "SL", "MM", "ML", "LL") -> hw_sizes
c("SM", "SL", "ML", "SS", "MM", "LL") -> hw_sizes
c("micro", "small", "large") -> universe_sizes

# list the stars in a homeworld
(function(hw) {unlist(strsplit(as.character(hw), ""))}) -> stars

# determine the universe size
(function(hw1, hw2) {
  case_when(length(intersect(stars(hw1), stars(hw2))) == 0 ~ "micro",
            length(unique(c(stars(hw1), stars(hw2)))) <= 2 ~ "small",
            T ~ "large")}) -> universe_size

Modelling First Player Advantage

We can model first player advantage by imagining that a game of Homeworlds is like a biased coin flip, where each opening is associated with an independent bias. For example, if the bias parameter for some opening is 0.6, then we expect the first player to win 60% of the time that opening is played.1

  • 1 Assuming “average play”, anyway.

  • If this parameter is distributed according to a beta distribution Beta(a, b), then its posterior distribution accounting for observing n wins and m losses for the first player is Beta(a + n, b + m).2 All that is left is to decide on a prior distribution representing our beliefs before observing any data. This is an interesting subtlety3, but it seems reasonable to choose a uniform prior, Beta(1, 1), under which all possibilities are equally credible.

  • 2 See these slides for a quick proof of this fact.

  • 3 See this StackExchange answer for more on uninformative Beta priors.

  • From the posterior distribution, we can construct a 90% credible interval using qbeta. Unlike the confidence intervals used in frequentist statistical inference, which are the source of much confusion, Bayesian credible intervals have a straightforward interpretation: we’re 90% sure the true value of the parameter is contained in a 90% credible interval.4

  • 4 Frequentists view the bounds of a confidence interval as random, and the parameter value as fixed. Bayesians view the bounds of a credible interval as fixed, and the parameter value as random.

  • We can also calculate the probability that the first player is favoured (i.e. is expected to win at least half the time) in each opening using pbeta. In this case we don’t consider the extent of the advantage, just whether there is one.

    homeworlds_openings |>
      mutate(across(c(p1_hw, p2_hw), 
                    \(x) factor(x, ordered = T, levels = hw_sizes))) |>
      rowwise() |>
      mutate(p1_wins = n*p1_wr,
             p2_wins = n*(1 - p1_wr),
             # calculate a 90% credible interval from the posterior distribution
             ci_lower = qbeta(c(0.05, 0.95), 1 + p1_wins, 1 + p2_wins)[1],
             ci_upper = qbeta(c(0.05, 0.95), 1 + p1_wins, 1 + p2_wins)[2],
             # format the interval as text
             formatted_ci = paste0(round(100 * ci_lower),
                                   "-",
                                   round(100 * ci_upper),
                                   "%"),
             # bold intervals that do not contain 50%
             ci_fontface = case_when(!between(0.5, ci_lower, ci_upper) ~ "bold",
                                     T ~ "plain"),
             universe_size = universe_size(p1_hw, p2_hw),
             universe_size = factor(universe_size, 
                                    ordered = TRUE, 
                                    levels = universe_sizes),
             prob_fav = 1 - pbeta(0.5, 1 + p1_wins, 1 + p2_wins),
             formatted_prob_fav = case_when(
               prob_fav < 0.005 ~ "<1%",
               prob_fav < 0.995 ~ paste0(round(100 * prob_fav), "%"),
               T ~ ">99%"),
             prob_fav_fontface = case_when(abs(prob_fav - 0.5) > 0.45 ~ "bold", 
                                           T ~ "plain")) |>
      arrange(p1_hw, p2_hw) ->
      visualization_data
    
    visualization_data |>
      select(p1_hw, p2_hw, formatted_ci, prob_fav) |>
      gt() |>
      cols_label(p1_hw = "First Player",
                 p2_hw = "Second Player",
                 formatted_ci = html("First Player Advantage<br>90% Credible Interval"),
                 prob_fav = html("Probability of<br>First Player Advantage")) |>
      tab_spanner(label = md("**Homeworld Sizes**"), columns = 1:2) |>
      cols_align(align = "center", columns = everything()) |>
      tab_header(title = "First Player Advantage in Homeworlds Openings") |>
      tab_options(container.height = "500px") |>
      opt_row_striping()
    First Player Advantage in Homeworlds Openings
    Homeworld Sizes First Player Advantage
    90% Credible Interval
    Probability of
    First Player Advantage
    First Player Second Player
    SM SM 60-69% 0.999999680
    SM SL 45-52% 0.248413661
    SM ML 47-52% 0.387849078
    SM SS 25-90% 0.687849323
    SM MM 32-51% 0.061530777
    SM LL 40-62% 0.556708384
    SL SM 41-49% 0.013054745
    SL SL 53-67% 0.991093430
    SL ML 46-54% 0.533008043
    SL SS 34-87% 0.773895167
    SL MM 21-61% 0.212754245
    SL LL 33-63% 0.423357565
    ML SM 44-50% 0.066035425
    ML SL 50-58% 0.935076177
    ML ML 55-64% 0.999647706
    ML SS 26-58% 0.202200438
    ML MM 26-55% 0.132786582
    ML LL 31-58% 0.249135577
    SS SM 8-36% 0.001698437
    SS SL 14-53% 0.081930335
    SS ML 8-47% 0.032714844
    SS SS 29-81% 0.635929076
    SS MM 17-83% 0.500000000
    SS LL 8-66% 0.187500000
    MM SM 52-73% 0.973623861
    MM SL 56-87% 0.986698151
    MM ML 57-77% 0.996318685
    MM SS 34-87% 0.773895167
    MM MM 40-69% 0.704614834
    MM LL 34-83% 0.746093750
    LL SM 60-81% 0.998935554
    LL SL 38-60% 0.448433249
    LL ML 54-71% 0.991765439
    LL SS 48-95% 0.937298892
    LL MM 61-96% 0.989278600
    LL LL 41-58% 0.462032892

    Visualizing First Player Advantage

    visualization_data |>
      # underlying plot
      ggplot() +
      # aesthetic mapping
      aes(x = p1_hw, y = p2_hw, fill = p1_wr) +
      # visual elements representing the data
      ## add tiles coloured to indicate the observed first player win rate
      geom_tile(width = 0.8, height = 0.8) +
      ## add dots to indicate universe size
      geom_point(aes(colour = universe_size), 
                 position = position_nudge(x = 0.3, y = 0.3), 
                 size = 3) +
      ## trick to circle the universe size dots without a fill scale collision
      geom_point(size = 3, 
                 shape = 21, 
                 fill = NA, 
                 colour = "black", 
                 position = position_nudge(x = 0.3, y = 0.3)) +
      # scales
      ## gradient scale for the tiles
      scale_fill_viridis(option = "cividis") +
      ## discrete scale for the universe size dots
      scale_colour_viridis(option = "cividis", discrete = TRUE) +
      scale_y_discrete(limits = rev) +
      # labels
      labs(title = "Homeworlds First Player Advantage by Opening",
           x = "First Player's Homeworld Sizes",
           y = "Second Player's Homeworld Sizes",
           fill = "Observed First Player\nWin Rate",
           colour = "Universe Size") +
      # theming
      theme_jdonland() +
    #  theme(panel.background = element_rect(colour = "#fffcf9")) +
      ## put the win rate legend under the universe size legend
      guides(colour = guide_legend(order = 1)) ->
      plot
    
    # version labelled with credible intervals
    plot +
      # add formatted credible intervals as text
      geom_label(aes(label = formatted_ci, fontface = ci_fontface),
                 fill = "#fffcf9",
                 label.padding = unit(0.125, "lines")) +
      # add subtitle
      labs(subtitle = "90% Bayesian credible intervals with uniform priors")

    # version labelled with posterior probability of first player advantage
    plot + 
      # add posterior probability first player is favoured as text
      geom_label(aes(label = formatted_prob_fav, fontface = prob_fav_fontface),
                fill = "#fffcf9",
                label.padding = unit(0.125, "lines")) +
      # add subtitle
      labs(subtitle = "Posterior probability first player is favoured")

    Observations

    It seems that the first player should probably choose a large-large or medium-medium homeworld configuration. A homeworld with equally-sized stars is known as a “Gemini” configuration. When the first player chooses a Gemini opening, the second player loses the option of creating a large universe; only a small or a microverse is possible.

    The second player should opt for a small universe rather than a microverse. (A large universe is not possible when the first player’s homeworld contains stars of equal size.)

    Why isn’t small-small also advantageous for the first player? This is likely due to creating a bank configuration which will provide better lines of play to the second player in the early turns of the game.

    Confounding Variables

    One might reasonably object to the model described above. For one thing, it does not take into account the colours of the pieces selected. This is a relatively minor issue, however, since a certain combination of colours5 is almost universally chosen.

  • 5 Blue and yellow stars with a green ship.

  • More concerning is the potentially confounding effect of player skill. It’s very possible that some openings may be effective only in the hands of experts. It would be interesting to revisit this analysis with more granular and detailed data that included Elo ratings. You can help with this by learning Homeworlds!

    You can buy a Homeworlds set from Looney Labs. The same pieces can also be used to play many other interesting games.

    Conclusions

    If you’re playing first, start with two large stars of blue and yellow, and a large green ship. If you’re playing second and your opponent has followed my advice, try small-large or large-large in the same colours.

    References