Division of teams on the basis of skill point - java

I am trying to put n players having different skill point(ranging from 100-3000) into r teams such that overall skill in each team is as close as possible to every other team.
I first sorted the players in descending order of skill points and top r players were put into each team. Now the team with lowest skill point(iterating and calculating sum) gets the top player remaining.
For eg.
A 600
B 550
C 400
D 250
E 220
F 200
G 150
H 140
For 2 teams, result will be:
Team A{600,250,220,150}= 1220
Team B{550,400,200,140}= 1290
In another approach each team gets a player from top and a player from bottom.
Team A{600,140,400,200}=1340
Team B{550,150,250,220}=1170
So here 1st approach was better, but for different set of data sometimes approach 2 is optimum and sometimes approach 1 is optimum.
Is there any specific algorithm to do this? I tried to read Microsoft's TrueSkill algorithm, but it was way too complex.

Looks like you want to score each combination of players. I'm going to cheat and use python here:
from itertools import combinations
players = [600, 550, 400, 250, 220, 150, 140]
scores = {}
for i in range(1, int(len(players)/2)):
for c in combinations(players, i):
scores[c] = abs(sum(c) - sum([p for p in players if p not in c]))
print sorted(scores.items(), key=lambda x: x[1])[0]
prints: ((600, 550), 10)
Edit: Didn't recognize this as a hard problem right away.

Like mcdowella mentioned in a comment, this problem, as stated, is np-hard. A classic approach would be Integer Programming.
The following implementation uses the Julia programming language and the JuMP library as modelling tool. The Integer/Mixed-Integer-Programming solver used is cbc, but a commercial solver as Gurobi may used too (only 2 lines of code-change needed!). Besides Gurobi, all the mentioned tools are open-source!
Code
using JuMP
using Cbc
# PARAMS
N_PLAYERS = 15
N_TEAMS = 3
LOWER_SKILL = 100
UPPER_SKILL = 3000
# RANDOM INSTANCE
SKILL = Int[]
for p = 1:N_PLAYERS
push!(SKILL, rand(LOWER_SKILL:UPPER_SKILL))
end
# MODEL
m = Model(solver=CbcSolver())
bigM = sum(SKILL)^2 # more tight bound possible
# VARS
#defVar(m, x[1:N_PLAYERS, 1:N_TEAMS], Bin) # player-team assignment vars
#defVar(m, 0 <= tsum_pos[1:N_TEAMS,1:N_TEAMS] <= bigM) # abs-linearization: pos-part
#defVar(m, 0 <= tsum_neg[1:N_TEAMS,1:N_TEAMS] <= bigM) # abs-linearization: neg-part
# CONSTRAINTS
# each player is assigned to exactly one team
for p = 1:N_PLAYERS
#addConstraint(m, sum{x[p,t], t=1:N_TEAMS} == 1)
end
# temporary team sum expresions
team_sums = AffExpr[]
for t = 1:N_TEAMS
#defExpr(y, SKILL[p] * sum{x[p,t], p=1:N_PLAYERS})
push!(team_sums, y)
end
# errors <-> splitted abs-vars equality
for t1 = 1:N_TEAMS
for t2 = 1:N_TEAMS
if t1 != t2
#addConstraint(m, (team_sums[t1] - team_sums[t2]) == (tsum_pos[t1,t2] - tsum_neg[t1,t2]))
end
end
end
# objective
#setObjective(m, Min, sum{tsum_pos[i,j] + tsum_neg[i,j], i=1:N_TEAMS, j=1:N_TEAMS}) # symmetry could be used
# SOLVE
tic()
status = solve(m)
toc()
# OUTPUT
println("Objective is: ", getObjectiveValue(m))
println("Solution: ")
println("Player skills: ", SKILL)
for p = 1:N_PLAYERS
for t = 1:N_TEAMS
if getValue(x[p,t]) > 0.5
println("player ", p, " in team ", t)
end
end
end
for t=1:N_TEAMS
sum_ = 0
for p=1:N_PLAYERS
if getValue(x[p,t]) > 0.5
sum_ += SKILL[p]
end
end
println("team: ", t, " -> ", sum_)
end
println(sum(SKILL))
This modelling uses some linearization-trick to model absolute values, as needed for a L1-norm-based error like you described in your post!
Output
elapsed time: 9.785739578 seconds
Objective is: 28.00000000000063 # REMARK: error is doubled because of symmetries which could be changed
Solution:
Player skills: [2919,1859,1183,1128,495,1436,2215,2045,651,540,2924,2367,1176,334,1300]
player 1 in team 3
player 2 in team 1
player 3 in team 3
player 4 in team 1
player 5 in team 3
player 6 in team 2
player 7 in team 2
player 8 in team 1
player 9 in team 1
player 10 in team 1
player 11 in team 3
player 12 in team 2
player 13 in team 2
player 14 in team 2
player 15 in team 1
team: 1 -> 7523
team: 2 -> 7528
team: 3 -> 7521
22572

