added AI on db

This commit is contained in:
Arnas Udovic 2023-12-30 00:15:11 +02:00
parent 04851913df
commit 2a18c27c02
8 changed files with 205 additions and 14 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@
# Go workspace file
go.work
game.db

108
db.go Normal file
View file

@ -0,0 +1,108 @@
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func connectDB() (*sql.DB, error) {
db, err := sql.Open("sqlite3", "./game.db")
if err != nil {
return db, err
}
err = checkPreinstall(db)
if err != nil {
return db, err
}
return db, nil
}
func checkPreinstall(db *sql.DB) error {
sqlStmt := `
CREATE TABLE IF NOT EXISTS Games (id INTEGER NOT NULL PRIMARY KEY, game_flow TEXT NOT NULL, first_won BOOL);
CREATE UNIQUE INDEX IF NOT EXISTS game_flow_idx ON Games (game_flow);
CREATE TABLE IF NOT EXISTS Stats (id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, count INT, comment TEXT);
CREATE UNIQUE INDEX IF NOT EXISTS stats_idx ON Stats (name);
INSERT OR IGNORE INTO Stats (name, count) VALUES ('total', 0);
INSERT OR IGNORE INTO Stats (name, count) VALUES ('ai_won', 0);
INSERT OR IGNORE INTO Stats (name, count) VALUES ('draw', 0);
INSERT OR IGNORE INTO Stats (name, comment) VALUES ('last_ai_lost', NULL);
`
_, err := db.Exec(sqlStmt)
if err != nil {
return err
}
return nil
}
func getStatsCounter(db *sql.DB, name string) (int, error) {
stmt, err := db.Prepare("SELECT count FROM Stats WHERE name = ?")
if err != nil {
return 0, err
}
defer stmt.Close()
var count int
err = stmt.QueryRow(name).Scan(&count)
if err != nil {
return 0, nil
}
return count, nil
}
func getStatsComment(db *sql.DB, name string) (string, error) {
stmt, err := db.Prepare("SELECT comment FROM Stats WHERE name = ?")
if err != nil {
return "", err
}
defer stmt.Close()
var comment string
err = stmt.QueryRow(name).Scan(&comment)
if err != nil {
return "", nil
}
return comment, nil
}
func saveGameLog(db *sql.DB, game *Game) {
db.Exec("UPDATE Stats SET count=count+1 WHERE name='total';")
if game.hasWinner() {
db.Exec(
"INSERT OR IGNORE INTO Games (game_flow, first_won) VALUES (?, ?);",
game.gameFlow,
game.isFirstWon(),
)
if game.isAiWon() {
db.Exec("UPDATE Stats SET count=count+1 WHERE name='ai_won';")
} else {
db.Exec("UPDATE Stats SET comment=datetime() WHERE name='last_ai_lost';")
}
} else {
db.Exec("UPDATE Stats SET count=count+1 WHERE name='draw';")
}
}
func getWinnerNextStep(db *sql.DB, game *Game) string {
stmt, err := db.Prepare("SELECT game_flow FROM Games WHERE game_flow LIKE ? AND first_won = ?")
if err != nil {
return ""
}
defer stmt.Close()
var gameFlow string
err = stmt.QueryRow(game.gameFlow+"%", !game.aiSecond).Scan(&gameFlow)
if err != nil {
return ""
}
return gameFlow[len(game.gameFlow) : len(game.gameFlow)+1]
}

20
game.go
View file

@ -167,6 +167,26 @@ func (game *Game) calculateState() {
}
}
func (game *Game) hasWinner() bool {
return game.winner != WinnerNone
}
func (game *Game) isAiWon() bool {
return game.winner == WinnerAI
}
func (game *Game) isFirstWon() bool {
if game.winner == WinnerNone {
return false
}
if game.aiSecond && game.winner == WinnerUser {
return true
}
return !game.aiSecond && game.winner == WinnerAI
}
func isSubArray(a, b []int) bool {
mb := make(map[int]struct{}, len(b))
for _, x := range b {

3
go.mod
View file

@ -4,8 +4,9 @@ go 1.21.5
require (
github.com/foolin/goview v0.3.0
github.com/gin-gonic/gin v1.9.1
github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.9.1
github.com/mattn/go-sqlite3 v2.0.3+incompatible
)
require (

10
go.sum
View file

@ -9,6 +9,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhD
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/foolin/goview v0.3.0 h1:q5wKwXKEFb20dMRfYd59uj5qGCo7q4L9eVHHUjmMWrg=
@ -23,6 +24,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -33,6 +36,7 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
@ -58,6 +62,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -67,6 +73,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@ -78,6 +85,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
@ -115,10 +123,12 @@ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=

67
main.go
View file

@ -1,7 +1,9 @@
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"strconv"
@ -12,6 +14,12 @@ import (
)
func main() {
db, err := connectDB()
if err != nil {
log.Fatal(err)
}
defer db.Close()
r := gin.Default()
r.HTMLRender = ginview.Default()
@ -47,11 +55,16 @@ func main() {
game := newGame(gameFlow, aiSecond)
if gameState == GameStateInProgress && !game.isFinished() {
gameFlow = gameFlow + getNewClick(gameFlow)
gameFlow = gameFlow + getNewStep(db, game)
game = newGame(gameFlow, aiSecond)
}
// if game ended - save results
if game.isFinished() {
saveGameLog(db, game)
}
stats := getStats(db)
fmt.Println(game)
c.HTML(http.StatusOK, "index", gin.H{
"title": "Index title!",
@ -79,11 +92,12 @@ func main() {
return false
},
"total": 8787,
"aiWon": 232,
"aiWonPercent": "3.5%",
"draw": 6777,
"drawPercent": "94.4%",
"total": stats["total"],
"aiWon": stats["aiWon"],
"aiWonPercent": stats["aiWonPercent"],
"draw": stats["draw"],
"drawPercent": stats["drawPercent"],
"lastAiLost": stats["lastAiLost"],
})
})
@ -131,8 +145,13 @@ func getGameFlow(c *gin.Context) (string, int) {
return gameFlow, GameStateInProgress
}
func getNewClick(gameFlow string) string {
return difference("123456789", gameFlow)[0]
func getNewStep(db *sql.DB, game *Game) string {
nextStep := getWinnerNextStep(db, game)
if "" != nextStep {
return nextStep
}
return difference("123456789", game.gameFlow)[0]
}
func difference(a, b string) []string {
@ -158,3 +177,33 @@ func str2array(str string) []string {
return splited
}
func getStats(db *sql.DB) map[string]string {
stats := make(map[string]string)
total, _ := getStatsCounter(db, "total")
stats["total"] = strconv.Itoa(total)
aiWon, _ := getStatsCounter(db, "ai_won")
stats["aiWon"] = strconv.Itoa(aiWon)
if total > 0 {
stats["aiWonPercent"] = fmt.Sprintf("%.2f%%", float64(aiWon)/float64(total)*100)
} else {
stats["aiWonPercent"] = "0%"
}
draw, _ := getStatsCounter(db, "draw")
stats["draw"] = strconv.Itoa(draw)
if total > 0 {
stats["drawPercent"] = fmt.Sprintf("%.2f%%", float64(draw)/float64(total)*100)
} else {
stats["drawPercent"] = "0%"
}
lastAiLost, _ := getStatsComment(db, "last_ai_lost")
stats["lastAiLost"] = lastAiLost
return stats
}

View file

@ -1,6 +1,7 @@
<p>
<b>Game:</b> {{.gameFlow}}<br>
<b>Total plaied:</b> {{.total}}<br>
<b>AI Won:</b> {{.aiWon}} {{.aiWonPercent}}<br>
<b>Draw:</b> {{.draw}} {{.drawPercent}}<br>
<b>AI Won:</b> {{.aiWon}} <sup class="text-danger">{{.aiWonPercent}}</sup><br>
<b>Draw:</b> {{.draw}} <sup class="text-danger">{{.drawPercent}}</sup><br>
<b>Last AI lost:</b> {{.lastAiLost}}<br>
</p>

View file

@ -1,6 +1,7 @@
<p>
<b>Žaidėms:</b> {{.gameFlow}}<br>
<b>Ėš vėsa žaidėmu:</b> {{.total}}<br>
<b>Laimiejė DI:</b> {{.aiWon}} {{.aiWonPercent}}<br>
<b>Līgiuoms:</b> {{.draw}} {{.drawPercent}}<br>
<b>Laimiejė DI:</b> {{.aiWon}} <sup class="text-danger">{{.aiWonPercent}}</sup><br>
<b>Līgiuoms:</b> {{.draw}} <sup class="text-danger">{{.drawPercent}}</sup><br>
<b>Paskutinis DI pralaimiejėms:</b> {{.lastAiLost}}<br>
</p>