Skip to content

Commit 6a6809a

Browse files
committed
Add TTT3D Fighting
1 parent 31d8927 commit 6a6809a

File tree

2 files changed

+70
-75
lines changed

2 files changed

+70
-75
lines changed

games-server/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ dependencies {
6363
compile "io.javalin:javalin:2.6.0"
6464
compile "com.github.kittinunf.fuel:fuel:2.0.1"
6565
compile "net.zomis:uttt-jvm:0.3.0"
66-
compile "net.zomis:fights:0.2.1-SNAPSHOT"
66+
compile "net.zomis:fights:0.3.0-SNAPSHOT"
6767
compile 'net.zomis:scorers:0.3.0-SNAPSHOT'
6868

6969
// testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo"

games-server/src/main/kotlin/net/zomis/games/server2/TTT3D.kt

Lines changed: 69 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package net.zomis.games.server2
33
import kotlinx.coroutines.*
44
import net.zomis.aiscores.*
55
import net.zomis.fight.ext.Fight
6+
import net.zomis.fight.ext.WinResult
7+
import net.zomis.fights.Fights
68
import net.zomis.games.ais.AlphaBeta
79
import net.zomis.games.ais.Best
810
import net.zomis.scorers.*
@@ -167,19 +169,16 @@ class TTT3D {
167169
return "Player $currentPlayer Fields ${allFields().toList()}"
168170
}
169171

170-
}
171-
172-
class TTT3DScorer(private val game: TTT3D): ScoreStrategy<TTT3DPiece, TTT3DPoint> {
173-
override fun canScoreField(parameters: ScoreParameters<TTT3DPiece>?, field: TTT3DPoint?): Boolean {
174-
return canScore(parameters!!.parameters, field!!)
172+
fun isGameOver(): Boolean {
173+
return findWinner() != null || isDraw()
175174
}
176175

177-
fun canScore(player: TTT3DPiece, field: TTT3DPoint): Boolean {
178-
return game.currentPlayer == player && game.canPlayAt(field)
179-
}
176+
}
177+
178+
class TTT3DScorer {
180179

181-
fun canScore(player: ScoreParams<TTT3DPiece>, field: TTT3DPoint): Boolean {
182-
return game.currentPlayer == player.param && game.canPlayAt(field)
180+
fun canScore(state: ScoreParams<TTT3D>, field: TTT3DPoint): Boolean {
181+
return state.param.canPlayAt(field)
183182
}
184183

185184
fun fieldsToScoreInGame(state: TTT3D): Sequence<TTT3DPoint> {
@@ -188,20 +187,17 @@ class TTT3DScorer(private val game: TTT3D): ScoreStrategy<TTT3DPiece, TTT3DPoint
188187
}.asSequence()
189188
}
190189

191-
override fun getFieldsToScore(parameters: TTT3DPiece?): MutableCollection<TTT3DPoint> {
192-
if (game.currentPlayer != parameters) {
193-
return mutableListOf()
194-
}
195-
return fieldsToScoreInGame(game).toMutableList()
190+
fun getFieldsToScore(state: TTT3D): MutableCollection<TTT3DPoint> {
191+
return fieldsToScoreInGame(state).toMutableList()
196192
}
197193

198194
}
199195

200-
class TTT3DIO(private val game: TTT3D) {
201-
val scorerStrategy = TTT3DScorer(game)
202-
val scorers = Scorers<TTT3DPiece, TTT3DPoint>(scorerStrategy::getFieldsToScore, scorerStrategy::canScore)
203-
val scorerWant = NamedScorer<TTT3DPiece, TTT3DPoint>("want") { piece, point ->
204-
game.winConditions.filter { it.contains(point) }.filter { it.canWin(piece.param) }.map {
196+
class TTT3DIO {
197+
val scorerStrategy = TTT3DScorer()
198+
val scorers = Scorers(scorerStrategy::getFieldsToScore, scorerStrategy::canScore)
199+
val scorerWant = NamedScorer<TTT3D, TTT3DPoint>("want") { params, point ->
200+
params.param.winConditions.filter { it.contains(point) }.filter { it.canWin(params.param.currentPlayer) }.map {
205201
when (it.emptySpaces()) {
206202
1 -> 100.0
207203
2 -> 1.0
@@ -210,8 +206,8 @@ class TTT3DIO(private val game: TTT3D) {
210206
}
211207
}.sum()
212208
}
213-
val scorerSabotage = NamedScorer<TTT3DPiece, TTT3DPoint>("sabotage") { piece, point ->
214-
game.winConditions.filter { it.contains(point) }.filter { it.canWin(piece.param.opponent()) }.map {
209+
val scorerSabotage = NamedScorer<TTT3D, TTT3DPoint>("sabotage") { params, point ->
210+
params.param.winConditions.filter { it.contains(point) }.filter { it.canWin(params.param.currentPlayer.opponent()) }.map {
215211
when (it.emptySpaces()) {
216212
1 -> 50.0
217213
2 -> 1.0
@@ -220,22 +216,22 @@ class TTT3DIO(private val game: TTT3D) {
220216
}
221217
}.sum()
222218
}
223-
val alerts = NamedScorer<TTT3DPiece, TTT3DPoint>("alerts") { piece, point ->
224-
val block = scorerSabotage.scoring(piece, point) > 30
225-
val doNotPlace = nextLevelReveal.scoring(piece, point) < -30
219+
val alerts = NamedScorer<TTT3D, TTT3DPoint>("alerts") { params, point ->
220+
val block = scorerSabotage.scoring(params, point) > 30
221+
val doNotPlace = nextLevelReveal.scoring(params, point) < -30
226222
val i = block.let { if (it) 30 else 0 } + doNotPlace.let { if (it) -40 else 0 }
227223
return@NamedScorer i.toDouble()
228224
}
229225
// Trap scorer: Extra points for creating traps -- if you force opponent to play somewhere that will give you the win
230226
// Field importance scorer: If you can create a win condition that will cause a trap (field is important for you)
231227
// Trigger Trap scorer: Extra points if you only need 1 piece to win on BOTH of the levels above point.
232-
val nextLevelReveal = NamedScorer<TTT3DPiece, TTT3DPoint>("nextReveal") { piece, point ->
228+
val nextLevelReveal = NamedScorer<TTT3D, TTT3DPoint>("nextReveal") { params, point ->
233229
if (point.z == RANGE.endInclusive) {
234230
return@NamedScorer 0.0
235231
}
236-
game.winConditions.filter { it.contains(game.pieces[point.y][point.x][point.z + 1]) }
232+
params.param.winConditions.filter { it.contains(params.param.pieces[point.y][point.x][point.z + 1]) }
237233
.filter { !it.contains(point) }
238-
.filter { it.canWin(piece.param.opponent()) }.map { it.emptySpaces() }.map {
234+
.filter { it.canWin(params.param.currentPlayer.opponent()) }.map { it.emptySpaces() }.map {
239235
when (it) {
240236
1 -> -50.0
241237
2 -> -1.0
@@ -245,14 +241,14 @@ class TTT3DIO(private val game: TTT3D) {
245241
}
246242

247243

248-
fun print() {
249-
print { it.piece?.toString() ?: " " }
244+
fun print(game: TTT3D) {
245+
print(game) { it.piece?.toString() ?: " " }
250246
val xwins = game.winConditions.filter { it.canWin(TTT3DPiece.X) }.groupBy { it.emptySpaces() }.mapValues { it.value.size }
251247
val owins = game.winConditions.filter { it.canWin(TTT3DPiece.O) }.groupBy { it.emptySpaces() }.mapValues { it.value.size }
252248
println("Winnables: X $xwins (${xwins.values.sum()}). O $owins (${owins.values.sum()})")
253249
}
254250

255-
fun print(function: (TTT3DPoint) -> String) {
251+
fun print(game: TTT3D, function: (TTT3DPoint) -> String) {
256252
val rowSeperator = "-".repeat((RANGE.last + 1) * (RANGE.last + 4) + 1)
257253
println(rowSeperator)
258254
game.pieces.forEach { y ->
@@ -275,13 +271,13 @@ class TTT3DIO(private val game: TTT3D) {
275271
.withScorer(scorerSabotage)
276272
.withScorer(nextLevelReveal)
277273

278-
fun printScores(factory: ScorersConfig<TTT3DPiece, TTT3DPoint>) {
274+
fun printScores(game: TTT3D, factory: ScorersConfig<TTT3D, TTT3DPoint>) {
279275
val scoreDetailsToString: (Pair<String, Double>) -> String = { score ->
280276
val format = "%.4f"
281277
"${score.first}: ${format.format(score.second)}"
282278
}
283279

284-
val scores = factory.producer(game.currentPlayer).score()
280+
val scores = factory.producer(game).score()
285281
val maxLength: Int = scores.fieldScores.values.flatMap {
286282
it.scores.map { score -> scoreDetailsToString(score.key to score.value).length + 2 }
287283
}.max() ?: 0
@@ -312,26 +308,26 @@ class TTT3DIO(private val game: TTT3D) {
312308
println()
313309
}
314310

315-
fun playVsAI() {
311+
fun playVsAI(game: TTT3D) {
316312
val scanner = Scanner(System.`in`)
317313

318-
while (game.findWinner() == null && scorers.fieldsToScore(game.currentPlayer).toList().isNotEmpty()) {
314+
while (!game.isGameOver()) {
319315
if (game.currentPlayer == TTT3DPiece.X) {
320-
makeMove(requestInput(scanner))
316+
makeMove(game, requestInput(game, scanner))
321317
// makeMove(alphaBetaPlay(game, 5))
322318
// makeMove(aiPlay())
323319
} else {
324-
makeMove(alphaBetaPlay(game, 5))
320+
makeMove(game, alphaBetaPlay(game, 5))
325321
// makeMove(aiPlay())
326322
}
327-
this.print()
323+
this.print(game)
328324
// Thread.sleep(2000)
329325
}
330-
print()
326+
print(game)
331327
println(game.findWinner())
332328
}
333329

334-
private fun makeMove(move: Pair<Int, Int>) {
330+
private fun makeMove(game: TTT3D, move: Pair<Int, Int>) {
335331
val x = move.first
336332
val y = move.second
337333
println("${game.currentPlayer} moves at $x, $y")
@@ -406,10 +402,10 @@ class TTT3DIO(private val game: TTT3D) {
406402
return move.first
407403
}
408404

409-
fun aiPlay(): Pair<Int, Int> {
410-
this.printScores(this.factory)
405+
fun aiPlay(game: TTT3D): Pair<Int, Int> {
406+
this.printScores(game, this.factory)
411407

412-
val ai = factory.producer(game.currentPlayer)
408+
val ai = factory.producer(game)
413409
val scores = ai.score()
414410
val best = scores.best()
415411
if (best.isEmpty()) {
@@ -419,22 +415,11 @@ class TTT3DIO(private val game: TTT3D) {
419415
return randomBestField.x to randomBestField.y
420416
}
421417

422-
fun play() {
423-
this.print { point -> game.winConditions.count { it.pieces.contains(point) }.toString() }
424-
425-
val scanner = Scanner(System.`in`)
426-
while (game.findWinner() == null) {
427-
this.requestInput(scanner)
428-
}
429-
println("Winner is ${game.findWinner()}")
430-
this.print()
431-
}
432-
433-
private fun requestInput(scanner: Scanner): Pair<Int, Int> {
418+
private fun requestInput(game: TTT3D, scanner: Scanner): Pair<Int, Int> {
434419
val alertConfig = scorers.config().withScorer(this.alerts)
435-
this.printScores(alertConfig)
420+
this.printScores(game, alertConfig)
436421
println()
437-
this.print()
422+
this.print(game)
438423
println("Where do you want to move? Current Player ${game.currentPlayer}")
439424
val x = scanner.nextInt()
440425
val y = scanner.nextInt()
@@ -443,33 +428,43 @@ class TTT3DIO(private val game: TTT3D) {
443428

444429
data class MoveData(val floorsCount: List<Int>)
445430
data class GameData(val ais: List<Any>, val winner: Any)
446-
/*
431+
data class NamedAI(val name: String, val ai: TTT3DAI) {
432+
override fun toString(): String {
433+
return name
434+
}
435+
}
436+
447437
fun fight() {
448-
Fights()
449-
.between(scorerAI(factory), alphaBetaAI(3), alphaBetaAI(4),
438+
val fightResult = Fights()
439+
.between(NamedAI("Scorer", scorerAI(factory)), alphaBetaAI(3), alphaBetaAI(4),
450440
alphaBetaAI(5), alphaBetaAI(6))
451-
.fight { players ->
441+
.fight { fight ->
452442
val game = TTT3D()
453443
while (!game.isGameOver()) {
454-
val move = players[game.currentPlayer.playerIndex].play(game)
455-
game.playAt(move)
456-
this.save(move)
457-
this.save(game)
444+
val move = fight.players[game.currentPlayer.playerIndex].ai.invoke(game)
445+
if (!game.playAt(move.second, move.first)) {
446+
print(game)
447+
throw IllegalArgumentException("Illegal move: $move")
448+
}
458449
}
459-
this.finish(game)
450+
println("Players ${fight.players} result: ${game.findWinner()}")
451+
fight.gameResult(fight.players[0], WinResult.result(game.isDraw(), game.findWinner() == TTT3DPiece.X))
452+
fight.gameResult(fight.players[1], WinResult.result(game.isDraw(), game.findWinner() == TTT3DPiece.O))
460453
}
461-
.fightEvenly(100)
454+
.fightEvenly(5)
455+
456+
fightResult.print()
462457
// .index("player")
463458
// .dataFinish(moveCount)
464459
}
465-
*/
466-
private fun alphaBetaAI(depth: Int): TTT3DAI {
467-
return { alphaBetaPlay(it, depth) }
460+
461+
private fun alphaBetaAI(depth: Int): NamedAI {
462+
return NamedAI("AlphaBeta $depth") { alphaBetaPlay(it, depth) }
468463
}
469464

470-
private fun scorerAI(factory: ScorersConfig<TTT3DPiece, TTT3DPoint>): TTT3DAI {
465+
private fun scorerAI(factory: ScorersConfig<TTT3D, TTT3DPoint>): TTT3DAI {
471466
return lambda@{
472-
val position = factory.producer(it.currentPlayer).score().best().random()
467+
val position = factory.producer(it).score().best().random()
473468
return@lambda position.x to position.y
474469
}
475470
}
@@ -502,8 +497,8 @@ net.zomis.spring.games.impls.ur.MonteCarloAI@3590ccd
502497

503498
fun main(args: Array<String>) {
504499
val game = loadMap("XXO | | XO | OX / | OXO | OOOX | / XX | XXO | | OO / OX | | | XX ")
505-
TTT3DIO(TTT3D()).playVsAI()
506-
500+
// TTT3DIO(TTT3D()).playVsAI()
501+
TTT3DIO().fight()
507502
}
508503
/*
509504
fun game() {

0 commit comments

Comments
 (0)