Related

Java sorting by spread between two BigDecimal

Please help with sorting in Java. I have a simple example (looks like this).
I need to sort list by the difference between two BigDecimals.
My Data class (I cut getters)
public class Quotation {
private final long id;
private final BigDecimal askPrice;
private final BigDecimal bidPrice;
public Quotation(long id, BigDecimal bidPrice, BigDecimal askPrice) {
this.id = id;
this.askPrice = askPrice;
this.bidPrice = bidPrice;
}
}
Here we store our records.
storeQuotation(new Quotation(1000,new BigDecimal("99"), new BigDecimal("104")));
storeQuotation(new Quotation(1001,new BigDecimal("69"), new BigDecimal("72")));
storeQuotation(new Quotation(1002,new BigDecimal("65"), new BigDecimal("69")));
storeQuotation(new Quotation(1003,new BigDecimal("70"), new BigDecimal("71")));
storeQuotation(new Quotation(1004,new BigDecimal("71"), new BigDecimal("73")));
storeQuotation(new Quotation(1005,new BigDecimal("90"), new BigDecimal("95")));
storeQuotation(new Quotation(1006,new BigDecimal("92"), new BigDecimal("93")));
storeQuotation(new Quotation(1007,new BigDecimal("94"), new BigDecimal("98")));
storeQuotation(new Quotation(1008,new BigDecimal("90"), new BigDecimal("92")));
storeQuotation(new Quotation(1009,new BigDecimal("92"), new BigDecimal("95")));
out - Not Sorted
id - 1000. Bid - 99. Ask - 104. Spread = 5
id - 1001. Bid - 69. Ask - 72. Spread = 3
id - 1002. Bid - 65. Ask - 69. Spread = 4
id - 1003. Bid - 70. Ask - 71. Spread = 1
id - 1004. Bid - 71. Ask - 73. Spread = 2
id - 1005. Bid - 90. Ask - 95. Spread = 5
id - 1006. Bid - 92. Ask - 93. Spread = 1
id - 1007. Bid - 94. Ask - 98. Spread = 4
id - 1008. Bid - 90. Ask - 92. Spread = 2
id - 1009. Bid - 92. Ask - 95. Spread = 3
And I just need to sort this list by the difference between bidPrice and askPrice.
I tried this method...
public static List<Quotation> getSpreadsList(boolean decreasing) {
List<Quotation> sortedBySpread = QuotationsStoreImpl.quotationList;
Collections.sort(sortedBySpread, (a, b) ->
(a.getBidPrice().intValue() - b.getAskPrice().intValue()));
// sortedBySpread.sort((a, b) ->
// (a.getBidPrice().intValue() - b.getAskPrice().intValue()));
if (decreasing) {
Collections.reverse(sortedBySpread);
}
return sortedBySpread;
}
}
But without success...
out - Sorted
id - 1002. Bid - 65. Ask - 69. Spread = 4
id - 1003. Bid - 70. Ask - 71. Spread = 1
id - 1004. Bid - 71. Ask - 73. Spread = 2
id - 1001. Bid - 69. Ask - 72. Spread = 3
id - 1008. Bid - 90. Ask - 92. Spread = 2
id - 1009. Bid - 92. Ask - 95. Spread = 3
id - 1006. Bid - 92. Ask - 93. Spread = 1
id - 1007. Bid - 94. Ask - 98. Spread = 4
id - 1005. Bid - 90. Ask - 95. Spread = 5
id - 1000. Bid - 99. Ask - 104. Spread = 5
The list is mixed but not sorted according to my criteria !
Spread not sorted !
How can I sort this list correct, by spread ?
I don't have much experience in java.
And all my attempts have come to nothing.
Collections.sort(sortedBySpread, (a, b) -> (a.getBidPrice().intValue() - b.getAskPrice().intValue())); does some math that makes little since since it subtracts the ask from the bid of two different quotes.
Instead you should calculate the spread of a and then subtract the spread of b:
Collections.sort(sortedBySpread, (a, b) -> (a.getBidPrice().intValue() - a.getAskPrice().intValue()) - (b.getBidPrice().intValue() - b.getAskPrice().intValue()));
Generally this could be expanded into the following to make it more clear what is going on:
Collections.sort(sortedBySpread, (Quotation a, Quotation b) -> {
int spreadA = a.getBidPrice().intValue() - a.getAskPrice().intValue();
int spreadB = b.getBidPrice().intValue() - b.getAskPrice().intValue();
return spreadA - spreadB;
});
But starting with the first snippet IntelliJ suggest the arguably far cleaner solution
Collections.sort(sortedBySpread, Comparator.comparingInt(a -> (a.getBidPrice().intValue() - a.getAskPrice().intValue())));
And going from there it might make sense to have a getSpread on Quotation:
public int getSpread() {
return bidPrice.intValue() - askPrice.intValue();
}
which would then allow
Collections.sort(sortedBySpread, Comparator.comparingInt(Quotation::getSpread));
And finally
sortedBySpread.sort(Comparator.comparingInt(Quotation::getSpread));
without the need for Collections.sort.
For demo, I put your values in a list. I also added the following to your class.
public BigDecimal getSpread() {
return askPrice.subtract(bidPrice);
}
public String toString() {
return "id - %d bid - %6.2f ask - %6.2f spread - %6.2f".formatted(id, bidPrice, askPrice,getSpread());
}
The data
List<Quotation> quotes = new ArrayList<>(List.of(
(new Quotation(1000,new BigDecimal("99"), new BigDecimal("104"))),
(new Quotation(1001,new BigDecimal("69"), new BigDecimal("72"))),
(new Quotation(1002,new BigDecimal("65"), new BigDecimal("69"))),
(new Quotation(1003,new BigDecimal("70"), new BigDecimal("71"))),
(new Quotation(1004,new BigDecimal("71"), new BigDecimal("73"))),
(new Quotation(1005,new BigDecimal("90"), new BigDecimal("95"))),
(new Quotation(1006,new BigDecimal("92"), new BigDecimal("93"))),
(new Quotation(1007,new BigDecimal("94"), new BigDecimal("98"))),
(new Quotation(1008,new BigDecimal("90"), new BigDecimal("92"))),
(new Quotation(1009,new BigDecimal("92"), new BigDecimal("95")))));
The sorting part is simple. Just use a comparator, referencing the spread.
quotes.sort(Comparator.comparing(Quotation::getSpread));
And print
for(Quotation q : quotes) {
System.out.println(q);
}
Prints
id - 1003 bid - 70.00 ask - 71.00 spread - 1.00
id - 1006 bid - 92.00 ask - 93.00 spread - 1.00
id - 1004 bid - 71.00 ask - 73.00 spread - 2.00
id - 1008 bid - 90.00 ask - 92.00 spread - 2.00
id - 1001 bid - 69.00 ask - 72.00 spread - 3.00
id - 1009 bid - 92.00 ask - 95.00 spread - 3.00
id - 1002 bid - 65.00 ask - 69.00 spread - 4.00
id - 1007 bid - 94.00 ask - 98.00 spread - 4.00
id - 1000 bid - 99.00 ask - 104.00 spread - 5.00
id - 1005 bid - 90.00 ask - 95.00 spread - 5.00

