Programming Technique: How to create a simple card game - ruby

as I am learning the Ruby language, I am getting closer to actual programming. I was thinking of creating a simple card game. My question isn't Ruby oriented, but I do know want to learn how to solve this problem with a genuine OOP approach. In my card game, I want to have four players, using a standard deck with 52 cards, no jokers/wildcards. In the game, I won't use the ace as a dual card, it is always the highest card.
So, the programming problems I wonder about are the following:
How can I sort/randomize the deck of cards? There are four types, each having 13 values. Eventually there can be only unique values, so picking random values could generate duplicates.
How can I implement a simple AI? As there are tons of card games, someone would have figured this part out already, so references would be great.
I am a true Ruby nuby, and my goal here is to learn to solve problems, so pseudo code would be great, just to understand how to solve the problem programmatically. I apologize for my grammar and writing style if it's unclear, for it is not my native language.
Also, pointers to sites where such challenges are explained would be a great resource!
Thank you for your comments, answers and feedback!

Something to get you started
You can ensure unique cards very easily by using numbers from 0 to 51.
The Array#shuffle method is based off the Knuth-Fisher-Yates shuffle algorithm. http://en.wikipedia.org/wiki/Fisher–Yates_shuffle
class Card
RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A)
SUITS = %w(Spade Heart Club Diamond)
attr_accessor :rank, :suit
def initialize(id)
self.rank = RANKS[id % 13]
self.suit = SUITS[id % 4]
end
end
class Deck
attr_accessor :cards
def initialize
# shuffle array and init each Card
self.cards = (0..51).to_a.shuffle.collect { |id| Card.new(id) }
end
end
# people with Ruby 1.9 (or 1.8.7 with backports) can safely ignore this duck punch
class Array
# knuth-fisher-yates shuffle algorithm
def shuffle!
n = length
for i in 0...n
r = rand(n-i)+i
self[r], self[i] = self[i], self[r]
end
self
end
def shuffle
dup.shuffle!
end
end
test
d = Deck.new
d.cards.each do |card|
puts "#{card.rank} #{card.suit}"
end
output
6 Spade
5 Heart
2 Heart
8 Heart
8 Diamond
7 Club
J Diamond
4 Club
K Spade
5 Diamond
J Heart
8 Spade
10 Club
4 Diamond
9 Heart
7 Diamond
3 Diamond
K Diamond
7 Spade
Q Diamond
9 Diamond
6 Heart
A Heart
9 Club
A Spade
5 Club
J Club
Q Spade
2 Club
2 Spade
Q Heart
A Diamond
10 Spade
10 Diamond
Q Club
3 Club
A Club
K Club
6 Club
10 Heart
2 Diamond
3 Spade
K Heart
5 Spade
9 Spade
7 Heart
4 Spade
J Spade
3 Heart
4 Heart
8 Club
6 Diamond

Rather than cramming this all in a comment, I'm adding this as a note for people that might find it useful. Ruby 1.9's native Array#shuffle! and Array#shuffle does in fact use the Knuth-Fisher-Yates shuffle algorithm.
ruby-1.9.1-p376/array.c
/*
* call-seq:
* array.shuffle! -> array
*
* Shuffles elements in _self_ in place.
*/
static VALUE
rb_ary_shuffle_bang(VALUE ary)
{
long i = RARRAY_LEN(ary);
rb_ary_modify(ary);
while (i) {
long j = rb_genrand_real()*i;
VALUE tmp = RARRAY_PTR(ary)[--i];
RARRAY_PTR(ary)[i] = RARRAY_PTR(ary)[j];
RARRAY_PTR(ary)[j] = tmp;
}
return ary;
}
/*
* call-seq:
* array.shuffle -> an_array
*
* Returns a new array with elements of this array shuffled.
*
* a = [ 1, 2, 3 ] #=> [1, 2, 3]
* a.shuffle #=> [2, 3, 1]
*/
static VALUE
rb_ary_shuffle(VALUE ary)
{
ary = rb_ary_dup(ary);
rb_ary_shuffle_bang(ary);
return ary;
}

