added many tournament parameters and generic computing of base criterion.

This commit is contained in:
Theo Barollet
2023-06-08 00:29:11 +02:00
parent e846ce383c
commit 2378c51a05
7 changed files with 340 additions and 96 deletions

View File

@@ -10,10 +10,15 @@ import kotlin.math.roundToInt
// Pairable
sealed class Pairable(val id: ID, val name: String, open val rating: Int, open val rank: Int) {
companion object {}
companion object {
val MIN_RANK: Int = -30 // 30k
}
abstract fun toJson(): Json.Object
abstract val club: String?
abstract val country: String?
open fun nameSeed(): String {
return name
}
val skip = mutableSetOf<Int>() // skipped rounds
}
@@ -71,6 +76,9 @@ class Player(
).also {
if (skip.isNotEmpty()) it["skip"] = Json.Array(skip)
}
override fun nameSeed(): String {
return name + firstname
}
}
fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Player(

View File

@@ -2,47 +2,134 @@ package org.jeudego.pairgoth.model
import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.ext.OpenGothaFormat
import org.jeudego.pairgoth.model.Pairing.PairingType.*
import org.jeudego.pairgoth.model.MacMahon
import org.jeudego.pairgoth.model.RoundRobin
import org.jeudego.pairgoth.model.Swiss
import org.jeudego.pairgoth.pairing.MacMahonSolver
import org.jeudego.pairgoth.pairing.SwissSolver
import java.util.Random
sealed class Pairing(val type: PairingType, val weights: Weights = Weights()) {
// Below are some constants imported from opengotha
/**
* Max value for BaAvoidDuplGame.
* In order to be compatible with max value of long (8 * 10^18),
* with max number of games (8000),
* with relative weight of this parameter (1/2)
*BA_MAX_AVOIDDUPLGAME should be strictly limited to 5 * 10^14
*/
private const val BA_MAX_AVOIDDUPLGAME: Long = 500000000000000L // 5e14
/**
* Max value for BaRandom.
* Due to internal coding,
* BA_MAX_RANDOM should be strictly limited to 2 * 10^9
*/
private const val BA_MAX_RANDOM: Long = 1000000000L // 2e9
private const val BA_MAX_BALANCEWB: Long = 1000000L // 1e6
private const val MA_MAX_AVOID_MIXING_CATEGORIES: Double = 2e13
// Ratio between MA_MAX_MINIMIZE_SCORE_DIFFERENCE and MA_MAX_AVOID_MIXING_CATEGORIES should stay below 1/ nbcat^2
private const val MA_MAX_MINIMIZE_SCORE_DIFFERENCE: Double = 1e11
private const val MA_MAX_DUDD_WEIGHT: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 1000; // Draw-ups Draw-downs
enum class MA_DUDD {TOP, MID, BOT}
private const val MA_MAX_MAXIMIZE_SEEDING: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE / 20000;
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
sealed class Pairing(val type: PairingType, val pairingParams: PairingParams = PairingParams()) {
companion object {}
enum class PairingType { SWISS, MACMAHON, ROUNDROBIN }
data class Weights(
val played: Double = 1_000_000.0, // players already met
val group: Double = 100_000.0, // different group
val handicap: Double = 50_000.0, // for each handicap stone
val score: Double = 10_000.0, // per difference of score or MMS
val place: Double = 1_000.0, // per difference of expected position for Swiss
val color: Double = 500.0, // per color unbalancing
val club: Double = 100.0, // same club weight
val country: Double = 50.0 // same country
enum class PairingType { SWISS, MAC_MAHON, ROUND_ROBIN }
data class PairingParams(
// Standard NX1 factor ( = Rather N X 1 than 1 X N)
val standardNX1Factor: Double = 0.5,
// Base criteria
val baseAvoidDuplGame: Long = BA_MAX_AVOIDDUPLGAME,
val baseRandom: Long = 0,
val baseDeterministic: Boolean = true,
val baseBalanceWB: Long = BA_MAX_BALANCEWB,
// Main criteria
// TODO move avoidmixingcategories to swiss with category
//val maAvoidMixingCategories: Double = MA_MAX_AVOID_MIXING_CATEGORIES,
val mainMinimizeScoreDifference: Double = MA_MAX_MINIMIZE_SCORE_DIFFERENCE,
val maDUDDWeight: Double = MA_MAX_DUDD_WEIGHT,
val maCompensateDUDD: Boolean = true,
val maDUDDUpperMode: MA_DUDD = MA_DUDD.MID,
val maDUDDLowerMode: MA_DUDD = MA_DUDD.MID,
val maMaximizeSeeding: Double = MA_MAX_MAXIMIZE_SEEDING, // 5 *10^6
val maLastRoundForSeedSystem1: Int = 1,
val maSeedSystem1: SeedMethod = SeedMethod.SPLIT_AND_RANDOM,
val maSeedSystem2: SeedMethod = SeedMethod.SPLIT_AND_FOLD,
// TODO get these parameters from Placement parameters
//val maAdditionalPlacementCritSystem1: Int = PlacementParameterSet.PLA_CRIT_RATING,
//val maAdditionalPlacementCritSystem2: Int = PlacementParameterSet.PLA_CRIT_NUL,
// Secondary criteria
val seBarThresholdActive: Boolean = true, // Do not apply secondary criteria for players above bar
val seRankThreshold: Int = 0, // Do not apply secondary criteria above 1D rank
val seNbWinsThresholdActive: Boolean = true, // Do not apply secondary criteria when nbWins >= nbRounds / 2
val seDefSecCrit: Double = MA_MAX_AVOID_MIXING_CATEGORIES, // Should be MA_MAX_MINIMIZE_SCORE_DIFFERENCE for MM, MA_MAX_AVOID_MIXING_CATEGORIES for others
// Geographical params
val geo: GeographicalParams = GeographicalParams(avoidSameGeo = seDefSecCrit),
// Handicap related settings
val hd: HandicapParams = HandicapParams(minimizeHandicap = seDefSecCrit),
)
abstract fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game>
}
data class GeographicalParams(
val avoidSameGeo: Double, // Should be SeDefSecCrit for SwCat and MM, 0 for Swiss
val preferMMSDiffRatherThanSameCountry: Int = 1, // Typically = 1
val preferMMSDiffRatherThanSameClubsGroup: Int = 2, // Typically = 2
val preferMMSDiffRatherThanSameClub: Int = 3, // Typically = 3
) {
companion object {
fun disabled() = GeographicalParams(avoidSameGeo = 0.0)
}
}
data class HandicapParams(
// minimizeHandicap is a secondary criteria but moved here
val minimizeHandicap: Double, // Should be paiSeDefSecCrit for SwCat, 0 for others
val basedOnMMS: Boolean = true, // if hdBasedOnMMS is false, hd will be based on rank
// When one player in the game has a rank of at least hdNoHdRankThreshold,
// then the game will be without handicap
val noHdRankThreshold: Int = 0, // 0 is 1d
val correction: Int = 1, // Handicap will be decreased by hdCorrection
val ceiling: Int = 9, // Possible values are between 0 and 9
) {
companion object {
fun disabled() = HandicapParams(
minimizeHandicap = 0.0,
basedOnMMS = false,
noHdRankThreshold=-30, // 30k
ceiling=0)
}
}
fun Tournament<*>.historyBefore(round: Int) =
if (lastRound() == 0) emptyList()
else (0 until round).flatMap { games(round).values }
class Swiss(
var method: Method,
var firstRoundMethod: Method = method,
): Pairing(SWISS, Weights(
handicap = 0.0, // no handicap games anyway
club = 0.0,
country = 0.0
class Swiss(): Pairing(SWISS, PairingParams(
maSeedSystem1 = SeedMethod.SPLIT_AND_SLIP,
maSeedSystem2 = SeedMethod.SPLIT_AND_SLIP,
seBarThresholdActive = true, // not relevant
seRankThreshold = -30,
seNbWinsThresholdActive = true, // not relevant
seDefSecCrit = MA_MAX_AVOID_MIXING_CATEGORIES,
geo = GeographicalParams.disabled(),
hd = HandicapParams.disabled(),
)) {
enum class Method { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
val actualMethod = if (round == 1) firstRoundMethod else method
return SwissSolver(tournament.historyBefore(round), pairables, weights, actualMethod).pair()
return SwissSolver(tournament.historyBefore(round), pairables, pairingParams).pair()
}
}
@@ -50,39 +137,64 @@ class MacMahon(
var bar: Int = 0,
var minLevel: Int = -30,
var reducer: Int = 1
): Pairing(MACMAHON) {
): Pairing(MAC_MAHON, PairingParams(seDefSecCrit = MA_MAX_MINIMIZE_SCORE_DIFFERENCE)) {
val groups = mutableListOf<Int>()
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
return MacMahonSolver(tournament.historyBefore(round), pairables, weights, mmBase = minLevel, mmBar = bar, reducer = reducer).pair()
return MacMahonSolver(tournament.historyBefore(round), pairables, pairingParams, mmBase = minLevel, mmBar = bar, reducer = reducer).pair()
}
}
class RoundRobin: Pairing(ROUNDROBIN) {
class RoundRobin: Pairing(ROUND_ROBIN) {
override fun pair(tournament: Tournament<*>, round: Int, pairables: List<Pairable>): List<Game> {
TODO()
}
}
// Serialization
// TODO failing on serialization
fun HandicapParams.toJson() = Json.Object(
"minimize_hd" to minimizeHandicap,
"mms_based" to basedOnMMS,
"no_hd_thresh" to noHdRankThreshold,
"correction" to correction,
"ceiling" to ceiling, )
fun HandicapParams.fromJson(json: Json.Object) = HandicapParams(
minimizeHandicap=json.getDouble("minimize_hd")!!,
basedOnMMS=json.getBoolean("mms_based")!!,
noHdRankThreshold=json.getInt("no_hd_thresh")!!,
correction=json.getInt("correction")!!,
ceiling=json.getInt("ceiling")!!,
)
fun GeographicalParams.toJson() = Json.Object(
"avoid_same_geo" to avoidSameGeo,
"country" to preferMMSDiffRatherThanSameCountry,
"club_group" to preferMMSDiffRatherThanSameClubsGroup,
"club" to preferMMSDiffRatherThanSameClub,)
fun GeographicalParams.fromJson(json: Json.Object) = GeographicalParams(
avoidSameGeo=json.getDouble("avoid_same_geo")!!,
preferMMSDiffRatherThanSameCountry=json.getInt("country")!!,
preferMMSDiffRatherThanSameClubsGroup=json.getInt("club_group")!!,
preferMMSDiffRatherThanSameClub=json.getInt("club")!!,
)
fun Pairing.Companion.fromJson(json: Json.Object) = when (json.getString("type")?.let { Pairing.PairingType.valueOf(it) } ?: badRequest("missing pairing type")) {
SWISS -> Swiss(
method = json.getString("method")?.let { Swiss.Method.valueOf(it) } ?: badRequest("missing pairing method"),
firstRoundMethod = json.getString("firstRoundMethod")?.let { Swiss.Method.valueOf(it) } ?: json.getString("method")!!.let { Swiss.Method.valueOf(it) }
)
MACMAHON -> MacMahon(
SWISS -> Swiss()
MAC_MAHON -> MacMahon(
bar = json.getInt("bar") ?: 0,
minLevel = json.getInt("minLevel") ?: -30,
reducer = json.getInt("reducer") ?: 1
)
ROUNDROBIN -> RoundRobin()
ROUND_ROBIN -> RoundRobin()
}
fun Pairing.toJson() = when (this) {
is Swiss ->
if (method == firstRoundMethod) Json.Object("type" to type.name, "method" to method.name)
else Json.Object("type" to type.name, "method" to method.name, "firstRoundMethod" to firstRoundMethod.name)
Json.Object("type" to type.name, "geo" to pairingParams.geo.toJson(), "hd" to pairingParams.hd.toJson())
is MacMahon -> Json.Object("type" to type.name, "bar" to bar, "minLevel" to minLevel, "reducer" to reducer)
is RoundRobin -> Json.Object("type" to type.name)
}

View File

@@ -72,7 +72,7 @@ sealed class Tournament <P: Pairable>(
// standings criteria
val criteria = mutableListOf<Criterion>(
if (pairing.type == Pairing.PairingType.MACMAHON) Criterion.MMS else Criterion.NBW,
if (pairing.type == Pairing.PairingType.MAC_MAHON) Criterion.MMS else Criterion.NBW,
Criterion.SOS,
Criterion.SOSOS
)

View File

@@ -21,9 +21,14 @@ open class HistoryHelper(protected val history: List<Game>) {
}).toSet()
}
// Returns the number of games played as white
// Only count games without handicap
private val colorBalance: Map<Int, Int> by lazy {
history.flatMap { game ->
history.flatMap { game -> if (game.handicap == 0) {
listOf(Pair(game.white, +1), Pair(game.black, -1))
} else {
listOf(Pair(game.white, 0), Pair(game.black, 0))
}
}.groupingBy { it.first }.fold(0) { acc, next ->
acc + next.second
}

View File

@@ -3,43 +3,27 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.Pairing
import org.jeudego.pairgoth.model.Swiss.Method.*
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.sign
class MacMahonSolver(history: List<Game>, pairables: List<Pairable>, weights: Pairing.Weights, val mmBase: Int, val mmBar: Int, val reducer: Int): Solver(history, pairables, weights) {
class MacMahonSolver(history: List<Game>, pairables: List<Pairable>, pairingParams: Pairing.PairingParams, val mmBase: Int, val mmBar: Int, val reducer: Int): Solver(history, pairables, pairingParams) {
val Pairable.mms get() = mmBase + score
// CB TODO - configurable criteria
override fun mainCriterion(p1: Pairable): Int {
TODO("Not yet implemented")
}
override fun mainCriterionMinMax(): Pair<Int, Int> {
TODO("Not yet implemented")
}
override fun sort(p: Pairable, q: Pairable): Int =
if (p.mms != q.mms) ((q.mms - p.mms) * 1000).toInt()
else if (p.sos != q.sos) ((q.sos - p.sos) * 1000).toInt()
else if (p.sosos != q.sosos) ((q.sosos - p.sosos) * 1000).toInt()
else 0
override fun weight(black: Pairable, white: Pairable): Double {
var weight = 0.0
if (black.played(white)) weight += weights.played
if (black.club == white.club) weight += weights.club
if (black.country == white.country) weight += weights.country
weight += (abs(black.colorBalance + 1) + abs(white.colorBalance - 1)) * weights.color
// MacMahon specific
weight += Math.abs(black.mms - white.mms) * weights.score
if (sign(mmBar - black.mms) != sign(mmBar - white.mms)) weight += weights.group
if (black.mms < mmBar && white.mms < mmBar && abs(black.mms - white.mms) > reducer) {
if (black.mms > white.mms) weight = Double.NaN
else weight = handicap(black, white) * weights.handicap
}
return weight
}
override fun handicap(black: Pairable, white: Pairable) =
if (black.mms > mmBar || white.mms > mmBar || abs(black.mms - white.mms) < reducer || black.mms > white.mms) 0
else (white.mms - black.mms - reducer).roundToInt()
}

View File

@@ -1,7 +1,6 @@
package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.Game.Result.*
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.Pairing
import org.jeudego.pairgoth.model.TeamTournament
@@ -10,27 +9,166 @@ import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching
import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense
import org.jgrapht.graph.DefaultWeightedEdge
import org.jgrapht.graph.SimpleDirectedWeightedGraph
import org.jgrapht.graph.SimpleWeightedGraph
import org.jgrapht.graph.builder.GraphBuilder
import java.util.*
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
interface HistoryDigester {
val colorBalance: Map<Int, Int>
val score: Map<Int, Double>
val sos: Map<Int, Double>
val sosos: Map<Int, Double>
val sodos: Map<Int, Double>
private fun detRandom(max: Long, p1: Pairable, p2: Pairable): Long {
var nR: Long = 0
var inverse = false
val seed1 = p1.nameSeed()
val seed2 = p2.nameSeed()
var name1 = seed1
var name2 = seed2
if (name1 > name2) {
name1 = name2.also { name2 = name1 }
inverse = true
}
val s = name1 + name2
for (i in s.indices) {
val c = s[i]
nR += (c.code * (i + 1)).toLong()
}
nR = nR * 1234567 % (max + 1)
if (inverse) nR = max - nR
return nR
}
sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val weights: Pairing.Weights) {
private fun nonDetRandom(max: Long): Long {
if (max == 0L) {
return 0
}
val r = Math.random() * (max + 1)
return r.toLong()
}
sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val pairingParams: Pairing.PairingParams) {
companion object {
val rand = Random(/* seed from properties - TODO */)
}
open fun sort(p: Pairable, q: Pairable): Int = 0 // no sort by default
abstract fun weight(black: Pairable, white: Pairable): Double
open fun handicap(black: Pairable, white: Pairable) = 0
open fun weight(p1: Pairable, p2: Pairable): Double {
var score = 1L // 1 is minimum value because 0 means "no matching allowed"
score += applyBaseCriteria(p1, p2)
return score as Double
}
// The main criterion that will be used to define the groups should be defined by subclasses
abstract fun mainCriterion(p1: Pairable): Int
abstract fun mainCriterionMinMax(): Pair<Int, Int>
// Base criteria
open fun avoidDuplicatingGames(p1: Pairable, p2: Pairable): Long {
if (historyHelper.playedTogether(p1, p2)) {
return pairingParams.baseAvoidDuplGame
} else {
return 0
}
}
open fun applyRandom(p1: Pairable, p2: Pairable): Long {
if (pairingParams.baseDeterministic) {
return detRandom(pairingParams.baseRandom, p1, p2)
} else {
return nonDetRandom(pairingParams.baseRandom)
}
}
open fun applyBalanceBW(p1: Pairable, p2: Pairable): Long {
// This cost is never applied if potential Handicap != 0
// It is fully applied if wbBalance(sP1) and wbBalance(sP2) are strictly of different signs
// It is half applied if one of wbBalance is 0 and the other is >=2
val potentialHd: Int = handicap(p1, p2)
if (potentialHd == 0) {
val wb1: Int = p1.colorBalance
val wb2: Int = p2.colorBalance
if (wb1 * wb2 < 0) {
return pairingParams.baseBalanceWB
} else if (wb1 == 0 && abs(wb2) >= 2) {
return pairingParams.baseBalanceWB / 2
} else if (wb2 == 0 && abs(wb1) >= 2) {
return pairingParams.baseBalanceWB / 2
}
}
return 0
}
open fun applyBaseCriteria(p1: Pairable, p2: Pairable): Long {
var score = 0L
// Base Criterion 1 : Avoid Duplicating Game
// Did p1 and p2 already play ?
score += avoidDuplicatingGames(p1, p2)
// Base Criterion 2 : Random
score += applyRandom(p1, p2)
// Base Criterion 3 : Balance W and B
score += applyBalanceBW(p1, p2)
return score
}
// Main criteria
open fun applyMainCriteria(p1: Pairable, p2: Pairable): Long {
var score = 0L;
// Main criterion 1 avoid mixing category is moved to Swiss with category
// TODO
// Main criterion 2 minimize score difference
score += minimizeScoreDifference(p1, p2)
return score
}
open fun minimizeScoreDifference(p1: Pairable, p2: Pairable): Long {
var scoCost: Long = 0
val scoRange: Int = numberGroups
// TODO check category equality if category are used in SwissCat
val x = abs(groups[p1.id]!! - groups[p2.id]!!) as Double / scoRange.toDouble()
val k: Double = pairingParams.standardNX1Factor
scoCost = (pairingParams.mainMinimizeScoreDifference * (1.0 - x) * (1.0 + k * x)) as Long
return scoCost
}
// Handicap functions
open fun handicap(p1: Pairable, p2: Pairable): Int {
var hd = 0
var pseudoRank1: Int = p1.rank
var pseudoRank2: Int = p2.rank
pseudoRank1 = min(pseudoRank1, pairingParams.hd.noHdRankThreshold)
pseudoRank2 = min(pseudoRank2, pairingParams.hd.noHdRankThreshold)
hd = pseudoRank1 - pseudoRank2
return clampHandicap(hd)
}
open fun clampHandicap(input_hd: Int): Int {
var hd = input_hd
if (hd > 0) {
hd -= pairingParams.hd.correction
hd = min(hd, 0)
}
if (hd < 0) {
hd += pairingParams.hd.correction
hd = max(hd, 0)
}
// Clamp handicap with ceiling
hd = min(hd, pairingParams.hd.ceiling)
hd = max(hd, -pairingParams.hd.ceiling)
return hd
}
open fun games(black: Pairable, white: Pairable): List<Game> {
// CB TODO team of individuals pairing
return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = handicap(black, white)))
@@ -58,6 +196,15 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val weig
return result
}
private fun computeGroups(): Pair<Map<Int, Int>, Int> {
val (mainScoreMin, mainScoreMax) = mainCriterionMinMax()
// TODO categories
val groups: Map<Int, Int> = pairables.associate { pairable -> Pair(pairable.id, mainCriterion(pairable)) }
return Pair(groups, mainScoreMax - mainScoreMin)
}
// Calculation parameters
val n = pairables.size
@@ -66,6 +213,10 @@ sealed class Solver(history: List<Game>, val pairables: List<Pairable>, val weig
if (pairables.first().let { it is TeamTournament.Team && it.teamOfIndividuals }) TeamOfIndividualsHistoryHelper(history)
else HistoryHelper(history)
private val groupsResult = computeGroups()
private val groups = groupsResult.first
private val numberGroups = groupsResult.second
// pairables sorted using overloadable sort function
private val sortedPairables by lazy {
pairables.sortedWith(::sort)

View File

@@ -3,11 +3,9 @@ package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.Pairable
import org.jeudego.pairgoth.model.Pairing
import org.jeudego.pairgoth.model.Swiss
import org.jeudego.pairgoth.model.Swiss.Method.*
import kotlin.math.abs
class SwissSolver(history: List<Game>, pairables: List<Pairable>, weights: Pairing.Weights, val method: Swiss.Method): Solver(history, pairables, weights) {
class SwissSolver(history: List<Game>, pairables: List<Pairable>, pairingParams: Pairing.PairingParams): Solver(history, pairables, pairingParams) {
override fun sort(p: Pairable, q: Pairable): Int =
when (p.score) {
@@ -15,25 +13,11 @@ class SwissSolver(history: List<Game>, pairables: List<Pairable>, weights: Pairi
else -> ((q.score - p.score) * 1000).toInt()
}
override fun weight(black: Pairable, white: Pairable): Double {
var weight = 0.0
if (black.played(white)) weight += weights.played
if (black.score != white.score) {
val placeWeight =
if (black.score > white.score) (black.placeInGroup.second + white.placeInGroup.first) * weights.place
else (white.placeInGroup.second + black.placeInGroup.first) * weights.place
weight += abs(black.score - white.score) * weights.score + placeWeight
} else {
weight += when (method) {
SPLIT_AND_FOLD ->
if (black.placeInGroup.first > white.placeInGroup.first) abs(black.placeInGroup.first - (white.placeInGroup.second - white.placeInGroup.first)) * weights.place
else abs(white.placeInGroup.first - (black.placeInGroup.second - black.placeInGroup.first)) * weights.place
override fun mainCriterion(p1: Pairable): Int {
TODO("Not yet implemented")
}
SPLIT_AND_RANDOM -> rand.nextDouble() * black.placeInGroup.second * weights.place
SPLIT_AND_SLIP -> abs(abs(black.placeInGroup.first - white.placeInGroup.first) - black.placeInGroup.second) * weights.place
}
}
weight += (abs(black.colorBalance + 1) + abs(white.colorBalance - 1)) * weights.color
return weight
override fun mainCriterionMinMax(): Pair<Int, Int> {
TODO("Not yet implemented")
}
}