Skip to content

Commit

Permalink
Merge pull request #16 from donseba/gamification
Browse files Browse the repository at this point in the history
adding game examples , snake and tictactoe
  • Loading branch information
donseba authored Aug 31, 2024
2 parents 6c32986 + 2a26fc2 commit 105a3ba
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/render/go.sum
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=
1 change: 0 additions & 1 deletion examples/render/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ <h1 class="text-3xl mb-5 flex justify-center"><span>partial and full page loadin
{{ .Partials.Content }}
</div>
</div>

</div>
</body>
</html>
29 changes: 29 additions & 0 deletions examples/snake/README.md
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/
13 changes: 13 additions & 0 deletions examples/snake/board.gohtml
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>
67 changes: 67 additions & 0 deletions examples/snake/game.go
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
}
}
}
5 changes: 5 additions & 0 deletions examples/snake/go.mod
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
1 change: 1 addition & 0 deletions examples/snake/go.sum
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=
57 changes: 57 additions & 0 deletions examples/snake/index.gohtml
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>
153 changes: 153 additions & 0 deletions examples/snake/snake.go
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")
}
1 change: 1 addition & 0 deletions examples/sse/go.sum
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=
29 changes: 29 additions & 0 deletions examples/tiktaktoe/README.md
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/
Loading

0 comments on commit 105a3ba

Please sign in to comment.