Don't bother looking for an AI package
You will learn more and get greater satisfaction by coding the "AI" yourself.
Start simple, just consider:
game state - what cards have been played or seen, what cards are visible to all players
strategy - how does a computer player respond based on its current hand and its knowledge of the game state
once you have that working, you can get develop more sophisticated strategies:
inference - what cards does the human player likely hold based on her prior actions
game tree search - how to maximize the chance of winning given what could possibly happen
then if you want to get really sophisticated, you can start looking into opponent modeling

Macek's answer is good as far as for setting up a deck.
You also asked about other entities.
You probably want four "Players". Each player might be either human or machine controlled.
To implement a human player, you interface with the screen/mouse/keyboard; to implement the machine controlled players, you have a hand and you can see some central cards on a table (all the players need to know of a central table that holds any cards that would be on the table).
From there the logic is based on what game you are playing.
Once your "Player" (AI) gets the turn (for instance, a takeTurn method is called on your player object), it should examine its cards and make the proper decisions--taking cards from stacks on the table or placing cards from its hand onto the table. (The table almost certainly has at least two stacks a player can access--"Draw" and "Discard".)
When a Human player has his takeTurn method called, it should interface with the screen--updating the player's hand, allowing him to draw and discard.
When each player is done with his turn, it should return. It can't directly call the next player (otherwise you'd start to build up a stack), so you need some form of turn control that can call the players in order. This centralized control also prevents players from knowing about each other, they shouldn't really need to (one of the best OO design tactics is that each object should know as little about other objects as possible).
...Still thinking... I may add more...

I'm not sure what sort of card game you want to build but the most common way of building this sort of AI is generating a tree of possible options. I don't think there's a library to do it as such but ruby can do trees easily.
The aim is to have a root node which is the present time and then each child node is a possible action. Then the child of each possible action is the next possible action. From there you can build a tree of every possible outcome. All that remains is to select the outcome you like.
Where you don't have all the information (ie can't see your opponents cards) you simulate it. By simulate I mean guess. The average of all the simulations/guesses will give you a good idea of which tree branches are 'likely to be the best'.
If you can do all that you're well on the way (and it's a really good exercise), there's hundreds of AI articles about, google will be your friend. The only problem with the approach I described is it can be desperately slow but there are many clever techniques to speed it up like transposition tables, alpha-beta pruning etc... which I don't suggest you look up quite yet.

Something very simple to get you started:
class CardGame
DECK = %w[A 2 3 4 5 6 7 8 9 T J Q K].product(%w[c d h s]).map(&:join)
def initialize(decks=1)
#decks = decks
end
def shuffle
#playing_deck = (DECK*#decks).shuffle
end
def deal(players=1, cards=5)
shuffle
#dealt = Array.new(players) { Array.new }
#dealt.map { |hand| cards.times { hand << #playing_deck.pop } }
end
def display
#dealt.each_with_index { |cards, i| puts "Player #{i+1}: #{cards.join(' | ')}" }
puts "Cards used: #{#dealt.flatten.size}"
puts "Cards remaining: #{#playing_deck.size}"
end
private :shuffle
end
game1 = CardGame.new
game1.deal
game1.display
puts
game1.deal(4)
game1.display
puts
game2 = CardGame.new(2)
game2.deal(6,10)
game2.display

Related

What Algorithm/Technique Can I Use to Spread Out Bots as Evenly as Possible?

