Added player chance calculations. In the middle of stream, changing computers.

This commit is contained in:
Jonathan Bernard 2010-07-05 23:40:23 -05:00
parent 2ea56d05c5
commit 242fbf2e56
8 changed files with 214 additions and 106 deletions

View File

@ -1,6 +1,6 @@
#Sun Jul 04 00:39:50 CDT 2010 #Mon Jul 05 23:10:39 CDT 2010
app.version=0.3.0 app.version=0.3.0
build.number=47
src.dir=src src.dir=src
build.number=4
build.dir=build build.dir=build
app.name=team-maker app.name=team-maker

View File

@ -0,0 +1,86 @@
package com.jdbernard.teammaker
public class OddsCalculator {
Map odds = [:]
public void recalculate(List players, int spotsOpen, PlayerChooser chooser) {
// reset computed player odds
odds = [:]
/* this list will contain each possible selection path with
* the odds at each node. It is a list of lists of lists::
*
* oddsList: [
* path: [
* node: [player, odds]
* node: [player, odds]
* ]
* path: [
* node: [player, odds]
* node: [player, odds]
* ]
* ]
*/
def oddsList = buildOdds([], players, spotsOpen)
/* The oddsMap is a condensed version of the oddsList, calculating
* the overall probability of each path::
*
* oddsMap: [
* [player, player]: pathOdds,
* [player, player]: pathOdds
* ]
*/
def oddsMap = [:]
// calculate the overall odds for each path
oddsList.each { path ->
def pathPlayers = []
float pathOdds = 1f
// the overall odds of the path is the product of the odds for
// each node in the path
path.each { node ->
pathPlayers << node[0] // player
pathOdds *= node[1] // node odds
}
oddsMap[(pathPlayers)] = pathOdds
}
// now we calculate the overall odds for each player. The odds for a
// player is the sum of the odds of each path that contains that player
// (since that means he was picked for one of the nodes, ie., open
// spots)
players.each { player ->
float playerOdds = 0f
// check each path in the map
oddsMap.each { pathPlayers, pathOdds ->
// if we are in this path, add that probability to our own
if (pathPlayers.contains(player))
playerOdds += pathOdds
}
odds[(player)] = playerOdds
}
}
protected List buildOdds(List chosenPath, List players, int spotsOpen) {
def oddsList = []
if (spotsOpen == 0) return [chosenPath]
players.each { player ->
def odds = chooser.getOdds(players, player)
def newPath = chosenPath.clone()
newPath << [player, odds]
oddsList.addAll(buildOdds(newPath, players - [player], spotsOpen - 1))
}
return oddsList
}
}

View File

@ -1,14 +1,13 @@
package com.jdbernard.teammaker package com.jdbernard.teammaker
public abstract class PlayerChooser { public abstract class PlayerChooser {
protected static final Random rand = new Random(System.currentTimeMillis())
public abstract Player choose (def players) public abstract Player choose (def players)
public abstract float calculateOdds(def players, def player) public abstract float getOdds(def players, Player player)
public final static Random = new Random(System.currentTimeMillis()) protected Player chooseRandomly(List choices) {
public static final Player chooseRandomly(def choices) {
choices.sort { rand.nextInt() } choices.sort { rand.nextInt() }
return choices[0]
} }
} }

View File