Reseting number java [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 last year.
Improve this question
So i got problem in java to determine who the last kid who got the candy, n is how many children, m is how many candy, and s is the number of kid who got the first candy.
so basically if n =4, m=6, and s =2 the answer is 3, because all started from 2(number of kid who got the first candy)->3->4 and restart to 1->2->3 and 3 is the last kid who got the candy.
the code run perfectly but i encounter bug at n = 3333, m =3333, and s = 1
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int s = in.nextInt();
int total = m%n;
int jaw = total+s-1;
System.out.print(jaw);
}
I assume the "bug" you're mentioning is that jaw is 0, right? That's because 3333%3333 will be 0 and thus 0 + s - 1 will be 0 as well (0+1-1 = 0).
You're on the right track, i.e. m % n would tell you how many pieces of candy are left after everyone got a fair/equal share.
Now you just distribute the "rest" (total) which means you "add" it to "first child" (s). Let's say this is c = s + total, so c is the last child that got anything. You subtract -1 because if you add anything the "cursor" advances to the next child and thus you want to track back.
However, what if you didn't have anything to distribute? Then the last child that got anything was the one before the first, which in case of 4 children and starting at 1 would be the 4th. Your calculation results in 0 which basically would express this.
Another problem you might face with your code is that if you start at the last child (say 4) and have 2 pieces of candy to distribute, then you'd get the "5th" child instead of the "1st" to be the last to get something: 2 + 4 - 1 = 5.
So how do you solve that?
Add jaw %= n to handle the "overflow" (if you start at the last child and distributed 2 more pieces of candy, the first would get it)
Check if jaw == 0 in which case the solution would be jaw = n (the last child got the last candy)
The problem is that you arrive at person 0 , who does not exist.
3333%3333 = 0
then 0 + 1 - 1 = 0
As we are in a loop 0 is the last person of the previous turn ie person number m.
We solve this by saying that, if the result is zero we return m.
import java.util.Scanner;
class sweets {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int s = in.nextInt();
int total = m%n;
int jaw = total+s-1;
if(jaw == 0){
jaw = m;
}
System.out.print(jaw);
}
}
You need to consider whenever m%n==0.
In that case jaw= n-s+1;
Just need to add an if statement to include it.