Trying to wrap my head around this but not able to come up with the algorithm. Maybe someone can help? The question is as follows:
You’re writing part of the code for an online card game. In the game, players pass cards right and left around a table. In your online version, you want to allow users to set up a game with some number of human players and some number of “bot” (automated) players. In order to be fair, you want to spread out the bots as evenly as possible around the table; for example, if there are 6 total players and 3 are bots, every third seat should be a bot, and the rest should be humans.
Example
Input: "6 3"
Output "HHBHHBHHB"
Basically given the input of "6 3" (where first number is number of humans and second number is number of bots) I need to return the seating arrangement that spaces out the bots as evenly as possible. "H" represents Human and "B" represents bots.
I can't think of the logic to implement this. I get a feeling it's embarrassingly simple but I'm drawing a blank. Can someone help please? Doesn't matter what coding language you use. I'm more interested in the algorithm and solution.
EDIT: Found out how to do it when Humans and Bots are nice numbers that divide into each other (Humans divided by bots: 9 and 3, 6 and 2, 10 and 5, etc.) but how about for any combination?
Example: Input "6 4"
You would think that the following will work:
HHBHHBHBHB
but that is not as evenly as possible. The following is the better solution:
HHBHBHHBHB
Here is a generic approach for any number of different classes of participants. I decided to offer an example with (E)xperts, (H)umans and (B)ots. It tries to be even no matter what.
It isn't perfectly optimal in the general case, but it is optimal in the simple case.
import random
def distribute (count_dict):
total = sum(count_dict.values())
freq = {}
state = {}
for key, count in count_dict.items():
freq[key] = count/total
state[key] = random.random() # Start randomly
answer = []
for i in range(total):
best_state = -1
best_key = None
for key, cur_state in state.items():
cur_state += freq[key]
state[key] = cur_state
if best_state < cur_state:
best_state = cur_state
best_key = key
answer.append(best_key)
state[best_key] -= 1
return answer
print(distribute({"E": 3, "B": 7, "H": 11}))

What is optimal algorithm of "random card distribution (Poker game)"?

What is the optimal algorithm of "random card distribution (Poker game)"?
I'd like to build a program that can distribute playing cards evenly with respect to their numbers and suits to the 4 players in a Poker game. In other words, all 4 players should have a straight or straight flush at the same turn. But, how do we build the algorithm for that?
This is how I think it can be approached:
Set i = suits (e.g., 1 for heart 2 for spades) j = number (ace = 1, king =13)
Make a matrix deck (Like [H1, H2, ... H13]...[S1, S2, ... S13])
Randomly choose the first card (random i1 and j1), save to player 1's deck
card = deck[i1, j1]
player1 = np.append(card)
Randomly choose second card (random i2 and j2)
If i2 = i1, rechoose i, if not, save the card to player 2 deck
Continue until 4th card
For 5th turn, if i5 = i1 and j5 = j1+1 or j1-1, save the card to player 1 deck. if not rechoose j or i.
Continue until 8th card
For the 9th turn, if i9 = i1 and j9 = j1+1 or j1-1 or j5+1 or j5-1, save the card to player 1 deck. if no rechoose j or i
However, this algorithm is time-consuming, because I need to introduce a new way to check every 4 turns whether it has a close number of previous cards that the player already has.
Is there any way to make this simpler, such as to check the card having a close number of previous cards for making a straight flush?
The way to approach is the same as if you wanted to deal cards in real life. Here is an example in python
Build a deck of cards:
import itertools
deck = ["".join(c) for c in itertools.product("AKQJT98765432", "cdhs")]
# ['Ac', 'Ad', 'Ah', 'As', 'Kc', 'Kd', ....
Shuffle the deck
import random
random.shuffle(deck)
# ['3c', '5c', '7h', 'Qs', 'Ts', '8s', 'Jd', ...
Deal cards to 4 players
players = []
for i in range(4):
cards = [deck.pop(), deck.pop()]
players.append(cards)
# [['Td', 'Jd'], ['7c', '5c'], ['Js', 'Ks'], ['6c', '8d']]

Algorithm Ballots cast in an Election

