game Of Life
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "modernc.org/sqlite" // Import the SQLite driver
|
||||
)
|
||||
|
||||
func initDb() *sql.DB {
|
||||
|
||||
db, err := sql.Open("sqlite", "./data/data.db?_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)")
|
||||
if err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a table
|
||||
sqlStmt := `
|
||||
CREATE TABLE IF NOT EXISTS boxes (id INTEGER PRIMARY KEY, value BOOLEAN);
|
||||
`
|
||||
|
||||
_, err = db.Exec(sqlStmt)
|
||||
if err != nil {
|
||||
e.Logger.Fatalf("%q: %s\n", err, sqlStmt)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func getBoxes() map[int]bool {
|
||||
|
||||
rows, err := db.Query("SELECT id, value FROM boxes")
|
||||
if err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
|
||||
boxes := make(map[int]bool, 1000)
|
||||
for i := 0; i < 1000; i++ {
|
||||
boxes[i] = false
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var value bool
|
||||
err = rows.Scan(&id, &value)
|
||||
if err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
boxes[id] = value
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return boxes
|
||||
}
|
||||
|
||||
func setBox(id int, value bool) {
|
||||
e.Logger.Info("START INSERT")
|
||||
|
||||
stmt, err := db.Prepare("INSERT OR REPLACE INTO boxes(id, value) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
|
||||
e.Logger.Info("PREPARED")
|
||||
|
||||
res, err := stmt.Exec(id, value)
|
||||
if err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
e.Logger.Info("INSERTED")
|
||||
|
||||
e.Logger.Info(res)
|
||||
}
|
||||
102
src/boxes.go
102
src/boxes.go
@@ -1,102 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/log"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
wsConnections = make(map[*websocket.Conn]bool)
|
||||
wsMutex sync.Mutex
|
||||
db = initDb()
|
||||
)
|
||||
|
||||
func handleWsError(err error, conn *websocket.Conn) {
|
||||
if err != nil {
|
||||
log.Errorf("Failed to handle websocket: %v", err)
|
||||
conn.Close()
|
||||
wsMutex.Lock()
|
||||
defer wsMutex.Unlock()
|
||||
delete(wsConnections, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func broadcastMessage(message string) {
|
||||
wsMutex.Lock()
|
||||
defer wsMutex.Unlock()
|
||||
|
||||
for conn := range wsConnections {
|
||||
err := websocket.Message.Send(conn, message)
|
||||
handleWsError(err, conn)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func formatMessage(index int, checked bool) string {
|
||||
message := "u:" + strconv.Itoa(index)
|
||||
if checked {
|
||||
message += ":+"
|
||||
} else {
|
||||
message += ":-"
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func handleMessage(msg string, ws *websocket.Conn) error {
|
||||
if strings.Contains(msg, "u:") {
|
||||
parts := strings.Split(msg, ":")
|
||||
index, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checked := parts[2] == "+"
|
||||
|
||||
message := formatMessage(index, checked)
|
||||
broadcastMessage(message)
|
||||
|
||||
setBox(index, checked)
|
||||
}
|
||||
if strings.Contains(msg, "ping") {
|
||||
len := len(wsConnections)
|
||||
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initWs(c echo.Context) error {
|
||||
websocket.Handler(func(ws *websocket.Conn) {
|
||||
wsMutex.Lock()
|
||||
wsConnections[ws] = true
|
||||
wsMutex.Unlock()
|
||||
|
||||
boxes := getBoxes()
|
||||
|
||||
initialState := "i:"
|
||||
for index, checked := range boxes {
|
||||
if checked {
|
||||
initialState += strconv.Itoa(index) + ";"
|
||||
}
|
||||
}
|
||||
|
||||
err := websocket.Message.Send(ws, initialState)
|
||||
handleWsError(err, ws)
|
||||
|
||||
for {
|
||||
var msg string
|
||||
err := websocket.Message.Receive(ws, &msg)
|
||||
handleWsError(err, ws)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
handleMessage(msg, ws)
|
||||
log.Infof("Received message: %s", msg)
|
||||
}
|
||||
}).ServeHTTP(c.Response(), c.Request())
|
||||
|
||||
return nil
|
||||
}
|
||||
87
src/boxes/box-db.go
Normal file
87
src/boxes/box-db.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package boxes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/labstack/gommon/log"
|
||||
|
||||
_ "modernc.org/sqlite" // Import the SQLite driver
|
||||
)
|
||||
|
||||
func initDb() *sql.DB {
|
||||
|
||||
db, err := sql.Open("sqlite", "./data/data.db?_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a table
|
||||
sqlStmt := `CREATE TABLE IF NOT EXISTS boxes (id INTEGER PRIMARY KEY, value BOOLEAN);`
|
||||
|
||||
_, err = db.Exec(sqlStmt)
|
||||
if err != nil {
|
||||
log.Fatalf("%q: %s\n", err, sqlStmt)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func GetBoxes() []Box {
|
||||
rows, err := db.Query("SELECT id, value FROM boxes")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
boxes := make([]Box, 1024)
|
||||
for i := 0; i < 1024; i++ {
|
||||
boxes[i] = Box{i, false}
|
||||
}
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var value bool
|
||||
err = rows.Scan(&id, &value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
boxes[id].Value = value
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return boxes
|
||||
}
|
||||
|
||||
func (box Box) persist() {
|
||||
|
||||
stmt, err := db.Prepare("INSERT OR REPLACE INTO boxes(id, value) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := stmt.Exec(box.Id, box.Value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
log.Info(res.LastInsertId())
|
||||
}
|
||||
|
||||
func getBox(id int) (Box, error) {
|
||||
var value bool
|
||||
|
||||
stmt, err := db.Prepare("SELECT value FROM boxes WHERE id = ?")
|
||||
if err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
err = stmt.QueryRow(id).Scan(&value)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return Box{}, nil
|
||||
}
|
||||
return Box{}, err
|
||||
}
|
||||
|
||||
return Box{id, value}, nil
|
||||
}
|
||||
152
src/boxes/boxes.go
Normal file
152
src/boxes/boxes.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package boxes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/log"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
wsConnections = make(map[*websocket.Conn]bool)
|
||||
wsMutex sync.Mutex
|
||||
db = initDb()
|
||||
random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
)
|
||||
|
||||
type Box struct {
|
||||
Id int
|
||||
Value bool
|
||||
}
|
||||
|
||||
func deserializeBox(msg string) (Box, error) {
|
||||
parts := strings.Split(msg, ":")
|
||||
index, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return Box{}, err
|
||||
}
|
||||
checked := parts[2] == "+"
|
||||
return Box{index, checked}, nil
|
||||
}
|
||||
|
||||
func (box Box) serialize() string {
|
||||
message := "b:" + strconv.Itoa(box.Id)
|
||||
if box.Value {
|
||||
message += ":+"
|
||||
} else {
|
||||
message += ":-"
|
||||
}
|
||||
return message + "\n"
|
||||
}
|
||||
|
||||
func handleWsError(err error, conn *websocket.Conn) {
|
||||
if err != nil {
|
||||
log.Errorf("Failed to handle websocket: %v", err)
|
||||
conn.Close()
|
||||
wsMutex.Lock()
|
||||
defer wsMutex.Unlock()
|
||||
delete(wsConnections, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func broadcastMessage(message string) {
|
||||
wsMutex.Lock()
|
||||
defer wsMutex.Unlock()
|
||||
|
||||
for conn := range wsConnections {
|
||||
err := websocket.Message.Send(conn, message)
|
||||
handleWsError(err, conn)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(msg string, ws *websocket.Conn) error {
|
||||
if msg == "gol" {
|
||||
matrix := make(map[string]Box)
|
||||
boxes := GetBoxes()
|
||||
index := 0
|
||||
for i := 0; i < 32; i++ {
|
||||
for j := 0; j < 32; j++ {
|
||||
matrix[fmt.Sprintf("%d-%d", i, j)] = boxes[index]
|
||||
index++
|
||||
}
|
||||
}
|
||||
log.Error(matrix)
|
||||
nextGen := make([]Box, 0)
|
||||
for id, item := range matrix {
|
||||
nextItem := shouldBeAlive(matrix, id)
|
||||
if nextItem.Value != item.Value {
|
||||
nextGen = append(nextGen, shouldBeAlive(matrix, id))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nextMessage := ""
|
||||
for _, item := range nextGen {
|
||||
item.persist()
|
||||
nextMessage += item.serialize()
|
||||
}
|
||||
broadcastMessage(nextMessage)
|
||||
|
||||
} else if msg == "random" {
|
||||
index := random.Int() % 1000
|
||||
box, err := getBox(index)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
box.Value = !box.Value
|
||||
box.persist()
|
||||
|
||||
message := box.serialize()
|
||||
broadcastMessage(message)
|
||||
|
||||
} else if strings.Contains(msg, "b:") {
|
||||
box, err := deserializeBox(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
broadcastMessage(msg)
|
||||
box.persist()
|
||||
} else if strings.Contains(msg, "ping") {
|
||||
len := len(wsConnections)
|
||||
websocket.Message.Send(ws, "pong-"+strconv.Itoa(len))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitWs(c echo.Context) error {
|
||||
websocket.Handler(func(ws *websocket.Conn) {
|
||||
wsMutex.Lock()
|
||||
wsConnections[ws] = true
|
||||
wsMutex.Unlock()
|
||||
|
||||
boxes := GetBoxes()
|
||||
msg := ""
|
||||
for _, box := range boxes {
|
||||
msg += box.serialize()
|
||||
}
|
||||
|
||||
err := websocket.Message.Send(ws, msg)
|
||||
handleWsError(err, ws)
|
||||
|
||||
for {
|
||||
var msg string
|
||||
err := websocket.Message.Receive(ws, &msg)
|
||||
handleWsError(err, ws)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
handleMessage(msg, ws)
|
||||
log.Infof("Received message: %s", msg)
|
||||
}
|
||||
}).ServeHTTP(c.Response(), c.Request())
|
||||
|
||||
return nil
|
||||
}
|
||||
70
src/boxes/matrix.go
Normal file
70
src/boxes/matrix.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package boxes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func shouldBeAlive(matrix map[string]Box, key string) Box {
|
||||
parts := strings.Split(key, "-")
|
||||
i, _ := strconv.Atoi(parts[0])
|
||||
j, _ := strconv.Atoi(parts[1])
|
||||
|
||||
self := matrix[key]
|
||||
nextGen := Box{self.Id, self.Value}
|
||||
|
||||
count := getCount(matrix, i, j)
|
||||
if self.Value && count < 2 {
|
||||
nextGen.Value = false
|
||||
} else if self.Value && count > 3 {
|
||||
nextGen.Value = false
|
||||
} else if !self.Value && count == 3 {
|
||||
nextGen.Value = true
|
||||
}
|
||||
|
||||
return nextGen
|
||||
}
|
||||
|
||||
func getCount(matrix map[string]Box, i, j int) int {
|
||||
count := 0
|
||||
|
||||
if isAlive(matrix, i-1, j-1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i-1, j) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i-1, j+1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i, j-1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i, j+1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i+1, j-1) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i+1, j) {
|
||||
count++
|
||||
}
|
||||
|
||||
if isAlive(matrix, i+1, j+1) {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func isAlive(matrix map[string]Box, i, j int) bool {
|
||||
item, ok := matrix[fmt.Sprintf("%d-%d", i, j)]
|
||||
return ok && item.Value
|
||||
}
|
||||
@@ -30,9 +30,9 @@ func broadcastMessage(message string, ws *websocket.Conn) {
|
||||
defer wsMutex.Unlock()
|
||||
|
||||
for conn := range wsConnections {
|
||||
// if ws == conn {
|
||||
// continue
|
||||
// }
|
||||
if ws == conn {
|
||||
continue
|
||||
}
|
||||
err := websocket.Message.Send(conn, message)
|
||||
handleWsError(err, conn)
|
||||
|
||||
|
||||
13
src/main.go
13
src/main.go
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/labstack/gommon/log"
|
||||
"knet.sk/src/boxes"
|
||||
"knet.sk/src/draw"
|
||||
)
|
||||
|
||||
@@ -25,10 +26,10 @@ func NewTemplates() *Templates {
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Boxes map[int]bool
|
||||
Boxes []boxes.Box
|
||||
}
|
||||
|
||||
func newPage(boxes map[int]bool) Page {
|
||||
func newPage(boxes []boxes.Box) Page {
|
||||
return Page{
|
||||
Boxes: boxes,
|
||||
}
|
||||
@@ -50,20 +51,16 @@ func main() {
|
||||
e.Static("/js", "js")
|
||||
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.Render(200, "index", newPage(getBoxes()))
|
||||
return c.Render(200, "index", newPage(boxes.GetBoxes()))
|
||||
})
|
||||
e.GET("/boxes/ws", boxes.InitWs)
|
||||
|
||||
e.GET("/ws", initWs)
|
||||
|
||||
//favicon
|
||||
e.File("/favicon.ico", "images/favicon.ico")
|
||||
|
||||
e.GET("/draw", draw.Page)
|
||||
e.GET("/draw/ws", draw.InitWs)
|
||||
|
||||
e.Logger.Fatal(e.Start(":54321"))
|
||||
|
||||
defer db.Close()
|
||||
}
|
||||
|
||||
func healthCheck(c echo.Context) error {
|
||||
|
||||
Reference in New Issue
Block a user