


Ride the Hype
- Pokemon is still popular
- Fall this year
- Pokemon Let's Go
- Pikachu
- Eevee
- Pokemon Let's Go
- It's a planning problem

- Party building
- RPG
- Strongly typed








https://pokemondb.net/pokedex/bulbasaur
I wanna be the very best...
- A full team
- Of different pokemon
- Strong against anything
- Weak against "nothing"...
- As few as possible














Optaplanner:
"Is this better?"


Consult Gloom


- SQL-ish
- Objects in Memory
- Evaluate Rules

/domain/Pokemon.kt
class Pokemon{
var id: Int? = null
var name: String? = null
var type1: Type? = null
var type2: Type? = null
var strongAgainst: List<String>
var weakAgainst: List<String>
constructor(id: Int?, name: String?, type1: Type?, type2: Type? = null) {
...
}
fun isStrongAgainst(type: Type): Boolean {
return this.strongAgainst.contains(type.name.toString())
}
fun isWeakAgainst(type:Type): Boolean {
return this.weakAgainst.contains(type.name.toString())
}
override fun toString(): String {
return "Pokemon(name=$name)"
}
}

/domain/Types.kt
data class Type(val name: TypeEnum,
val strongAgainst: List<TypeEnum>,
val weakAgainst: List<TypeEnum>,
val blankAgainst: List<TypeEnum>)
val Normal = Type(
name = TypeEnum.NORMAL,
strongAgainst = listOf(),
weakAgainst = listOf(TypeEnum.ROCK),
blankAgainst = listOf(TypeEnum.GHOST)
)
val Fighting = Type(
name = TypeEnum.FIGHTING,
strongAgainst = listOf(TypeEnum.NORMAL, TypeEnum.ROCK, TypeEnum.ICE),
weakAgainst = listOf(TypeEnum.POISON, TypeEnum.BUG, TypeEnum.PSYCHIC, TypeEnum.FLYING),
blankAgainst = listOf(TypeEnum.GHOST)
)
...
val Types = listOf(
Normal,
Fighting,
...)

/domain/TeamSlot.kt
@PlanningEntity
class TeamSlot {
var id: Int = 0
@PlanningVariable(valueRangeProviderRefs = ["pokeRange"])
var pokemon: Pokemon? = null
override fun equals(other: Any?): Boolean {
return this.id == (other as TeamSlot).id
}
override fun toString(): String {
return "TeamSlot(id=$id, pokemon=${pokemon?.name})"
}
constructor()
constructor(id: Int, pokemon: Pokemon?) {
this.id = id
this.pokemon = pokemon
}
}

/domain/Team.kt
@PlanningSolution
class Team() {
@PlanningEntityCollectionProperty
lateinit var team: List<TeamSlot>
@PlanningScore
lateinit var score: HardSoftScore
@ValueRangeProvider(id = "pokeRange")
@ProblemFactCollectionProperty
lateinit var pokemon: List<Pokemon>
@ValueRangeProvider(id = "pokeTypes")
@ProblemFactCollectionProperty
lateinit var types: List<Type>
constructor(team: List<TeamSlot>, pokemon: List<Pokemon>, types: List<Type>): this() {
this.team = team
this.pokemon = pokemon
this.types = types
}
override fun toString(): String {
return "Team(team=$team, score=$score)"
}
}

solverConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<solver>
<!--<environmentMode>FULL_ASSERT</environmentMode>-->
<!--<environmentMode>FAST_ASSERT</environmentMode>-->
<solutionClass>net.spantree.kantoptimal.domain.Team</solutionClass>
<entityClass>net.spantree.kantoptimal.domain.TeamSlot</entityClass>
<scoreDirectorFactory>
<scoreDrl>rules.drl</scoreDrl>
</scoreDirectorFactory>
<termination>
<secondsSpentLimit>10</secondsSpentLimit>
<bestScoreLimit>0hard/0soft</bestScoreLimit>
</termination>
<constructionHeuristic/>
<localSearch>
<acceptor>
<simulatedAnnealingStartingTemperature>2hard/100soft</simulatedAnnealingStartingTemperature>
</acceptor>
<forager>
<acceptedCountLimit>4</acceptedCountLimit>
</forager>
</localSearch>
</solver>

KantoptimalApp.kt
@SpringBootApplication
class KantoptimalApp {
@Bean
fun init() = CommandLineRunner {
println("ok")
}
fun main(args: Array<String>) {
runApplication<KantoptimalApp>(*args)
val solution = Team(listOf(
TeamSlot(1, null),
TeamSlot(2, null),
TeamSlot(3, null),
TeamSlot(4, null),
TeamSlot(5, null),
TeamSlot(6, null)
),
POKEDEX.values.toList(),
Types
)
val solverFactory: SolverFactory<Team> = SolverFactory.createFromXmlResource(
"solverConfig.xml")
val solver: Solver<Team> = solverFactory.buildSolver()
val newTeam = solver.solve(solution)
val scoreDirector = solver.scoreDirectorFactory.buildScoreDirector()
println(newTeam)
scoreDirector.workingSolution = newTeam
}
}

No Rules!






Oh...

rule "no duplicates"
when
TeamSlot($pokemon1: pokemon, pokemon != null)
TeamSlot(pokemon == $pokemon1)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end






Oh...

rule "STAB (same-type-attack-bonus) against everything"
when
$type: Type()
not (exists TeamSlot(pokemon != null, pokemon.isStrongAgainst($type) == true))
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end


Viability!
- Geodude
- Paras
- Jynx
- Mankey
- Gastly
- Bulbasaur






- Weak against: [ROCK, ICE, BUG, GRASS, FIGHTING, POISON, PSYCHIC, GHOST, GROUND]
rule "fewest weaknesses"
when
$type: Type()
TeamSlot(pokemon.isWeakAgainst($type), pokemon != null)
then
scoreHolder.addSoftConstraintMatch(kcontext, -1);
end
Better!
- Jynx
- Hitmonlee
- Electrode
- Pinsir
- Diglett
- Haunter






- Weak against: 8 types
- 9 before
// TODO
- Remove STAB assumption
- Special/Physical
- Remodeling

Sources
- https://pokemondb.net/
- https://assets.pokemon.com/
Thanks
- Shane
- Kevin
- Cedric
Optaplanner Let's Go
By dlindema
Optaplanner Let's Go
- 441