Can someone help me solve this?
We recently held an election for 3 positions.
There were 6 candidates.
Each member could cast 3 votes but could not vote for the same person more than once.
134 ballots were cast. (402 total votes)
The final tally was
result = {a:91, b:66, c:63, d:63, e:60, f:59}
I can easily determine the 20 possible unique ballots cast
result.keys.combination(3).to_a
But obviously given the number of possible combinations brute force would be impossibly time consuming so I am hoping someone can provide an algorithm to solve this practically.
I am trying to find a reasonably efficient way to determine a single possible tally of ballots but if you can provide multiple or all possible tallies that would be amazing.
Lets think about simpler thing, for example canapé. You have N small slices of bread and M ingredients (Mi pieces each).
You have to create N canapes with unique ingredients. Obviously, this is impossible if there is Mi > N.
reasonably efficient way to determine a single possible tally of ballots
Arrange the bread slices in a line. Take the first ingredient and spread it all out, starting with the first slice of bread. Take the second ingredient and so on until you reach the last piece of bread. Return to the first slice of bread and continue to lay on top. Continue until you run out of ingredients.
require 'set'
result = {a: 91, b: 66, c: 63, d: 63, e: 60, f: 59}
BALLOTS = 134
if (result.values.any? { |v| v > BALLOTS }) || (result.values.sum % BALLOTS != 0)
raise 'Unfair election!'
end
ballots = BALLOTS.times.map { Set.new }
i = 0
result.each do |candidate, votes|
votes.times do
ballots[i % BALLOTS] << candidate
i += 1
end
end
puts "Do all of them have 3 unique votes? #{ballots.all? { |b| b.size == 3 }}"
Obviously, it's O(∑Mi) where ∑Mi is 402 (your "total votes"). I don't think there is a faster way.
but if you can provide multiple or all possible tallies
You can change the ingredients order. In your case there are 6! = 720 ways to fill the ballots. And I found that 60 of them are unique.
There is different amount of unique tallies for the different results. There are only 10 unique ways for result = {a: 67, b: 67, c: 67, d: 67, e: 67, f: 67} for example.
Changing start position (i = start # instead of 0) does not provide new unique ways.
If anybody has more than one third of the possible votes, or if the number of votes is not a multiple of three, there is no possible answer.
If there are at least three votes left, and nobody has more than one third of the possible votes, decide on a ballot paper that gives the top three candidates one vote each and reduce their totals by one.
This process stops either with all votes accounted for or with somebody having more than one third of the vote. I think the worst case for this is votes (N+1, N, N, N) where you go to (N, N-1, N-1, N) where the count not decremented gains a little but does not reach one third, so I think you can continue this process to account for all votes cast.
There are obvious many different equivalent counts. Any pair of actions that do not overlap in two candidates has at least one possible alternative interpretation. One way to generate multiple solutions would be to chose possible ballots at random subject only to the constraint that no sum ever gets to more than one third of the possible votes left. Another would be to rearrange answers at random and follow https://en.wikipedia.org/wiki/Metropolis%E2%80%93Hastings_algorithm (although I have not proved that any particular set of small rearrangements makes the set of possible solutions connected)
Although one can may be able to find a feasible solution for a given result using a heuristic algorithm, there is no assurance of that as it an NP-hard problem. It could be formulated as an integer linear program (ILP). In principle, ILP software will identify a feasible solution or report that no feasible solution exists. I say "in principle" because the problem may well be unsolvable in the time available.
This ILP problem has 20x6 = 120 non-negative, integer-valued variables and 6 constraints.
The variables are:
ni : the number of times ballot i is cast, i = 1,2,...,20
#<Set: {1, 3, 5}> is an example of one of the 20 ballots.
There are two types of known constants:
aij : equal to 1 if ballot i contains a vote for candidate j,
i = 1,2,...,20, j = 1,...,6, else zero
vj : the number of votes candidate j is to receive, j = 1,...,6
The constraints are as follows.
∑iniaij = vj, j = 1,...,6
ni >= 0 and integer-valued, i = 1,2,...,20
The first set of contraints ensures that each candidate receives the specified number of votes. The second set of contraints is implicit in ILP's.
ILP's also have objective functions to be maximized or minimimized, subject to the constraints. Here only a feasible solution is desired so the objective function might be expressed as
max 0x1
or something equivalent.
I can't believe I was so stuck on this but thank you #mcdowella for shaking me free of it.
We can just keep randomly shuffling and shifting the lists until we get a list where each group contains exactly 3 elements. It is definitely brute force but for a single result it is reasonably efficient (albeit unpredictable)
result = {a: 91, b: 66, c: 63, d: 63, e: 60, f: 59}
votes = result.map { |candidate, count| [candidate] * count }
def build_election(arr)
134.times.map do |x|
arr.shuffle.select {|a| !a.empty? }.first(3).map do |s|
s.pop
end
end
end
#a = build_election(votes.map(&:dup)) until #a&.all? {|s| s.size == 3}

Running a function multiple times and tracking results of the fight simulation

Ive made a function to run a fight simulation. Its got a random element so would like to run it 100 times to check results.
Ive learnt that ruby cant have functions inside functions.
$p1_skill = 10
$p1_health = 10
$p2_skill = 10
$p2_health = 10
def hp_check
if $p2_health >= 1 && $p1_health == 0
return "p2_wins"
elsif $p1_health >= 1 && $p2_health == 0
return "p1_wins"
else
battle
end
end
def battle
p1_fight = $p1_skill + rand(2..12)
p2_fight = $p2_skill + rand(2..12)
if p1_fight > p2_fight
$p2_health -= 2
hp_check
elsif p2_fight > p1_fight
$p1_health -= 2
hp_check
else
battle
end
end
battle
Right now this accurately produces a winner. It rolls two dice and adds them to a players skill. If its higher than the other players the other player loses 2 health.
The skills and hp of players will change throughout the game, this is for a project assignment.
Id like this to produce odds for win chances for balancing issues.
I have several suggestions regarding your implementation. Note that since this is a homework I'm providing the answer in pieces rather than just giving you an entire program. In no particular order...
Don't use global variables. I suspect this is the major hurdle you're running into with trying to achieve multiple runs of your model. The model state should be contained within the model methods, and initial state can be passed to it as arguments. Example:
def battle(p1_skill, p1_health, p2_skill, p2_health)
Unless your instructor has mandated that you use recursion, a simple loop structure will serve you much better. There's no need to check who won until one player or the other drops down to zero (or lower). There's also no need for an else to recursively call battle, the loop will iterate to the next round of the fight if both are still in the running, even if neither player took a hit.
while p1_health > 0 && p2_health > 0
# roll the dice and update health
end
# check who won and return that answer
hp_check really isn't needed, when you lose the recursive calls it becomes a one-liner if you perform the check after breaking out of the loop. Also, it would be more useful to return just the winner, so whoever gets that return value can decide whether they want to print it, use it to update a tally, both, or something else entirely. After you break out of the loop outlined above:
# determine which player won, since somebody's health dropped to 0 or less
p1_health > 0 ? 1 : 2
When you're incrementing or decrementing a quantity, don't do equality testing. p1_health <= 0 is much safer than p1_health == 0, because some day you or somebody else is going to start from an odd number while decrementing by 2's, or decrement by some other (random?) amount.
Generating a number uniformly between 2 and 12 is not the same as summing two 6-sided dice. There are 36 possible outcomes for the two dice. Only one of the 36 yields a 2, only one yields a 12, and at the other extreme, there are six ways to get a sum of 7. I created a little die-roll method which takes the number of dice as an argument:
def roll_dice(n)
n.times.inject(0) { |total| total + rand(1..6) }
end
so, for example, determining player 1's fight score becomes p1_fight = p1_skill + roll_dice(2).
After making these sorts of changes, tallying up the statistics is pretty straightforward:
n = 10000
number_of_p1_wins = 0
n.times { number_of_p1_wins += 1 if battle(10, 10, 10, 10) == 1 }
proportion = number_of_p1_wins.to_f / n
puts "p1 won #{"%5.2f" % (100.0 * proportion)}% of the time"
If you replace the constant 10's in the call to battle by getting user input or iterating over ranges, you can explore a rich set of other scenarios.

Algorithm to calculate sum of points for groups with varying member count [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
Let's start with an example. In Harry Potter, Hogwarts has 4 houses with students sorted into each house. The same happens on my website and I don't know how many users are in each house. It could be 20 in one house 50 in another and 100 in the third and fourth.
Now, each student can earn points on the website and at the end of the year, the house with the most points will win.
But it's not fair to "only" do a sum of the points, as the house with a 100 students will have a much higher chance to win, as they have more users to earn points. So I need to come up with an algorithm which is fair.
You can see an example here: https://worldofpotter.dk/points
What I do now is to sum all the points for a house, and then divide it by the number of users who have earned more than 10 points. This is still not fair, though.
Any ideas on how to make this calculation more fair?
Things we need to take into account:
* The percent of users earning points in each house
* Few users earning LOTS of points
* Many users earning FEW points (It's not bad earning few points. It still counts towards the total points of the house)
Link to MySQL dump(with users, houses and points): https://worldofpotter.dk/wop_points_example.sql
Link to CSV of points only: https://worldofpotter.dk/points.csv
I'd use something like Discounted Cumulative Gain which is used for measuring the effectiveness of search engines.
The concept is as it follows:
FUNCTION evalHouseScore (0_INDEXED_SORTED_ARRAY scores):
score = 0;
FOR (int i = 0; i < scores.length; i++):
score += scores[i]/log2(i);
END_FOR
RETURN score;
END_FUNCTION;
This must be somehow modified as this way of measuring focuses on the first result. As this is subjective you should decide on your the way you would modify it. Below I'll post the code which some constants which you should try with different values:
FUNCTION evalHouseScore (0_INDEXED_SORTED_ARRAY scores):
score = 0;
FOR (int i = 0; i < scores.length; i++):
score += scores[i]/log2(i+K);
END_FOR
RETURN L*score;
END_FUNCTION
Consider changing the logarithm.
Tests:
int[] g = new int[] {758,294,266,166,157,132,129,116,111,88,83,74,62,60,60,52,43,40,28,26,25,24,18,18,17,15,15,15,14,14,12,10,9,5,5,4,4,4,4,3,3,3,2,1,1,1,1,1};
int[] s = new int[] {612,324,301,273,201,182,176,139,130,121,119,114,113,113,106,86,77,76,65,62,60,58,57,54,54,42,42,40,36,35,34,29,28,23,22,19,17,16,14,14,13,11,11,9,9,8,8,7,7,7,6,4,4,3,3,3,3,2,2,2,2,2,2,2,1,1,1};
int[] h = new int[] {813,676,430,382,360,323,265,235,192,170,107,103,80,70,60,57,43,41,21,17,15,15,12,10,9,9,9,8,8,6,6,6,4,4,4,3,2,2,2,1,1,1};
int[] r = new int[] {1398,1009,443,339,242,215,210,205,177,168,164,144,144,92,85,82,71,61,58,47,44,33,21,19,18,17,12,11,11,9,8,7,7,6,5,4,3,3,3,3,2,2,2,1,1,1,1};
The output is for different offsets:
1182
1543
1847
2286
904
1231
1421
1735
813
1120
1272
1557
It sounds like some sort of constraint between the houses may need to be introduced. I might suggest finding the person that earned the most points out of all the houses and using it as the denominator when rolling up the scores. This will guarantee the max value of a user's contribution is 1, then all the scores for a house can be summed and then divided by the number of users to normalize the house's score. That should give you a reasonable comparison. It does introduce issues with low numbers of users in a house that are high achievers in which you may want to consider lower limits to the number of house members. Another technique may be to introduce handicap scores for users to balance the scales. The algorithm will most likely flex over time based on the data you receive. To keep it fair it will take some responsive action after the initial iteration. Players can come up with some creative ways to make scoring systems work for them. Here is some pseudo-code in PHP that you may use:
<?php
$mostPointsEarned; // Find the user that earned the most points
$houseScores = [];
foreach ($houses as $house) {
$numberOfUsers = 0;
$normalizedScores = [];
foreach ($house->getUsers() as $user) {
$normalizedScores[] = $user->getPoints() / $mostPointsEarned;
$numberOfUsers++;
}
$houseScores[] = array_sum($normalizedScores) / $numberOfUsers;
}
var_dump($houseScores);
You haven't given any examples on what should be preferred state, and what are situations against which you want to be immune. (3,2,1,1 compared to 5,2 etc.)
It's also a pity you haven't provided us the dataset in some nice way to play.
scala> val input = Map( // as seen on 2016-09-09 14:10 UTC on https://worldofpotter.dk/points
'G' -> Seq(758,294,266,166,157,132,129,116,111,88,83,74,62,60,60,52,43,40,28,26,25,24,18,18,17,15,15,15,14,14,12,10,9,5,5,4,4,4,4,3,3,3,2,1,1,1,1,1),
'S' -> Seq(612,324,301,273,201,182,176,139,130,121,119,114,113,113,106,86,77,76,65,62,60,58,57,54,54,42,42,40,36,35,34,29,28,23,22,19,17,16,14,14,13,11,11,9,9,8,8,7,7,7,6,4,4,3,3,3,3,2,2,2,2,2,2,2,1,1,1),
'H' -> Seq(813,676,430,382,360,323,265,235,192,170,107,103,80,70,60,57,43,41,21,17,15,15,12,10,9,9,9,8,8,6,6,6,4,4,4,3,2,2,2,1,1,1),
'R' -> Seq(1398,1009,443,339,242,215,210,205,177,168,164,144,144,92,85,82,71,61,58,47,44,33,21,19,18,17,12,11,11,9,8,7,7,6,5,4,3,3,3,3,2,2,2,1,1,1,1)
) // and the results on the website were: 1. R 1951, 2. H 1859, 3. S 990, 4. G 954
Here is what I thought of:
def singleValuedScore(individualScores: Seq[Int]) = individualScores
.sortBy(-_) // sort from most to least
.zipWithIndex // add indices e.g. (best, 0), (2nd best, 1), ...
.map { case (score, index) => score * (1 + index) } // here is the 'logic'
.max
input.mapValues(singleValuedScore)
res: scala.collection.immutable.Map[Char,Int] =
Map(G -> 1044,
S -> 1590,
H -> 1968,
R -> 2018)
The overall positions would be:
Ravenclaw with 2018 aggregated points
Hufflepuff with 1968
Slytherin with 1590
Gryffindor with 1044
Which corresponds to the ordering on that web: 1. R 1951, 2. H 1859, 3. S 990, 4. G 954.
The algorithms output is maximal product of score of user and rank of the user within a house.
This measure is not affected by "long-tail" of users having low score compared to the active ones.
There are no hand-set cutoffs or thresholds.
You could experiment with the rank attribution (score * index or score * Math.sqrt(index) or score / Math.log(index + 1) ...)
I take it that the fair measure is the number of points divided by the number of house members. Since you have the number of points, the exercise boils down to estimate the number of members.
We are in short supply of data here as the only hint we have on member counts is the answers on the website. This makes us vulnerable to manipulation, members can trick us into underestimating their numbers. If the suggested estimation method to "count respondents with points >10" would be known, houses would only encourage the best to do the test to hide members from our count. This is a real problem and the only thing I will do about it is to present a "manipulation indicator".
How could we then estimate member counts? Since we do not know anything other than test results, we have to infer the propensity to do the test from the actual results. And we have little other to assume than that we would have a symmetric result distribution (of the logarithm of the points) if all members tested. Now let's say the strong would-be respondents are more likely to actually test than weak would-be respondents. Then we could measure the extra dropout ratio for the weak by comparing the numbers of respondents in corresponding weak and strong test-point quantiles.
To be specific, of the 205 answers, there are 27 in the worst half of the overall weakest quartile, while 32 in the strongest half of the best quartile. So an extra 5 respondents of the very weakest have dropped out from an assumed all-testing symmetric population, and to adjust for this, we are going to estimate member count from this quantile by multiplying the number of responses in it by 32/27=about 1.2. Similarly, we have 29/26 for the next less-extreme half quartiles and 41/50 for the two mid quartiles.
So we would estimate members by simply counting the number of respondents but multiplying the number of respondents in the weak quartiles mentioned above by 1.2, 1.1 and 0.8 respectively. If however any result distribution within a house would be conspicuously skewed, which is not the case now, we would have to suspect manipulation and re-design our member count.
For the sample at hand however, these adjustments to member counts are minor, and yields the same house ranks as from just counting the respondents without adjustments.
I got myself to amuse me a little bit with your question and some python programming with some random generated data. As some people mentioned in the comments you need to define what is fairness. If as you said you don't know the number of people in each of the houses, you can use the number of participations of each house, thus you motivate participation (it can be unfair depending on the number of people of each house, but as you said you don't have this data on the first place).
The important part of the code is the following.
import numpy as np
from numpy.random import randint # import random int
# initialize random seed
np.random.seed(4)
houses = ["Gryffindor","Slytherin", "Hufflepuff", "Ravenclaw"]
houses_points = []
# generate random data for each house
for _ in houses:
# houses_points.append(randint(0, 100, randint(60,100)))
houses_points.append(randint(0, 50, randint(2,10)))
# count participation
houses_participations = []
houses_total_points = []
for house_id in xrange(len(houses)):
houses_total_points.append(np.sum(houses_points[house_id]))
houses_participations.append(len(houses_points[house_id]))
# sum the total number of participations
total_participations = np.sum(houses_participations)
# proposed model with weighted total participation points
houses_partic_points = []
for house_id in xrange(len(houses)):
tmp = houses_total_points[house_id]*houses_participations[house_id]/total_participations
houses_partic_points.append(tmp)
The results of this method are the following:
House Points per Participant
Gryffindor: [46 5 1 40]
Slytherin: [ 8 9 39 45 30 40 36 44 38]
Hufflepuff: [42 3 0 21 21 9 38 38]
Ravenclaw: [ 2 46]
House Number of Participations per House
Gryffindor: 4
Slytherin: 9
Hufflepuff: 8
Ravenclaw: 2
House Total Points
Gryffindor: 92
Slytherin: 289
Hufflepuff: 172
Ravenclaw: 48
House Points weighted by a participation factor
Gryffindor: 16
Slytherin: 113
Hufflepuff: 59
Ravenclaw: 4
You'll find the complete file with printing results here (https://gist.github.com/silgon/5be78b1ea0b55a20d90d9ec3e7c515e5).
You should enter some more rules to define the fairness.
Idea 1
You could set up the rule that anyone has to earn at least 10 points to enter the competition.
Then you can calculate the average points for each house.
Positive: Everyone needs to show some motivation.
Idea 2
Another approach would be to set the rule that from each house only the 10 best students will count for the competition.
Positive: Easy rule to calculate the points.
Negative: Students might become uninterested if they see they can't reach the top 10 places of their house.
From my point of view, your problem is diveded in a few points:
The best thing to do would be to re - assignate the player in the different Houses so that each House has the same number of players. (as explain by #navid-vafaei)
If you don't want to do that because you believe that it may affect your game popularity with player whom are in House that they don't want because you can change the choice of the Sorting Hat at least in the movie or books.
In that case, you can sum the point of the student's house and divide by the number of students. You may just remove the number of student with a very low score. You may remove as well the student with a very low activity because students whom skip school might be fired.
The most important part for me n your algorithm is weather or not you give points for all valuables things:
In the Harry Potter's story, the students earn point on the differents subjects they chose at school and get point according to their score.
At the end of the year, there is a special award event. At that moment, the Director gave points for valuable things which cannot be evaluated in the subject at school suche as the qualites (bravery for example).

Resources