working logics

This commit is contained in:
Arnas Udovic 2023-12-29 18:22:54 +02:00
parent 06849170e4
commit 0aae6ce4af
5 changed files with 221 additions and 19 deletions

BIN
assets/cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/nought.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

168
game.go
View file

@ -1,23 +1,183 @@
package main
import (
"strconv"
)
const (
GameStateInProgress int = 0
GameStateEnded = 1
GameStateDraw = 2
GameStateAIWon = 3
GameStateAILost = 4
GameStateWaitingUser = 1
GameStateEnded = 2
GameStateDraw = 3
GameStateAIWon = 4
GameStateAILost = 5
)
const (
ImageCross string = "cross"
ImageNought = "nought"
)
const (
WinnerNone int = 0
WinnerAI = 1
WinnerUser = 2
)
var field2point = map[int][2]int{
1: {0, 0},
2: {0, 1},
3: {0, 2},
4: {1, 0},
5: {1, 1},
6: {1, 2},
7: {2, 0},
8: {2, 1},
9: {2, 2},
}
var FieldClass = map[int]string{
1: "tt-item br bb",
2: "tt-item bb",
3: "tt-item bl bb",
4: "tt-item br",
5: "tt-item",
6: "tt-item bl",
7: "tt-item bt br",
8: "tt-item bt",
9: "tt-item bl bt",
}
var winner = [][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{1, 4, 7},
{2, 5, 8},
{3, 6, 9},
{1, 5, 9},
{7, 5, 3},
}
type Field struct {
disabled bool
image string
line bool
}
type Game struct {
gameFlow string
aiSecond bool
aiFields []int
userFields []int
fields map[int]Field
state int
winnerFields [3]int
winner int
}
func newGame(gameFlow string, aiSecond bool) *Game {
fields := make(map[int]Field)
for i := 1; i < 10; i++ {
field := &Field{
disabled: false,
}
fields[i] = *field
}
game := &Game{
gameFlow: gameFlow,
aiSecond: aiSecond,
fields: fields,
winner: WinnerNone,
}
for i, f := range str2array(game.gameFlow) {
fieldNum, _ := strconv.ParseInt(f, 0, 16)
if field, ok := game.fields[int(fieldNum)]; ok {
field.disabled = true
if i%2 == 0 {
field.image = ImageCross
} else {
field.image = ImageNought
}
game.fields[int(fieldNum)] = field
}
if (i%2 == 0) == game.aiSecond {
game.userFields = append(game.userFields, int(fieldNum))
} else {
game.aiFields = append(game.aiFields, int(fieldNum))
}
}
game.calculateState()
if game.isFinished() && game.winner != WinnerNone {
for _, f := range game.winnerFields {
if field, ok := game.fields[f]; ok {
field.line = true
game.fields[f] = field
}
}
for i := 1; i < 10; i++ {
if field, ok := game.fields[i]; ok {
field.disabled = true
game.fields[i] = field
}
}
}
return game
}
func (game *Game) isFinished() bool {
return game.state > GameStateWaitingUser
}
func (game *Game) calculateState() {
game.state = GameStateInProgress
if "0" == game.gameFlow {
game.state = GameStateWaitingUser
return
}
for _, w := range winner {
if isSubArray(w[:], game.aiFields) {
game.state = GameStateAIWon
game.winnerFields = w
game.winner = WinnerAI
return
}
if isSubArray(w[:], game.userFields) {
game.state = GameStateAILost
game.winnerFields = w
game.winner = WinnerUser
return
}
}
if len(game.gameFlow) == 9 {
game.state = GameStateDraw
}
}
func isSubArray(a, b []int) bool {
mb := make(map[int]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
var diff []int
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return len(diff) == 0
}

31
main.go
View file

@ -22,6 +22,8 @@ func main() {
r.StaticFile("/favicon.ico", "./assets/favicon.ico")
r.StaticFile("/sgs.png", "./assets/sgs.png")
r.StaticFile("/en.png", "./assets/en.png")
r.StaticFile("/cross.png", "./assets/cross.png")
r.StaticFile("/nought.png", "./assets/nought.png")
r.GET("/", func(c *gin.Context) {
lang := getLang(c)
@ -63,13 +65,40 @@ func main() {
game := newGame(gameFlow, aiSecond)
fmt.Println(gameState)
if gameState == GameStateInProgress && !game.isFinished() {
gameFlow = gameFlow + getNewClick(gameFlow)
game = newGame(gameFlow, aiSecond)
}
// if game ended - save results
c.HTML(http.StatusOK, "index", gin.H{
"title": "Index title!",
"lang": lang,
"gameFlow": gameFlow,
"game": game,
"fields": [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
"fieldClass": func(game *Game, fieldNum int) string {
fieldClass := FieldClass[fieldNum]
if field, ok := game.fields[fieldNum]; ok {
if len(field.image) > 0 {
fieldClass = fieldClass + " " + field.image
}
if field.line {
fieldClass = fieldClass + " line"
}
}
return fieldClass
},
"disabled": func(game *Game, fieldNum int) bool {
if field, ok := game.fields[fieldNum]; ok {
return field.disabled
}
return false
},
})
})
@ -108,7 +137,7 @@ func getLang(c *gin.Context) string {
func getGameFlow(c *gin.Context) (string, int) {
gameFlow := c.Param("game")
if "0" == gameFlow {
return gameFlow, GameStateInProgress
return gameFlow, GameStateWaitingUser
}
if "0" == string(gameFlow[0]) {

View file

@ -22,21 +22,34 @@
.bl { border-left: 5px solid #333; }
.br { border-right: 5px solid #333; }
.cross { background-image: url('/cross.png'); }
.nought { background-image: url('/nought.png'); }
.line.cross {
background-color: #f88;
border: 2px solid #f33;
}
.line.nought {
background-color: #aaf;
border: 2px solid #33f;
}
</style>
{{end}}
{{define "content"}}
<div class="main-content">
<a class="tt-item br bb" href="{{ .gameFlow -}}1">&nbsp;</a>
<a class="tt-item bb" href="{{ .gameFlow -}}2">&nbsp;</a>
<a class="tt-item bl bb" href="{{ .gameFlow -}}3">&nbsp;</a>
<a class="tt-item br" href="{{ .gameFlow -}}4">&nbsp;</a>
<a class="tt-item" href="{{ .gameFlow -}}5">&nbsp;</a>
<a class="tt-item bl" href="{{ .gameFlow -}}6">&nbsp;</a>
<a class="tt-item bt br" href="{{ .gameFlow -}}7">&nbsp;</a>
<a class="tt-item bt" href="{{ .gameFlow -}}8">&nbsp;</a>
<a class="tt-item bl bt" href="{{ .gameFlow -}}9">&nbsp;</a>
{{ $Game := .game }}
{{ $GameFlow := .gameFlow }}
{{ range .fields }}
{{ if call $.disabled $Game . }}
<span class="{{ call $.fieldClass $Game . }}"></span>
{{ else }}
<a class="{{ call $.fieldClass $Game . }}" href="{{ $GameFlow -}}{{- . }}"></a>
{{ end }}
{{ end }}
</div>
<b>Game:</b> {{.gameFlow}}