@ -1,33 +1,35 @@
package com.jdbernard.teammaker package com.jdbernard.teammaker
import java.awt.BorderLayout
import java.awt.Color import java.awt.Color
import java.awt.Component import java.awt.Component
import javax.swing.EmptyBorder
import javax.swing.JLabel import javax.swing.JLabel
import javax.swing.JList import javax.swing.JList
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.LineBorder
import javax.swing.ListCellRenderer import javax.swing.ListCellRenderer
import javax.swing.BorderFactory
public class PlayerListCellRenderer extends JPanel implements ListCellRenderer { public class PlayerListCellRenderer extends JPanel implements ListCellRenderer {
boolean showStats boolean showStats
boolean colored boolean colored
private PlayerChooser chooser private TeamMaker teamMaker
private JLabel nameLabel private JLabel nameLabel
private JLabel statsLabel private JLabel statsLabel
private static def linedBorder = new LineBorder(Color.BLACK)
private static def emptyBorder = new EmptyBorder(1) private static def linedBorder = BorderFactory.createLineBorder(Color.BLACK)
private static def emptyBorder = BorderFactory.createEmptyBorder()
private def colors = [Color.getHSBColor(0.32f, 1f, 1f), private def colors = [Color.getHSBColor(0.32f, 1f, 1f),
Color.getHSBColor(0.24f,1f,1f), Color.getHSBColor(0.16f, 1f, 1f), Color.getHSBColor(0.24f,1f,1f), Color.getHSBColor(0.16f, 1f, 1f),
Color.getHSBColor(0.08f, 1f, 1f), Color.getHSBColor(0.0f, 1f, 1f)] Color.getHSBColor(0.08f, 1f, 1f), Color.getHSBColor(0.0f, 1f, 1f)]
public PlayerListCellRenderer(Map params) { public PlayerListCellRenderer(Map params, def tm) {
teamMaker = tm
showStats = params.showStats ?: false showStats = params.showStats ?: false
colored = params.colored ?: false colored = params.colored ?: false
chooser = params.chooser ?: WeightedChooser.getInstance()
setLayout(new BorderLayout()) setLayout(new BorderLayout())
setOpaque(colored) setOpaque(colored)
@ -39,7 +41,7 @@ public class PlayerListCellRenderer extends JPanel implements ListCellRenderer {
if (showStats) { if (showStats) {
statsLabel = new JLabel() statsLabel = new JLabel()
statsLabel.horizontalAlignment = JLabel.TRAILING, statsLabel.horizontalAlignment = JLabel.TRAILING
statsLabel.opaque = false statsLabel.opaque = false
add(statsLabel, BorderLayout.EAST) add(statsLabel, BorderLayout.EAST)
} }
@ -55,8 +57,11 @@ public class PlayerListCellRenderer extends JPanel implements ListCellRenderer {
if (isSelected) setBorder(linedBorder) if (isSelected) setBorder(linedBorder)
else setBorder(emptyBorder) else setBorder(emptyBorder)
if (showStats) if (showStats) {
statsLabel.text = value.gamesSat int odds = Math.round(teamMaker.playerChooser.calculateOdds(
list.model, teamMaker.spotsOpen, value) * 100)
statsLabel.text = "${value.gamesSat} (${odds}%)"
}
if (colored) { if (colored) {
def c = colors[Math.min(4, value.gamesSat)] def c = colors[Math.min(4, value.gamesSat)]
@ -65,4 +70,5 @@ public class PlayerListCellRenderer extends JPanel implements ListCellRenderer {
return this return this
} }
} }

View File

@ -1,14 +0,0 @@
package com.jdbernard.teammaker
public class RandomChooser extends PlayerChooser {
public Player choose(def players) {
def choices = []
players.each { choices << it }
chooseRandomly(choices)
}
public float calculateOdds(def players, def player) {
return 1f / (float) players
}
}

View File

@ -0,0 +1,14 @@
package com.jdbernard.teammaker
public class RandomPlayerChooser extends AbstractPlayerChooser {
public Player choose(def players) {
def choices = []
players.each { choices << it }
return chooseRandomly(choices)
}
public float getOdds(def players, Player player) {
return 1f / (float) players.size()
}
}

View File

@ -14,7 +14,7 @@ public class TeamMaker {
/* ======== MODEL ======== */ /* ======== MODEL ======== */
public static def swing = new SwingBuilder() public static def swing = new SwingBuilder()
public static final String version = "0.2" public static final String version = "0.3"
def frame def frame
def team1List def team1List
@ -24,10 +24,13 @@ public class TeamMaker {
def newGameButton def newGameButton
def sittingList def sittingList
PlayerChooser chooser = new RandomChooser() AbstractPlayerChooser playerChooser = GameWeightedChooser.getInstance()
@Bindable boolean inGame = false class Observables {
@Bindable int teamSize = 4 @Bindable boolean inGame = false
@Bindable int teamSize = 4
}
def model = new Observables()
@ -77,8 +80,8 @@ public class TeamMaker {
label('Team B', constraints: gbc(gridx: 1, gridy: 0, label('Team B', constraints: gbc(gridx: 1, gridy: 0,
fill: GBC.BOTH, insets: [5, 5, 0, 5])) fill: GBC.BOTH, insets: [5, 5, 0, 5]))
def teamListRenderer = new PlayerListCellRenderer(showStats: false, def teamListRenderer = new PlayerListCellRenderer(this,
colored: false) showStats: false, colored: false)
team1List = list(cellRenderer: teamListRenderer, team1List = list(cellRenderer: teamListRenderer,
constraints: gbc(gridx: 0, gridy: 1, fill: GBC.BOTH, constraints: gbc(gridx: 0, gridy: 1, fill: GBC.BOTH,
@ -88,20 +91,8 @@ public class TeamMaker {
team1WinsButton = button('Team A Wins', team1WinsButton = button('Team A Wins',
constraints: gbc(gridx: 0, gridy: 2, anchor: constraints: gbc(gridx: 0, gridy: 2, anchor:
GBC.CENTER, insets: [5, 5, 5, 0]), GBC.CENTER, insets: [5, 5, 5, 0]),
enabled: false, enabled: bind { model.inGame },
actionPerformed: { actionPerformed: { declareWinner(team1List)})
sittingList.model.each { it.gamesSat++ }
team1List.model.each { player ->
player.gamesSat = 0
sittingList.model.addElement(player)
}
team1List.model.clear()
team1List.repaint()
sittingList.repaint()
inGame = false
team1WinsButton.enabled = false
team2WinsButton.enabled = false
})
team2List = list(cellRenderer: teamListRenderer, team2List = list(cellRenderer: teamListRenderer,
constraints: gbc(gridx: 1, gridy: 1, fill: GBC.BOTH, constraints: gbc(gridx: 1, gridy: 1, fill: GBC.BOTH,
@ -111,20 +102,8 @@ public class TeamMaker {
team2WinsButton = button('Team B Wins', team2WinsButton = button('Team B Wins',
constraints: gbc(gridx: 1, gridy: 2, constraints: gbc(gridx: 1, gridy: 2,
anchor: GBC.CENTER, insets: [5, 5, 5, 5]), anchor: GBC.CENTER, insets: [5, 5, 5, 5]),
enabled: false, enabled: bind { model.inGame },
actionPerformed: { actionPerformed: { declareWinner(team2List)} )
sittingList.model.each { it.gamesSat++ }
team2List.model.each { player ->
player.gamesSat = 0
sittingList.model.addElement(player)
}
team2List.model.clear()
team2List.repaint()
sittingList.repaint()
inGame = false
team1WinsButton.enabled = false
team2WinsButton.enabled = false
})
} }
@ -133,25 +112,18 @@ public class TeamMaker {
border: titledBorder(title: 'Sitting Players')) { border: titledBorder(title: 'Sitting Players')) {
sittingList = list(cellRenderer: new PlayerListCellRenderer( sittingList = list(cellRenderer: new PlayerListCellRenderer(
showStats: true, colored: true), this, showStats: true, colored: true),
model: new SortedPlayerModel()) model: new SortedPlayerModel())
} }
button('Next Game', constraints: gbc(gridx: 0, gridy: 1, button('Next Game', constraints: gbc(gridx: 0, gridy: 1,
anchor: GBC.CENTER, insets: [5, 5, 5, 0]), anchor: GBC.CENTER, insets: [5, 5, 5, 0]),
enabled: bind { !model.inGame},
actionPerformed: { newGame() }) actionPerformed: { newGame() })
button('Add Player', constraints: gbc(gridx: 1, gridy: 1, button('Add Player', constraints: gbc(gridx: 1, gridy: 1,
anchor: GBC.CENTER, insets: [5, 5, 5, 0]), anchor: GBC.CENTER, insets: [5, 5, 5, 0]),
actionPerformed: { actionPerformed: { addPlayer() })
def name = JOptionPane.showInputDialog(frame,
"Enter the new player's name: ", "New Player...",
JOptionPane.QUESTION_MESSAGE)
def player = new Player()
player.name = name
player.gamesSat = 0
sittingList.model.addElement(player)
})
newGameButton = button('Delete Player', newGameButton = button('Delete Player',
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.CENTER, constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.CENTER,
@ -178,41 +150,58 @@ public class TeamMaker {
init() init()
} }
private void newGame() { public int getSpotsOpen() {
if (team1List.model.size() < teamSize) def spots = (model.teamSize - team1List.model.size()) +
populate(team1List, sittingList, teamSize) (model.teamSize - team2List.model.size())
return (spots == 0 ? model.teamSize : spots)
if (team2List.model.size() < teamSize)
populate(team2List, sittingList, teamSize)
team1WinsButton.enabled = true
team2WinsButton.enabled = true
newGameButton.enabled = false
} }
private static void populate(def teamList, def sittingList, int teamSize) { private void addPlayer() {
while (teamList.model.size() < teamSize) { def name = JOptionPane.showInputDialog(frame,
def player = choosePlayer(sittingList) "Enter the new player's name: ", "New Player...",
JOptionPane.QUESTION_MESSAGE)
if (!name) return
def player = new Player()
player.name = name
player.gamesSat = 0
sittingList.model.addElement(player)
}
private void newGame() {
if (team1List.model.size() < model.teamSize)
populate(team1List)
if (team2List.model.size() < model.teamSize)
populate(team2List)
model.inGame = true
//team1WinsButton.enabled = true
//team2WinsButton.enabled = true
//newGameButton.enabled = false
}
private void declareWinner(def teamList) {
sittingList.model.each { it.gamesSat++ }
teamList.model.each { player ->
player.gamesSat = 0
sittingList.model.addElement(player)
}
teamList.model.clear()
teamList.repaint()
sittingList.repaint()
model.inGame = false
//team1WinsButton.enabled = false
//team2WinsButton.enabled = false
}
private void populate(def teamList) {
while (teamList.model.size() < model.teamSize) {
def player = playerChooser.choose(sittingList.model)
teamList.model.addElement(player) teamList.model.addElement(player)
sittingList.model.removeElement(player) sittingList.model.removeElement(player)
} }
} }
private static Player choosePlayer(def list) {
def choices = []
list.model.each { player ->
player.gamesSat.times { choices << player }
}
// if no players have sat at least one game, add all once
if (choices.size() == 0) list.model.each { player -> choices << player }
choices.sort { rand.nextInt() }
println choices
return choices[0]
}
private float calculatePercentage(Player p, def sittingList) {
}
} }

View File

@ -0,0 +1,28 @@
package com.jdbernard.teammaker
public abstract class WeightedChooser extends AbstractPlayerChooser {
int hardLimit = 5
public Player choose(def players) {
def choices = []
// find sitting threshold (longest sat - hard limit)
int threshold = (player.max { it.gamesSat }) - hardLimit
// add players, ignoring those past the hard limit
players.each { player ->
if (player.gamesSat >= threshold) {
choices << player
player.gamesSat.times { choices << player }
}
}
return chooseRandomly(choices)
}
public float getOdds(def players, Player player) {
def playerGames = player.gamesSat + 1
def totalGames = players.sum { it.gamesSat + 1 }
return (float) playerGames / (float) totalGames
}
}