-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from donseba/gamification
adding game examples , snake and tictactoe
- Loading branch information
Showing
16 changed files
with
579 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github.com/donseba/go-htmx v1.9.0/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
|
||
## Getting Started | ||
|
||
* If not already installed, please install the [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) command. | ||
|
||
```console | ||
go install golang.org/x/tools/cmd/gonew@latest | ||
``` | ||
|
||
* Create a new project using this template. | ||
- Second argument passed to `gonew` is a module path of your new app. | ||
|
||
```console | ||
gonew github.com/donseba/go-htmx/examples/snake your.module/my-app # e.g. github.com/donseba/my-app | ||
cd my-app | ||
go mod tidy | ||
go build | ||
|
||
``` | ||
|
||
## Testing | ||
|
||
- Start your app | ||
|
||
```console | ||
./my-app | ||
``` | ||
|
||
- Open your browser http://localhost:3210/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<table> | ||
<tbody> | ||
{{range $i, $row := .Data.game.Board }} | ||
<tr> | ||
{{range $j, $cell := $row}} | ||
<td> | ||
{{ $cell }} | ||
</td> | ||
{{end}} | ||
</tr> | ||
{{end}} | ||
</tbody> | ||
</table> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package main | ||
|
||
import "math/rand" | ||
|
||
func moveSnake(game *SnakeGame) { | ||
head := game.Snake[0] | ||
newHead := Position{X: head.X + game.Dir.X, Y: head.Y + game.Dir.Y} | ||
|
||
// Wrap around the edges of the board | ||
if newHead.X < 0 { | ||
newHead.X = 19 // Move to the rightmost edge | ||
} else if newHead.X >= 20 { | ||
newHead.X = 0 // Move to the leftmost edge | ||
} | ||
|
||
if newHead.Y < 0 { | ||
newHead.Y = 19 // Move to the bottom edge | ||
} else if newHead.Y >= 20 { | ||
newHead.Y = 0 // Move to the top edge | ||
} | ||
|
||
// Check if the snake eats the food | ||
if newHead == game.Food { | ||
// Grow the snake by not removing the last part | ||
placeFood(game) // Place new food | ||
} else { | ||
// Move the snake by removing the tail | ||
game.Snake = game.Snake[:len(game.Snake)-1] | ||
} | ||
|
||
// Add the new head to the front of the snake | ||
game.Snake = append([]Position{newHead}, game.Snake...) | ||
|
||
// Update the board | ||
for i := range game.Board { | ||
for j := range game.Board[i] { | ||
game.Board[i][j] = "" | ||
} | ||
} | ||
for _, pos := range game.Snake { | ||
game.Board[pos.X][pos.Y] = "S" | ||
} | ||
// Place food on the board | ||
game.Board[game.Food.X][game.Food.Y] = "F" | ||
} | ||
|
||
func placeFood(game *SnakeGame) { | ||
for { | ||
x := rand.Intn(20) | ||
y := rand.Intn(20) | ||
foodPos := Position{X: x, Y: y} | ||
|
||
// Ensure food is not placed on the snake | ||
occupied := false | ||
for _, pos := range game.Snake { | ||
if pos == foodPos { | ||
occupied = true | ||
break | ||
} | ||
} | ||
if !occupied { | ||
game.Food = foodPos | ||
game.Board[game.Food.X][game.Food.Y] = "F" | ||
break | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/donseba/go-htmx/examples/snake | ||
|
||
go 1.23 | ||
|
||
require github.com/donseba/go-htmx v1.9.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github.com/donseba/go-htmx v1.9.0/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Snake Game with SSE</title> | ||
<script src="https://unpkg.com/htmx.org@2.0.2"></script> | ||
<script src="https://unpkg.com/htmx-ext-sse@2.2.2/sse.js"></script> | ||
<style> | ||
table { | ||
border-collapse: collapse; | ||
margin: 20px auto; | ||
} | ||
td { | ||
width: 20px; | ||
height: 20px; | ||
text-align: center; | ||
vertical-align: middle; | ||
border: 1px solid #000; | ||
background-color: #ddd; | ||
} | ||
.snake { | ||
background-color: #000; | ||
} | ||
</style> | ||
<script> | ||
function keyup(event) { | ||
if (event.key === 'ArrowUp' || event.key === 'ArrowRight' || event.key === 'ArrowDown' || event.key === 'ArrowLeft') { | ||
let direction; | ||
|
||
if (event.key === 'ArrowUp') { | ||
direction = 'up'; | ||
} else if (event.key === 'ArrowRight') { | ||
direction = 'right'; | ||
} else if (event.key === 'ArrowDown') { | ||
direction = 'down'; | ||
} else if (event.key === 'ArrowLeft') { | ||
direction = 'left'; | ||
} | ||
|
||
htmx.ajax('PUT', '/move/'+direction, '#dummy') | ||
} | ||
|
||
if (event.key === 'Space' || event.key === ' ' || event.key === 'p') { | ||
htmx.ajax('PUT', '/pause', '#dummy') | ||
} | ||
} | ||
</script> | ||
</head> | ||
<body hx-on:keyup="keyup(event)"> | ||
<span id="dummy"></span> | ||
<h1 style="text-align: center;">Snake Game with SSE</h1> | ||
<div sse-swap="board" hx-ext="sse" sse-connect="/sse"> | ||
{{ .Partials.board }} | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"github.com/donseba/go-htmx" | ||
"github.com/donseba/go-htmx/sse" | ||
"log" | ||
"math/rand" | ||
"net/http" | ||
"regexp" | ||
"sync" | ||
"time" | ||
) | ||
|
||
type ( | ||
App struct { | ||
htmx *htmx.HTMX | ||
game SnakeGame | ||
} | ||
|
||
Position struct { | ||
X, Y int | ||
} | ||
|
||
SnakeGame struct { | ||
Board [20][20]string | ||
Snake []Position | ||
Food Position | ||
Dir Position | ||
Mu sync.Mutex | ||
Active bool | ||
} | ||
) | ||
|
||
var ( | ||
sseManager sse.Manager | ||
) | ||
|
||
func main() { | ||
|
||
app := &App{ | ||
htmx: htmx.New(), | ||
game: SnakeGame{ | ||
Dir: Position{X: 1, Y: 0}, | ||
Snake: []Position{{X: 10, Y: 10}, {X: 9, Y: 10}, {X: 8, Y: 10}}, | ||
Active: true, | ||
}, | ||
} | ||
|
||
placeFood(&app.game) | ||
|
||
sseManager = sse.NewManager(5) | ||
|
||
go func() { | ||
for { | ||
app.game.Mu.Lock() | ||
if app.game.Active { | ||
moveSnake(&app.game) | ||
} | ||
app.game.Mu.Unlock() | ||
|
||
page := htmx.NewComponent("board.gohtml").SetData(map[string]any{ | ||
"game": &app.game, | ||
}) | ||
|
||
out, err := page.Render(context.Background()) | ||
if err != nil { | ||
log.Printf("error rendering page: %v", err.Error()) | ||
} | ||
|
||
re := regexp.MustCompile(`\s+`) | ||
stringOut := re.ReplaceAllString(string(out), "") | ||
|
||
sseManager.Send(sse.NewMessage(stringOut).WithEvent("board")) | ||
time.Sleep(150 * time.Millisecond) | ||
} | ||
}() | ||
|
||
mux := http.NewServeMux() | ||
mux.Handle("GET /", http.HandlerFunc(app.Home)) | ||
mux.Handle("GET /sse", http.HandlerFunc(app.SSE)) | ||
mux.Handle("PUT /move/{dir}", http.HandlerFunc(app.Move)) | ||
mux.Handle("PUT /pause", http.HandlerFunc(app.Pause)) | ||
|
||
err := http.ListenAndServe(":3210", mux) | ||
log.Fatal(err) | ||
} | ||
|
||
func (a *App) Home(w http.ResponseWriter, r *http.Request) { | ||
a.game.Mu.Lock() | ||
defer a.game.Mu.Unlock() | ||
|
||
h := a.htmx.NewHandler(w, r) | ||
|
||
page := htmx.NewComponent("board.gohtml").SetData(map[string]any{ | ||
"game": &a.game, | ||
}).Wrap(mainContent(), "board") | ||
|
||
_, err := h.Render(r.Context(), page) | ||
if err != nil { | ||
log.Printf("error rendering page: %v", err.Error()) | ||
} | ||
} | ||
|
||
func (a *App) Move(w http.ResponseWriter, r *http.Request) { | ||
a.game.Mu.Lock() | ||
defer a.game.Mu.Unlock() | ||
|
||
dir := r.PathValue("dir") | ||
switch dir { | ||
case "up": | ||
a.game.Dir = Position{X: -1, Y: 0} | ||
case "down": | ||
a.game.Dir = Position{X: 1, Y: 0} | ||
case "left": | ||
a.game.Dir = Position{X: 0, Y: -1} | ||
case "right": | ||
a.game.Dir = Position{X: 0, Y: 1} | ||
} | ||
|
||
w.WriteHeader(http.StatusNoContent) | ||
_, _ = w.Write(nil) | ||
} | ||
|
||
func (a *App) Pause(w http.ResponseWriter, r *http.Request) { | ||
a.game.Mu.Lock() | ||
defer a.game.Mu.Unlock() | ||
|
||
a.game.Active = !a.game.Active | ||
|
||
w.WriteHeader(http.StatusNoContent) | ||
_, _ = w.Write(nil) | ||
} | ||
|
||
func (a *App) SSE(w http.ResponseWriter, r *http.Request) { | ||
cl := sse.NewClient(randStringRunes(10)) | ||
|
||
sseManager.Handle(w, r, cl) | ||
} | ||
|
||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | ||
|
||
func randStringRunes(n int) string { | ||
b := make([]rune, n) | ||
for i := range b { | ||
b[i] = letterRunes[rand.Intn(len(letterRunes))] | ||
} | ||
return string(b) | ||
} | ||
|
||
func mainContent() htmx.RenderableComponent { | ||
return htmx.NewComponent("index.gohtml") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github.com/donseba/go-htmx v1.9.0/go.mod h1:8PTAYvNKf8+QYis+DpAsggKz+sa2qljtMgvdAeNBh5s= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
|
||
## Getting Started | ||
|
||
* If not already installed, please install the [gonew](https://pkg.go.dev/golang.org/x/tools/cmd/gonew) command. | ||
|
||
```console | ||
go install golang.org/x/tools/cmd/gonew@latest | ||
``` | ||
|
||
* Create a new project using this template. | ||
- Second argument passed to `gonew` is a module path of your new app. | ||
|
||
```console | ||
gonew github.com/donseba/go-htmx/examples/tiktaktoe your.module/my-app # e.g. github.com/donseba/my-app | ||
cd my-app | ||
go mod tidy | ||
go build | ||
|
||
``` | ||
|
||
## Testing | ||
|
||
- Start your app | ||
|
||
```console | ||
./my-app | ||
``` | ||
|
||
- Open your browser http://localhost:3210/ |
Oops, something went wrong.