REGEX not working as in JAVA program as expected

I have been working on a program which makes use of Regular Expressions. It searches for some text in the files to give me a database based on the scores of different players.
Here is the sample of the text within which it searches.
ISLAMABAD UNITED 1st innings
Player Status Runs Blls 4s 6s S/R
David Warner lbw b. Hassan 19 16 4 0 118.8%
Joe Burns b. Morkel 73 149 16 0 49.0%
Kane Wiliiamson b. Tahir 135 166 28 2 81.3%
Asad Shafiq c. Rahane b. Morkel 22 38 5 0 57.9%
Kraigg Braithwaite c. Khan b. Boult 24 36 5 0 66.7%
Corey Anderson b. Tahir 18 47 3 0 38.3%
Sarfaraz Ahmed b. Morkel 0 6 0 0 0.0%
Tim Southee c. Hales b. Morkel 0 6 0 0 0.0%
Kyle Abbbott c. Rahane b. Morkel 26 35 4 0 74.3%
Steven Finn c. Hales b. Hassan 10 45 1 0 22.2%
Yasir Shah not out 1 12 0 0 8.3%
Total: 338/10 Overs: 92.1 Run Rate: 3.67 Extras: 10
Day 2 10:11 AM
-X-
I am using the following regex to get the different fields..
((?:\/)?(?:[A-Za-z']+)?\s?(?:[A-Za-z']+)?\s?(?:[A-Za-z']+)?\s?)\s+(?:lbw)?(?:not\sout)?(?:run\sout)?\s?(?:\(((?:[A-Za-z']+)?\s?(?:['A-Za-z]+)?)\))?(?:(?:st\s)?\s?(?:((?:['A-Za-z]+)\s(?:['A-Za-z]+)?)))?(?:c(?:\.)?\s((?:(?:['A-Za-z]+)?\s(?:[A-Za-z']+)?)?(?:&)?))?\s+(?:b\.)?\s+((?:[A-Za-z']+)\s(?:[A-Za-z']+)?)?\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)
Batsman Name - Group 1
Person Affecting Stumping (if any) - Group 2
Person Affecting RunOut (if any) - Group 3
Person Taking Catch (if any) - Group 4
Person Taking the wicket (if any) - Group 5
Runs Scored - Group 6
Balls Faced - Group 7
Fours Hit - Group 8
Sixes Hit - Group 9
Here is an example of the text I need to extract...
Group 0 contains David Warner lbw b. Hassan 19 16 4 0 118.8%
Group 1 contains 'David Warner'
Group 2 does not exist in this example
Group 3 does not exist in this example
Group 4 does not exist in this example
Group 5 contains 'Hassan'
Group 6 contains '19'
Group 7 contains '16'
Group 8 contains '4'
Group 9 contains '0'
When I try this on Regexr or Regex101, it gives the Group 1 as David Warner in the Group 1... But in my Java Program, it gives it as David. It is same for all results. I don't know why?
Here's the code of my program:
Matcher bat = Pattern.compile("((?:\\/)?(?:[A-Za-z']+)?\\s?(?:[A-Za-z']+)?\\s?(?:[A-Za-z']+)?\\s?)\\s+(?:lbw)?(?:not\\sout)?(?:run\\sout)?\\s?(?:\\(((?:[A-Za-z']+)?\\s?(?:['A-Za-z]+)?)\\))?(?:(?:st\\s)?\\s?(?:((?:['A-Za-z]+)\\s(?:['A-Za-z]+)?)))?(?:c(?:\\.)?\\s((?:(?:['A-Za-z]+)?\\s(?:[A-Za-z']+)?)?(?:&)?))?\\s+(?:b\\.)?\\s+((?:[A-Za-z']+)\\s(?:[A-Za-z']+)?)?\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)").matcher(batting.group(1));
while (bat.find()) {
batPos++;
Batsman a = new Batsman(bat.group(1).replace("\n", "").replace("\r", "").replace("S/R", "").replace("/R", "").trim(), batting.group(2));
if (bat.group(0).contains("not out")) {
a.bat(Integer.parseInt(bat.group(6)), Integer.parseInt(bat.group(7)), Integer.parseInt(bat.group(8)), Integer.parseInt(bat.group(9)), batting.group(2), false);
} else {
a.bat(Integer.parseInt(bat.group(6)), Integer.parseInt(bat.group(7)), Integer.parseInt(bat.group(8)), Integer.parseInt(bat.group(9)), batting.group(2), true);
}
if (!teams.contains(batting.group(2))) {
teams.add(batting.group(2));
}
boolean f = true;
Batsman clone = null;
for (Batsman b1 : batted) {
if (b1.eq(a)) {
clone = b1;
f = false;
break;
}
}
if (!f) {
if (bat.group(0).contains("not out")) {
clone.batUpdate(a.getRunScored(), a.getBallFaced(), a.getFour(), a.getSix(), false, true);
} else {
clone.batUpdate(a.getRunScored(), a.getBallFaced(), a.getFour(), a.getSix(), true, true);
}
} else {
batted.add(a);
}
}
Your regex is way too complicated for such a simple task. To make it simple(or eliminate it for that matter), operate on a single line rather than the bunch of text.
For this, do
String array[] = str.split("\\n");
Then once you get each individual line, just split by a mutliple spaces, like
String parts[] = array[1].split("\\s\\s+");
Then you can access each part seperately, like Status can be accessed like
System.out.println("Status - " + parts[1]);
All commentators are right, of course, this might not be a typical problem to solve with a regex. But to answer your question - why is there a difference between java and regex101? - let's try to pull out some of the problems caused by your regex that makes it too complex. Next step would be to track down if and why there is a difference in using it in java.
I tried to understand your regex (and cricket at the same time!) and came up with a proposal that might help you to make us understand what your regex should look like.
First attempt reads until the number columns are reached. My guess is, that you should be looking at alternation instead of introducing a lot of groups. Take a look at this: example 1
Explanation:
( # group 1 start
\/? # not sure why there should be /?
[A-Z][a-z]+ # first name
(?:\s(?:[A-Z]['a-z]+)+) # last name
)
(?:\ # spaces
( # group 2 start
lbw # lbw or
|not\sout # not out or
|(c\.|st|run\sout) # group 3: c., st or run out
\s # space
\(? # optional (
(\w+) # group 4: name
\)? # optional )
))? # group 2 end
(?:\s+ # spaces
( # group 5 start
(?:b\.\s)(\w+) # b. name
))? # group 5 end
\s+ # spaces
EDIT 1: Actually, there is a 'stumped' option missing in your regex as well. Added that in mine.
EDIT 2: Stumped doesn't have a dot.
EDIT 3: The complete example can be found at example 2
Some java code to test it:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Foo {
public static void main(String[] args) {
String[] examples = {
"David Warner lbw b. Hassan 19 16 4 0 118.8%",
"Joe Burns b. Morkel 73 149 16 0 49.0%",
"Asad Shafiq c. Rahane b. Morkel 22 38 5 0 57.9%",
"Yasir Shah not out 1 12 0 0 8.3%",
"Yasir Shah st Rahane 1 12 0 0 8.3%",
"Morne Morkel run out (Shah) 11 17 1 1 64.7%"
};
Pattern pattern = Pattern.compile("(\\/?[A-Z][a-z]+(?:\\s(?:[A-Z]['a-z]+)+))(?:\\s+(lbw|not\\sout|(c\\.|st|run\\sout)\\s\\(?(\\w+)\\)?))?(?:\\s+((?:b\\.\\s)(\\w+)))?\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+\\.\\d%)");
for (String text : examples) {
System.out.println("TEXT: " + text);
Matcher matcher = pattern.matcher(text);
if (matcher.matches()) {
System.out.println("batsman: " + matcher.group(1));
if (matcher.group(2) != null) System.out.println(matcher.group(2));
if (matcher.group(5) != null && matcher.group(5).matches("^b.*"))
System.out.println("bowler: " + matcher.group(6));
StringBuilder sb = new StringBuilder("numbers are: ");
int[] groups = {7, 8, 9, 10, 11};
for (int i : groups) {
sb.append(" " + matcher.group(i));
}
System.out.println(sb.toString());
System.out.println();
}
}
}
}

Scalable solution for Rock-Paper-Scissor

Just went through a variant of the game : Rock-Paper-Scissor-Lizard-Spock
I have written a Java code for traditional R-P-S problem, but when I tried extending my code for the newer version of the game (R-P-S-L-S)..I felt my code is terribly bad. Here is a snippet :
if (player1.equals("ROCK") &&
player2.equals("SCISSORS")) {
winner = 1;
}
// Paper covers rock...
else if (player1.equals("PAPER") &&
player2.equals("ROCK")) {
winner = 1;
}
// Scissors cut paper...
else if (player1.equals("SCISSORS") &&
player2.equals("PAPER")) {
winner = 1;
}
else {
winner = 2;
}
I realized the code cant be extended easily for the newer version - as well as for more than 2 players. This is mainly because of multiple if/else or switch/cases. I need some help re-designing my code for achieving the 2 objectives :
Further modification as per R-P-C-L-S problem.
Support for more than 2 players.
I don't need code, just some guidelines should help.
Thanks !!
EDIT : Seems like I was wrong in thinking that this game can be played by more than 2 players. I am sorry for this mistake, please ignore the second requirement.
In, Rock-Paper-Scissor games, it is easy to decide if move a wins against move b using their index at a cycle. So you don't have to manually decide in your code the result of every combination as other answers here suggest.
For the Rock-Paper-Scissor-Spock-Lizard version:
Let's assign a number to each move (0, 1, 2, 3, 4).
Notice that every move beats two moves:
The move previous to it in the cycle (or four cases ahead)
The move two cases ahead in the cycle
So let d = (5 + a - b) % 5. Then:
d = 1 or d = 3 => a wins
d = 2 or d = 4 => b wins
d = 0 => tie
For the Rock-Paper-Scissor version:
let d = (3 + a - b) % 3. Then:
d = 1 => a wins
d = 2 => b wins
d = 0 => tie
Generalization For n >= 3 and n odd:
Let d = (n + a - b) % n. Then:
If d = 0 => tie
If d % 2 = 1 => a wins
If d % 2 = 0 => b wins
The nature of Rock-Paper-Scissors is such that you have to explicitly handle the case for every possible combination of states. So the number of cases you have to cover increases exponentially with the number of players, and polynomially (with the order of the polynomial being the number of players) with the number of options.
Having said that, Java's enums are good for this kind of thing.
Here's my stab at it:
import java.util.Arrays;
import java.util.List;
enum Result {
WIN, LOSE, DRAW;
}
enum Option {
ROCK(SCISSORS),
PAPER(ROCK),
SCISSORS(PAPER);
private List<Option> beats;
private Option(Option... beats) {
this.beats = Arrays.asList(beats);
}
Result play(Option other) {
if beats.contains(other) {
return Result.WIN;
} else if other.beats.contains(this) {
return Result.LOSE;
} else {
return Result.DRAW;
}
}
}
Adding more cases (Lizard and Spock) is consequently relatively simple. Adding more players would be more complicated; among other things, you'd have to determine what the rules of three-player Rock-Paper-Scissors even are, because I have no idea.
i think: 1 beats 2 or 5 loses to the rest. 2 beats 3 or 1 loses to the rest. 3 beats 4 or 2 loses to rest. 4 beats 5 or 3 loses to the rest. 5 beast 1 or 3 loses to the rest. For 3 players, compare the values of 2 players, then compare the winner vs player 3.
Design an enum Choice (ROCK, PAPER, SCISSORS), where each enum has a Set<Choice> which it wins against.
Have each of your players choose one of the choices.
Iterate through your players, and for each one, iterate over all the other players that are after him in the list of players (for player 0, iterate through players 1, 2, 3, etc; for player 1, iterate through players 2, 3, etc.; ...).
For each match, you have three possibilities:
A beats B (the choice of B is in the set of choices that A beats): increment A's score
A and B have the same choice: do nothing
A doesn't beat B: increment B's score
I suggested a better design in an answer to another post. Have a single switch, and switch over a single encoding of every possible combination of moves, and for an encoding use a positional number system with a base that's a power of 2, so that each digit will map directly to a number of bits, and so that bitwise manipulations are intuitive.
Three bits are sufficient for five choices, and although octal would be ideal, the syntax sucks, so use hex. Each hexadecimal digit then represents one of your five moves, with room to spare. A byte is large enough two encode the simultaneous moves of two players, an int for eight, a long for sixteen. It's straightforward. Follow the link for a code example.
This is a basic logic problem. It is small enough you can do a manual truth table ( or skip ahead to a k-map), minimize and get a solution.
So basically, you need to evaluate first, if it is a draw. Then, you need to evaluate winning relative to other players. Doing this without needing to compare against each user can be a confusing task. Since this only has 5 variables, you can find a minimized solution with a K-map.
You will need to evaluate each user, based on which item they chose with a specific algorithm to determine if they win. Note that with more than 2 players, there can be more than one winner if two people choose the same thing but both beat a 3rd player. Or you can consider that a tie, whatever. I'll assume the former. You should check also that all players didn't choose the same item.
So I've done the first part of the algorithm for you when the user you are evaluating has chosen "rock".
In code, this would look like:
rock=0, paper=0, scissors=0, lizard=0, spock=0, win=0, tie=0
if ( someone chose rock ) rock=1
if ( someone chose paper ) paper=1
if ( someone chose scissors ) scissors=1
if ( someone chose lizard ) lizard=1
if ( someone chose spock ) spock=1
// Check if tie / draw, double check these, but I think I got them all
tie=rock && !paper && spock && lizard || rock && paper && scissors ||
rock && paper && lizard || spock && paper && scissors ||
spock && !rock && paper && lizard || !spock && scissors && lizard && paper
if ( tie ) die()
CheckIfUserWins() {
if ( user chose rock ) {
win=rock && !paper && !spock
if ( user chose paper) {
// .... calculate using k-map and fill in
}
return win
Notice that win=rock && !paper && !spock is exactly what would be expected based on the graphic of what beats what at the link you provided. So you can go to that graphic and pretty quickly fill in the rest of the equations.
This solution is not dependent on any number of players other than to say "someone chose X". So it should scale to > 5 players, etc.
The shortest way:
var n = 5; // Rock, Paper, Scissors, Lizard-Spock
function calculate(x, y, n) {
return 1 - ((n + x - y) % n) % 2;
}
function getWinner(p1Gestrure, p2Guesture) {
if(p1Gestrure === p2Guesture) {
return - 1; // tie
}
return this.calculate(p1Gestrure, p2Guesture); // 0: win for p1. 1: win for p2.
}
I've created a cli game, please feel free to take a look there.
https://github.com/julianusti/rpc-es6

Solving a simple maximization game

I've got a very simple question about a game I created (this is not homework): what should the following method contain to maximize payoff:
private static boolean goForBiggerResource() {
return ... // I must fill this
};
Once again I stress that this is not homework: I'm trying to understand what is at work here.
The "strategy" is trivial: there can only be two choices: true or false.
The "game" itself is very simple:
P1 R1 R2 P2
R5
P3 R3 R4 P4
there are four players (P1, P2, P3 and P4) and five resources (R1, R2, R3, R4 all worth 1 and R5, worth 2)
each player has exactly two options: either go for a resource close to its starting location that gives 1 and that the player is sure to get (no other player can get to that resource first) OR the player can try to go for a resource that is worth 2... But other players may go for it too.
if two or more players go for the bigger resource (the one worth 2), then they'll arrive at the bigger resource at the same time and only one player, at random, will get it and the other player(s) going for that resource will get 0 (they cannot go back to a resource worth 1).
each player play the same strategy (the one defined in the method goForBiggerResource())
players cannot "talk" to each other to agree on a strategy
the game is run 1 million times
So basically I want to fill the method goForBiggerResource(), which returns either true or false, in a way to maximize the payoff.
Here's the code allowing to test the solution:
private static final int NB_PLAYERS = 4;
private static final int NB_ITERATIONS = 1000000;
public static void main(String[] args) {
double totalProfit = 0.0d;
for (int i = 0; i < NB_ITERATIONS; i++) {
int nbGoingForExpensive = 0;
for (int j = 0; j < NB_PLAYERS; j++) {
if ( goForBiggerResource() ) {
nbGoingForExpensive++;
} else {
totalProfit++;
}
}
totalProfit += nbGoingForExpensive > 0 ? 2 : 0;
}
double payoff = totalProfit / (NB_ITERATIONS * NB_PLAYERS);
System.out.println( "Payoff per player: " + payoff );
}
For example if I suggest the following solution:
private static boolean goForBiggerResource() {
return true;
};
Then all four players will go for the bigger resource. Only one of them will get it, at random. Over one million iteration the average payoff per player will be 2/4 which gives 0.5 and the program shall output:
Payoff per player: 0.5
My question is very simple: what should go into the method goForBiggerResource() (which returns either true or false) to maximize the average payoff and why?
Since each player uses the same strategy described in your goForBiggerResource method, and you try to maximize the overall payoff, the best strategy would be three players sticking with the local resource and one player going for the big game. Unfortunately since they can not agree on a strategy, and I assume no player can not be distinguished as a Big Game Hunter, things get tricky.
We need to randomize whether a player goes for the big game or not. Suppose p is the probability that he goes for it. Then separating the cases according to how many Big Game Hunters there are, we can calculate the number of cases, probabilities, payoffs, and based on this, expected payoffs.
0 BGH: (4 choose 0) cases, (1-p)^4 prob, 4 payoff, expected 4(p^4-4p^3+6p^2-4p+1)
1 BGH: (4 choose 1) cases, (1-p)^3*p prob, 5 payoff, expected 20(-p^4+3p^3-3p^2+p)
2 BGH: (4 choose 2) cases, (1-p)^2*p^2 prob, 4 payoff, expected 24(p^4-2p^3+p^2)
3 BGH: (4 choose 3) cases, (1-p)*p^3 prob, 3 payoff, expected 12(-p^4+p^3)
4 BGH: (4 choose 4) cases, p^4 prob, 2 payoff, expected 2(p^4)
Then we need to maximize the sum of the expected payoffs. Which is -2p^4+8p^3-12p^2+4p+4 if I calculated correctly. Since the first term is -2 < 0, it is a concave function, and hopefully one of the roots to its derivative, -8p^3+24p^2-24p+4, will maximize the expected payoffs. Plugging it into an online polynomial solver, it returns three roots, two of them complex, the third being p ~ 0.2062994740159. The second derivate is -24p^2+48p-24 = 24(-p^2+2p-1) = -24(p-1)^2, which is < 0 for all p != 1, so we indeed found a maximum. The (overall) expected payoff is the polynomial evaluated at this maximum, around 4.3811015779523, which is a 1.095275394488075 payoff per player.
Thus the winning method is something like this
private static boolean goForBiggerResource ()
{
return Math.random() < 0.2062994740159;
}
Of course if players can use different strategies and/or play against each other, it's an entirely different matter.
Edit: Also, you can cheat ;)
private static int cheat = 0;
private static boolean goForBiggerResource ()
{
cheat = (cheat + 1) % 4;
return cheat == 0;
}
I take it you tried the following:
private static boolean goForBiggerResource() {
return false;
};
where none of the player try to go for the resource that is worth 2. They are hence guaranteed to each get a resource worth 1 every time hence:
Payoff per player: 1.0
I suppose also that if you ask this nice question is because you guess there's a better answer.
The trick is that you need what is called a "mixed strategy".
EDIT: ok here I come with a mixed-strategy... I don't get how Patrick found the 20% that fast (when he commented, only minutes after you posted your question) but, yup, I found out basically that same value too:
private static final Random r = new Random( System.nanoTime() );
private static boolean goForBiggerResource() {
return r.nextInt(100) < 21;
}
Which gives, for example:
Payoff per player: 1.0951035
Basically if I'm not mistaken you want to read the Wikipedia page on the "Nash equilibrium" and particularly this:
"Nash Equilibrium is defined in terms of mixed strategies, where players choose a probability distribution over possible actions"
Your question/simple example if I'm not mistaken also can be used to show why colluding players can do better average payoffs: if players could colude, they'd get 1.25 on average, which beats the 1.095 I got.
Also note that my answers contains approximation errors (I only check random numbers from 0 to 99) and depends a bit on the Random PRNG but you should get the idea.
if the players cannot cooperate and have no memory there is only one possible way to implement goForBiggerResource: choose a value randomly. Now the question is what is the best rate to use.
Now simple mathematics (not really programming related):
assume the rate x represents the probability to stay with the small resource;
therefore the chance for no player going for the big one is x^4;
so the chance for at least one player going to the big one is 1-x^4;
total profit is x + ( 1 - x^4 ) / 2
find the maximum of that formula for 0% <= x <= 100%
the result is about 79.4% (for returning false)
Mmm, I think your basic problem is that the game as described is trivial. In all cases, the optimal strategy is to stick with the local resource, because the expected payoff for going for R5 is only 0.5 (1/4 * 2). Raise the reward for R5 to 4, and it becomes even; there's no better strategy. reward(R5)>4 and it always pays to take R5.

Categories

Resources