Skip to content

Commit

Permalink
Merge pull request #298 from alyssaruth/x01-any-finish
Browse files Browse the repository at this point in the history
X01 - Finish on non-double
  • Loading branch information
alyssaruth authored Feb 27, 2024
2 parents ddeb23c + 70ff29e commit db7a553
Show file tree
Hide file tree
Showing 56 changed files with 808 additions and 197 deletions.
9 changes: 5 additions & 4 deletions src/main/kotlin/dartzee/achievements/AchievementSqlUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fun ensureX01RoundsTableExists(playerIds: List<String>, database: Database) {
val created =
database.createTableIfNotExists(
X01_ROUNDS_TABLE,
"PlayerId VARCHAR(36), GameId VARCHAR(36), ParticipantId VARCHAR(36), StartingScore INT, RoundNumber INT, " +
"PlayerId VARCHAR(36), GameId VARCHAR(36), GameParams VARCHAR(255), ParticipantId VARCHAR(36), StartingScore INT, RoundNumber INT, " +
"TotalDartsThrown INT, RemainingScore INT, LastDartScore INT, LastDartMultiplier INT, DtRoundFinished TIMESTAMP"
)

Expand All @@ -63,13 +63,13 @@ fun ensureX01RoundsTableExists(playerIds: List<String>, database: Database) {
val tmp1 =
database.createTempTable(
"X01RoundsPt1",
"PlayerId VARCHAR(36), GameId VARCHAR(36), ParticipantId VARCHAR(36), StartingScore INT, RoundNumber INT, LastDartOrdinal INT"
"PlayerId VARCHAR(36), GameId VARCHAR(36), GameParams VARCHAR(255), ParticipantId VARCHAR(36), StartingScore INT, RoundNumber INT, LastDartOrdinal INT"
)

database.executeUpdate(
"""
INSERT INTO $tmp1
SELECT pt.PlayerId, pt.GameId, pt.RowId, drtFirst.StartingScore, drtFirst.RoundNumber, MAX(drt.Ordinal)
SELECT pt.PlayerId, pt.GameId, g.GameParams, pt.RowId, drtFirst.StartingScore, drtFirst.RoundNumber, MAX(drt.Ordinal)
FROM ${EntityName.Dart} drtFirst, ${EntityName.Participant} pt, ${EntityName.Game} g, ${EntityName.Dart} drt
WHERE drtFirst.ParticipantId = pt.RowId
AND drtFirst.PlayerId = pt.PlayerId
Expand All @@ -80,7 +80,7 @@ fun ensureX01RoundsTableExists(playerIds: List<String>, database: Database) {
AND drtFirst.ParticipantId = drt.ParticipantId
AND drtFirst.RoundNumber = drt.RoundNumber
${getPlayerSql(playerIds)}
GROUP BY pt.PlayerId, pt.GameId, pt.RowId, drtFirst.StartingScore, drtFirst.RoundNumber
GROUP BY pt.PlayerId, pt.GameId, g.GameParams, pt.RowId, drtFirst.StartingScore, drtFirst.RoundNumber
"""
.trimIndent()
)
Expand All @@ -91,6 +91,7 @@ fun ensureX01RoundsTableExists(playerIds: List<String>, database: Database) {
SELECT
zz.PlayerId,
zz.GameId,
zz.GameParams,
zz.ParticipantId,
zz.StartingScore,
zz.RoundNumber,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package dartzee.achievements.x01

import dartzee.achievements.AbstractAchievementBestGame
import dartzee.achievements.AchievementType
import dartzee.game.FinishType
import dartzee.game.GameType
import dartzee.game.X01Config
import dartzee.utils.ResourceCache

class AchievementX01BestGame : AbstractAchievementBestGame() {
override val achievementType = AchievementType.X01_BEST_GAME
override val name = "Leg-up"
override val desc = "Best game of 501"
override val gameType = GameType.X01
override val gameParams = "501"
override val gameParams = X01Config(501, FinishType.Doubles).toJson()

override val redThreshold = 99
override val orangeThreshold = 60
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class AchievementX01HighestBust : AbstractAchievement() {

override fun populateForConversion(playerIds: List<String>, database: Database) {
val whereSql =
"RemainingScore < 0 OR RemainingSCore = 1 OR (RemainingScore = 0 AND LastDartMultiplier <> 2)"
"RemainingScore < 0 OR (GameParams LIKE '%Doubles%' AND (RemainingScore = 1 OR (RemainingScore = 0 AND LastDartMultiplier <> 2)))"

unlockThreeDartAchievement(playerIds, whereSql, "StartingScore", achievementType, database)
}
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/dartzee/ai/DartsAiModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dartzee.ai
import com.fasterxml.jackson.module.kotlin.readValue
import dartzee.core.util.jsonMapper
import dartzee.game.ClockType
import dartzee.game.FinishType
import dartzee.logging.CODE_AI_ERROR
import dartzee.`object`.ComputedPoint
import dartzee.`object`.SegmentType
Expand Down Expand Up @@ -42,7 +43,7 @@ data class DartsAiModel(
private val distribution = NormalDistribution(mean.toDouble(), standardDeviation)

/** X01 */
fun throwX01Dart(score: Int): ComputedPoint {
fun throwX01Dart(score: Int, finishType: FinishType): ComputedPoint {
// Check for a specific dart to aim for. It's possible to override any value for a specific
// AI strategy.
val drtToAimAt = getOveriddenDartToAimAt(score)
Expand All @@ -55,7 +56,7 @@ data class DartsAiModel(
return if (score > 60) {
throwScoringDart()
} else {
val defaultDrt = getDefaultDartToAimAt(score)
val defaultDrt = getX01AimDart(score, finishType)
val ptToAimAt = getPointForScore(defaultDrt)
throwDartAtPoint(ptToAimAt)
}
Expand Down
13 changes: 8 additions & 5 deletions src/main/kotlin/dartzee/ai/DartsSimulationX01.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dartzee.ai

import dartzee.db.PlayerEntity
import dartzee.game.FinishType
import dartzee.game.GameType
import dartzee.game.X01Config
import dartzee.`object`.Dart
import dartzee.utils.isBust
import dartzee.utils.shouldStopForMercyRule
Expand All @@ -15,7 +17,8 @@ class DartsSimulationX01(player: PlayerEntity, model: DartsAiModel) :
private var startingScore = -1
private var currentScore = -1

override val gameParams = "$X01"
private val config = X01Config(501, FinishType.Doubles)
override val gameParams = config.toJson()
override val gameType = GameType.X01

override fun getTotalScore(): Int {
Expand All @@ -37,15 +40,15 @@ class DartsSimulationX01(player: PlayerEntity, model: DartsAiModel) :
startingScore = currentScore
resetRound()

val pt = model.throwX01Dart(currentScore)
val pt = model.throwX01Dart(currentScore, config.finishType)
dartThrown(pt)
}

private fun finishedRound() {
confirmRound()

// If we've bust, then reset the current score back
if (isBust(dartsThrown.last())) {
if (isBust(dartsThrown.last(), config.finishType)) {
currentScore = startingScore
}

Expand All @@ -62,11 +65,11 @@ class DartsSimulationX01(player: PlayerEntity, model: DartsAiModel) :
if (
currentScore <= 1 ||
dartsThrown.size == 3 ||
shouldStopForMercyRule(model, startingScore, currentScore)
shouldStopForMercyRule(model, startingScore, currentScore, config.finishType)
) {
finishedRound()
} else {
val pt = model.throwX01Dart(currentScore)
val pt = model.throwX01Dart(currentScore, config.finishType)
dartThrown(pt)
}
}
Expand Down
29 changes: 28 additions & 1 deletion src/main/kotlin/dartzee/ai/StrategyUtils.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dartzee.ai

import dartzee.game.FinishType
import dartzee.`object`.ComputedPoint
import dartzee.`object`.DartboardSegment
import dartzee.`object`.SegmentType
Expand All @@ -22,7 +23,13 @@ fun getComputedPointForScore(score: Int, type: SegmentType): ComputedPoint =
AI_DARTBOARD.getPointToAimAt(DartboardSegment(type, score))

/** Get the application-wide default thing to aim for, which applies to any score of 60 or less */
fun getDefaultDartToAimAt(score: Int): AimDart {
fun getX01AimDart(score: Int, finishType: FinishType) =
when (finishType) {
FinishType.Doubles -> getX01AimDartDoublesMode(score)
FinishType.Any -> getX01AimDartRelaxedMode(score)
}

private fun getX01AimDartDoublesMode(score: Int): AimDart {
// Aim for the single that puts you on double top
if (score > 40) {
val single = score - 40
Expand All @@ -40,6 +47,26 @@ fun getDefaultDartToAimAt(score: Int): AimDart {
return AimDart(singleToAimFor, 1)
}

private fun getX01AimDartRelaxedMode(score: Int): AimDart {
// Finish on single if possible
if (score <= 20) {
return AimDart(score, 1)
}

// Go for treble finishes if possible
if (score % 3 == 0) {
return AimDart(score / 3, 3)
}

// Aim to put ourselves on single 20
if (score <= 40) {
return AimDart(score - 20, 1)
}

// Just go for single 20
return AimDart(20, 1)
}

private fun getHighestPowerOfTwoLessThan(score: Int): Int {
var i = 2
while (i < score) {
Expand Down
28 changes: 24 additions & 4 deletions src/main/kotlin/dartzee/bean/GameParamFilterPanelX01.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
package dartzee.bean

import dartzee.game.FinishType
import dartzee.game.X01Config
import java.awt.BorderLayout
import java.awt.event.ActionListener
import javax.swing.Box
import javax.swing.JCheckBox
import javax.swing.JPanel

class GameParamFilterPanelX01 : GameParamFilterPanel() {
private val panel = JPanel()
val spinner = SpinnerX01()
private val spinner = SpinnerX01()
private val cbFinishOnDouble = JCheckBox("Finish on double")
private val separator = Box.createHorizontalStrut(20)

init {
add(panel, BorderLayout.CENTER)
panel.add(cbFinishOnDouble)
panel.add(separator)
panel.add(spinner)
cbFinishOnDouble.isSelected = true
}

override fun getGameParams() = "${spinner.value}"
override fun getGameParams() = constructGameParams().toJson()

override fun setGameParams(gameParams: String) {
spinner.value = gameParams.toInt()
val config = X01Config.fromJson(gameParams)
spinner.value = config.target
cbFinishOnDouble.isSelected = config.finishType == FinishType.Doubles
}

override fun getFilterDesc() = "games of ${getGameParams()}"
private fun constructGameParams() =
X01Config(
spinner.value as Int,
if (cbFinishOnDouble.isSelected) FinishType.Doubles else FinishType.Any
)

override fun getFilterDesc() = "games of ${constructGameParams().description()}"

override fun enableChildren(enabled: Boolean) {
spinner.isEnabled = enabled
cbFinishOnDouble.isEnabled = enabled
}

override fun addActionListener(listener: ActionListener) {
spinner.addActionListener(listener)
cbFinishOnDouble.addActionListener(listener)
}

override fun removeActionListener(listener: ActionListener) {
spinner.removeActionListener()
cbFinishOnDouble.addActionListener(listener)
}
}
13 changes: 10 additions & 3 deletions src/main/kotlin/dartzee/game/GameType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package dartzee.game

import dartzee.db.DartzeeTemplateEntity

const val GAME_PARAMS_NOT_APPLICABLE = "N/A"

enum class GameType {
X01,
GOLF,
Expand All @@ -26,11 +28,16 @@ enum class GameType {
}
}

fun getParamsDescription(gameParams: String) =
when (this) {
X01 -> gameParams
fun getParamsDescription(gameParams: String): String {
if (gameParams == GAME_PARAMS_NOT_APPLICABLE) {
return GAME_PARAMS_NOT_APPLICABLE
}

return when (this) {
X01 -> X01Config.fromJson(gameParams).description()
GOLF -> "$gameParams holes"
ROUND_THE_CLOCK -> RoundTheClockConfig.fromJson(gameParams).getDescription()
DARTZEE -> DartzeeTemplateEntity().retrieveForId(gameParams, false)?.name.orEmpty()
}
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/dartzee/game/X01Config.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dartzee.game

import com.fasterxml.jackson.module.kotlin.readValue
import dartzee.core.util.jsonMapper

enum class FinishType {
Any,
Doubles
}

data class X01Config(val target: Int, val finishType: FinishType) {
fun toJson(): String = jsonMapper().writeValueAsString(this)

fun description() = "$target${finishTypeDesc()}"

private fun finishTypeDesc() = if (finishType == FinishType.Any) " (relaxed finish)" else ""

companion object {
fun fromJson(json: String) = jsonMapper().readValue<X01Config>(json)
}
}
12 changes: 8 additions & 4 deletions src/main/kotlin/dartzee/game/state/X01PlayerState.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package dartzee.game.state

import dartzee.game.X01Config
import dartzee.`object`.Dart
import dartzee.utils.isBust
import dartzee.utils.isFinishRound
import dartzee.utils.isNearMissDouble
import dartzee.utils.sumScore

data class X01PlayerState(
private val startingScore: Int,
private val config: X01Config,
override val wrappedParticipant: IWrappedParticipant,
override val completedRounds: MutableList<List<Dart>> = mutableListOf(),
override val currentRound: MutableList<Dart> = mutableListOf(),
Expand All @@ -33,15 +34,15 @@ data class X01PlayerState(
roundSubSet
.filterNot { round ->
val lastDart = round.lastOrNull()
lastDart?.let(::isBust) ?: false
lastDart?.let { isBust(it, config.finishType) } ?: false
}
.toMutableList()

if (roundNumber == currentRoundNumber()) {
nonBustRounds.add(currentRound.toList())
}

return startingScore - nonBustRounds.sumOf { sumScore(it) }
return config.target - nonBustRounds.sumOf { sumScore(it) }
}

fun getRemainingScore() = getRemainingScoreForRound(currentRoundNumber())
Expand All @@ -53,7 +54,10 @@ data class X01PlayerState(

fun getLastRound() = completedRounds.last()

fun isCurrentRoundComplete() = currentRound.size == 3 || getRemainingScore() <= 1
fun isCurrentRoundComplete() =
currentRound.size == 3 ||
getRemainingScore() <= 0 ||
currentRound.lastOrNull()?.let { isBust(it, config.finishType) } ?: false

override fun dartThrown(dart: Dart) {
dart.startingScore = getRemainingScore()
Expand Down
Loading

0 comments on commit db7a553

Please sign in to comment.