hananamar / war-of-whispers

1 stars 0 forks source link

I made an additional variant - can you add it? #1

Closed frhrdr closed 2 years ago

frhrdr commented 2 years ago

Hi Hanan,

I wanted to make an app to fix the initial loyalty assignment for War of Whispers and then I discovered you had already made an app for it, which is super neat! I have a different approach from the ones that you used though and was wondering if you might want to integrate it. My variant balances the sums of score multipliers across players for each faction (or nearly balances it). The script is written in Python and available unter this link.

I don't know any Ruby, so I'll need some help integrating this into your app if you are interested. Each run requires a little bit of search, which might be too complicated, so what I can offer instead is to precompute valid assignments (up to symmetry) for player counts 2-5 and perhaps slack 0 and 1, which can then quickly be loaded in ruby. The files should be small: at slack=0, 5 players have 950 valid assignments and at slack=1 for 5 players I get 43714, which is a bit larger but should still be easy to store. numbers for fewer players are significantly lower.

Let me know if you're interested, Cheers Frederik

hananamar commented 2 years ago

Hi Fredrik, I would be happy to add your algorithm!

I'm not familiar with the algorithm, but it doesn't seem too complicated to implement in ruby.

I tried going through your code, but I am not familiar with Python syntax and libraries. Do you mind walking me through the steps in the algorithm, even in a mathematical formulation?

Thanks

frhrdr commented 2 years ago

Hi Hanan,

Sure I'm happy to explain the algorithm :) I've updated parts of it, so make sure to load a new copy

Goal

the goal is to find all starting loyalties for a given number of players such that each empire is worth about the same amount of points across players. Since the score multipliers are [4,3,2,0,-1], the average multiplier is 1.6. In a balanced three-player game, for instance, each empire should therefore get a sum of multipliers around 3*1.6=4.8 across players. The algorithm finds all initializations where each empire has a sum of either 4 or 5. One solution would be this: player 1 loyalties are: 4x red, 3x yellow, 2x green, 0x blue, -1x brown player 2 loyalties are: 4x brown, 3x green, 2x red, 0x blue, -1x yellow player 3 loyalties are: 4x blue, 3x yellow, 2x brown, 0x green, -1x red Sum of multipliers per empire: red: 5, yellow: 5, green: 5, blue: 4, brown: 5

This is as balanced as it gets, but maybe we don't want a fully balanced game. Slack relaxes the constraint a little by widening the range of acceptable "sum of multipliers" values the empires can have. At slack=0, we only have a range of 4-5. With slack=1 we allow 4-6, 3-6 with slack=2, and with slack=3 we get 3-7, which can already lead to a fair bit of imbalance.

Implementation

Everything important happens in equidistant_init(). At first we compute the mean_score=1.6 and from that our allowed range (including slack) of min_emprie_score - max_empire_score. Starting with a single player and then adding another one in each loop (line 58), the algorithm looks at each possible assignment with n-1 players (line 66) and generates all possible initializations for the next player (line 69). For each complete assignment, we check if it meets our balance constraints with is_valid_assignment(). If so, we add it to the list of valid assignments. Of course, this naive approach would be very inefficient, so there are some ways we speed it up.

TLDR

To make things simpler, I have precomputed all the assignments and added them in these zip files for 2-4 players and slacks of 0-3. I think that is a sensible range. wow_balanced_inits.zip Now what you would still need to do is re-implement load_assignments() and sample_assignment(). The important steps in the latter are:

I hope this clarifies things somewhat. Let me know if you have any more questions! Cheers, Frederik

(edit: I called the empires factions by mistake, the text is now corrected)

hananamar commented 2 years ago

Hi Frederik,

Thanks for the very detailed explanation. I went over your explanation and it makes things very clear, I think I understand it a lot better now. Later I will go over the code as well, and make an attempt to implement it in Ruby. I don't think it will be too difficult.

Hanan

hananamar commented 2 years ago

I also took a look at the csv files with your generated values, and something stood out to me so I wanted to clarify: You wrote that "[we] ... enforce that the assignment of player n+1 has a higher value than that of player n". While this assumption is technically not against the rules of the game, I understand why you would want to assume this. However in the csv files there are several entries of the int 1234, which is also the assignment of p1. Am I correct to surmise that this should actually not be allowed under the mentioned assumption?

frhrdr commented 2 years ago

Oh, I guess I made a mistake in the explanation. The constraint is that assignments for later players should be greater OR EQUAL. with 4 or more players (or large slack) you can have solutions where assignments for different players match. The CSV files don't include player one, because it is always the same in order to save space. So the entries that start with 01234 are actually ones where P1 and P2 have the same loyalties

hananamar commented 2 years ago

I see, thanks for clarifying. I'm not trying to be nit picky, just making sure I understand :)

frhrdr commented 2 years ago

No worries, I'm happy to clarify :) Also in the cases of two or more players having matching loyalty assignments, I'm not 100% sure my method eliminates all duplicates, but if there is an error, the number of duplicates should be very small I think. Maybe there should be an extra constraint that each assignment for a new player n that is not a duplicate of n-1 can have at most as many duplicates as the previous one, but I'm not certain this works and also I don't think it will matter much.

hananamar commented 2 years ago

Okay, so I just finished implementing the algorithm in Ruby. I verified my results with your CSV files and they seem to match so far. I just need to finish up assigning the factions and implementing the slack selection into the web app. Not quite sure how to present the slack input yet... maybe just a dropdown with 0,1,2 as options for now.

I must say, I like the concept of this algorithm, making sure that the empires all have a similar total support. Thank you for getting in touch!

hananamar commented 2 years ago

Commit b530940 was pushed with this feature. I decided to leave the slack out of the hands of the user, because I feel like its too fiddly. I set the slack to be 0 for 4 players, 1 for 3p and 2 for 2p, based on my feeling. Let me know if you think of a better approach.

Also you can check out the updated web app here.

frhrdr commented 2 years ago

Looks good! The slack setting makes sense. Thanks for adding this. Now I don't have to pass my laptop around the table the next time we play, which is a huge improvement! :grin: