diff --git a/.gitignore b/.gitignore index adf8f72..e34f2c7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +game.db diff --git a/db.go b/db.go new file mode 100644 index 0000000..c23178d --- /dev/null +++ b/db.go @@ -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] +} diff --git a/game.go b/game.go index a2a4144..58fb43e 100644 --- a/game.go +++ b/game.go @@ -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 { diff --git a/go.mod b/go.mod index 5fd6e96..6d73b96 100644 --- a/go.mod +++ b/go.mod @@ -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 ( diff --git a/go.sum b/go.sum index c9c2056..d8f2e09 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/main.go b/main.go index c391513..21e8fad 100644 --- a/main.go +++ b/main.go @@ -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 +} diff --git a/views/statistics.en.html b/views/statistics.en.html index 82a2c15..e2e60e8 100644 --- a/views/statistics.en.html +++ b/views/statistics.en.html @@ -1,6 +1,7 @@

Game: {{.gameFlow}}
Total plaied: {{.total}}
- AI Won: {{.aiWon}} {{.aiWonPercent}}
- Draw: {{.draw}} {{.drawPercent}}
+ AI Won: {{.aiWon}} {{.aiWonPercent}}
+ Draw: {{.draw}} {{.drawPercent}}
+ Last AI lost: {{.lastAiLost}}

diff --git a/views/statistics.sgs.html b/views/statistics.sgs.html index 052f9af..3f7d213 100644 --- a/views/statistics.sgs.html +++ b/views/statistics.sgs.html @@ -1,6 +1,7 @@

Žaidėms: {{.gameFlow}}
Ėš vėsa žaidėmu: {{.total}}
- Laimiejė DI: {{.aiWon}} {{.aiWonPercent}}
- Līgiuoms: {{.draw}} {{.drawPercent}}
+ Laimiejė DI: {{.aiWon}} {{.aiWonPercent}}
+ Līgiuoms: {{.draw}} {{.drawPercent}}
+ Paskutinis DI pralaimiejėms: {{.lastAiLost}}