Skip to content

Commit 814ecc2

Browse files
committed
Game screens + htmx flow
1 parent f622bfd commit 814ecc2

File tree

4 files changed

+279
-125
lines changed

4 files changed

+279
-125
lines changed

internal/game/rps.go

Lines changed: 189 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package game
22

33
import (
4+
"bytes"
5+
"context"
46
"fmt"
7+
"math/rand"
58
"net/http"
9+
"rps_main/internal/templates"
10+
"strconv"
611
"sync"
712
"time"
813

@@ -39,8 +44,9 @@ const (
3944
const RoundTime = 30 * time.Second
4045

4146
type HTMXMessage struct {
42-
Headers interface{} `json:"HEADERS"`
43-
Choice string `json:"choice"`
47+
Headers interface{} `json:"HEADERS"`
48+
Choice string `json:"choice"`
49+
PlayerId string `json:"playerId"`
4450
}
4551

4652
type ClientMove struct {
@@ -70,18 +76,15 @@ func (c *Client) readPump() {
7076
break
7177
}
7278

73-
// do something with message
74-
fmt.Println("htmxMsg", htmxMsg.Choice)
75-
// call server setChoice
76-
//c.gs.setChoice(c.id, move(htmxMsg.Text))
77-
//fmt.Println("htmxMsg", htmxMsg)
78-
//c.gs.broadcast <- message
79+
// set move
80+
c.gs.setChoice(c.id, move(htmxMsg.Choice))
7981
}
8082
}
8183

8284
func (c *Client) writePump() {
8385
defer c.conn.Close()
8486
for msg := range c.send {
87+
fmt.Println("write", string(msg))
8588
err := c.conn.WriteMessage(websocket.TextMessage, msg)
8689
if err != nil {
8790
fmt.Println("write err", err)
@@ -100,6 +103,7 @@ type Game struct {
100103
Id string
101104
GameMode string
102105
Type string
106+
BestOf int
103107
State int
104108
Round int
105109
Timer time.Duration
@@ -115,6 +119,7 @@ type GameServer struct {
115119
unregister chan *Client
116120
broadcast chan []byte
117121
ticker *time.Ticker
122+
tickerDone chan bool
118123
game *Game
119124
mutex sync.Mutex
120125
}
@@ -145,13 +150,15 @@ func NewServer(bestOf int) string {
145150
broadcast: make(chan []byte),
146151
register: make(chan *Client),
147152
unregister: make(chan *Client),
148-
ticker: time.NewTicker(time.Second),
153+
tickerDone: make(chan bool),
149154
game: &Game{
150-
Id: id,
151-
Type: TypeAI,
152-
State: StatePlaying,
153-
Round: 1,
154-
Timer: RoundTime,
155+
Id: id,
156+
GameMode: "rps",
157+
Type: TypeAI,
158+
BestOf: bestOf,
159+
State: StatePlaying,
160+
Round: 0,
161+
Timer: RoundTime,
155162
Score: Score{
156163
PlayerOne: 0,
157164
PlayerTwo: 0,
@@ -160,44 +167,105 @@ func NewServer(bestOf int) string {
160167
},
161168
}
162169
fmt.Println("new server", id)
163-
manager.servers[id].ticker.Stop()
164170
go manager.servers[id].run()
165171
return id
166172
}
167173

174+
func (gs *GameServer) Connect(c echo.Context) error {
175+
conn, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
176+
if err != nil {
177+
fmt.Println("ws err", err)
178+
return err
179+
}
180+
client := &Client{
181+
gs: gs,
182+
conn: conn,
183+
send: make(chan []byte),
184+
}
185+
gs.register <- client
186+
go client.readPump()
187+
go client.writePump()
188+
return nil
189+
}
190+
191+
func (gs *GameServer) cleanup() {
192+
fmt.Println("cleaning up server", gs.id)
193+
gs.tickerDone <- true
194+
close(gs.broadcast)
195+
close(gs.register)
196+
close(gs.unregister)
197+
manager.mutex.Lock()
198+
delete(manager.servers, gs.id)
199+
defer manager.mutex.Unlock()
200+
}
201+
168202
func (gs *GameServer) run() {
169203
fmt.Println("running server", gs.id)
204+
gs.tick()
170205
for {
171206
select {
172207
case client := <-gs.register:
173208
gs.clients[client] = true
209+
client.id = "playerOne"
174210
fmt.Println("client registered", client.id)
211+
if len(gs.clients) == 2 || gs.game.Type == TypeAI {
212+
gs.startRound()
213+
}
175214
case client := <-gs.unregister:
176215
if _, ok := gs.clients[client]; ok {
216+
fmt.Println("client unregistered", client.id)
177217
close(client.send)
178218
delete(gs.clients, client)
179219
client.conn.Close()
180220
}
221+
if len(gs.clients) == 0 {
222+
gs.cleanup()
223+
return
224+
}
181225

182226
// Send message to all clients
183227
case msg := <-gs.broadcast:
184228
for client := range gs.clients {
185229
client.send <- msg
186230
}
231+
}
232+
}
233+
}
187234

188-
case <-gs.ticker.C:
189-
if gs.game.State == StatePlaying {
190-
gs.mutex.Lock()
191-
gs.game.Timer -= time.Second
192-
gs.mutex.Unlock()
193-
if gs.game.Timer <= 0 {
194-
gs.endRound()
195-
} else {
196-
//gs.broadcast <- temaplates.GameTimer(gs.game.Timer)
235+
func (gs *GameServer) tick() {
236+
if gs.ticker == nil {
237+
gs.ticker = time.NewTicker(time.Second)
238+
}
239+
go func() {
240+
for {
241+
select {
242+
case <-gs.tickerDone:
243+
gs.ticker.Stop()
244+
return
245+
case <-gs.ticker.C:
246+
if gs.game.State == StatePlaying {
247+
gs.mutex.Lock()
248+
gs.game.Timer -= time.Second
249+
gs.mutex.Unlock()
250+
if gs.game.Timer < 0 {
251+
gs.endRound()
252+
} else {
253+
gs.broadcast <- []byte(renderScoreboard(gs.game))
254+
}
255+
} else if gs.game.State == StateRoundOver {
256+
gs.mutex.Lock()
257+
gs.game.Timer -= time.Second
258+
gs.mutex.Unlock()
259+
if gs.game.Timer < 0 {
260+
gs.startRound()
261+
gs.broadcast <- []byte(renderSelection())
262+
} else {
263+
gs.broadcast <- []byte(renderScoreboard(gs.game))
264+
}
197265
}
198266
}
199267
}
200-
}
268+
}()
201269
}
202270

203271
func (gs *GameServer) startRound() {
@@ -207,18 +275,30 @@ func (gs *GameServer) startRound() {
207275
gs.game.PlayerTwoChoice = ""
208276
gs.game.Round++
209277
gs.game.Timer = RoundTime
210-
gs.ticker.Reset(time.Second)
211278
gs.mutex.Unlock()
279+
gs.ticker.Reset(time.Second)
280+
}
281+
282+
func makeAIChoice() move {
283+
choices := []move{ChoiceRock, ChoicePaper, ChoiceScissors}
284+
return choices[rand.Intn(len(choices))]
212285
}
213286

214287
func (gs *GameServer) setChoice(playerId string, choice move) {
288+
if gs.game.State != StatePlaying {
289+
return
290+
}
291+
215292
fmt.Println("setChoice", playerId, choice)
216293
gs.mutex.Lock()
217294
if playerId == "playerOne" {
218295
gs.game.PlayerOneChoice = choice
219296
} else if playerId == "playerTwo" {
220297
gs.game.PlayerTwoChoice = choice
221298
}
299+
if gs.game.Type == TypeAI {
300+
gs.game.PlayerTwoChoice = makeAIChoice()
301+
}
222302
gs.mutex.Unlock()
223303
gs.checkRoundOver()
224304
}
@@ -229,37 +309,101 @@ func (gs *GameServer) checkRoundOver() {
229309
}
230310
}
231311

312+
func getWinner(moveOne, moveTwo move) int {
313+
if moveOne == moveTwo {
314+
return 0
315+
}
316+
switch moveOne {
317+
case "rock":
318+
if moveTwo == "scissors" {
319+
return 1
320+
}
321+
case "paper":
322+
if moveTwo == "rock" {
323+
return 1
324+
}
325+
case "scissors":
326+
if moveTwo == "paper" {
327+
return 1
328+
}
329+
}
330+
return 2
331+
}
332+
232333
func (gs *GameServer) endRound() {
233334
gs.mutex.Lock()
234335
gs.game.State = StateRoundOver
336+
winner := getWinner(gs.game.PlayerOneChoice, gs.game.PlayerTwoChoice)
337+
fmt.Println("endRound winner:", winner, gs.game.PlayerOneChoice, gs.game.PlayerTwoChoice)
338+
if winner == 0 {
339+
gs.game.Score.Draw++
340+
} else if winner == 1 {
341+
gs.game.Score.PlayerOne++
342+
} else if winner == 2 {
343+
gs.game.Score.PlayerTwo++
344+
}
235345
gs.mutex.Unlock()
346+
347+
// End Game
348+
nRound := (gs.game.BestOf / 2) + 1
349+
if gs.game.Score.PlayerOne == nRound || gs.game.Score.PlayerTwo == nRound {
350+
gs.endGame(winner)
351+
return
352+
}
353+
354+
// TODO: render round result
355+
gs.game.Timer = time.Second * 5
356+
gs.broadcast <- []byte(renderEndRound(gs.game, winner))
236357
}
237358

238-
func (gs *GameServer) endGame() {
359+
func (gs *GameServer) endGame(winner int) {
360+
fmt.Println("endGame")
239361
gs.mutex.Lock()
240362
gs.game.State = StateGameOver
241363
gs.mutex.Unlock()
364+
365+
// TODO: render game result
366+
gs.broadcast <- []byte(renderEndGame(gs.game, winner))
242367
}
243368

244-
func (gs *GameServer) Connect(c echo.Context) error {
245-
conn, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
246-
if err != nil {
247-
fmt.Println("ws err", err)
248-
return err
249-
}
369+
func renderScoreboard(g *Game) string {
370+
buf := new(bytes.Buffer)
371+
var playerOneScore string = strconv.Itoa(g.Score.PlayerOne)
372+
var playerTwoScore string = strconv.Itoa(g.Score.PlayerTwo)
373+
var timer string = g.Timer.String()
374+
templates.Scoreboard(playerOneScore, playerTwoScore, timer).Render(context.Background(), buf)
375+
return buf.String()
376+
}
250377

251-
client := &Client{
252-
id: uuid.NewString(),
253-
gs: gs,
254-
conn: conn,
255-
send: make(chan []byte),
256-
}
257-
gs.register <- client
258-
fmt.Println("client pre", client.id)
378+
func renderSelection() string {
379+
buf := new(bytes.Buffer)
380+
templates.SelectionScreen().Render(context.Background(), buf)
381+
html := `<div hx-swap-oob="innerHTML:#gameScreen">` + buf.String() + `</div>`
382+
return html
383+
}
259384

260-
go client.readPump()
261-
go client.writePump()
262-
fmt.Println("client after", client.id)
385+
func renderEndRound(g *Game, winner int) string {
386+
buf := new(bytes.Buffer)
387+
winnerName := ""
388+
if winner == 0 {
389+
winnerName = "Draw"
390+
} else if winner == 1 {
391+
winnerName = "Player 1"
392+
} else if winner == 2 {
393+
winnerName = "Player 2"
394+
}
395+
templates.ResultScreen(string(g.PlayerOneChoice), string(g.PlayerTwoChoice), winnerName).Render(context.Background(), buf)
396+
return buf.String()
397+
}
263398

264-
return nil
399+
func renderEndGame(g *Game, winner int) string {
400+
buf := new(bytes.Buffer)
401+
winnerName := ""
402+
if winner == 1 {
403+
winnerName = "Player 1"
404+
} else if winner == 2 {
405+
winnerName = "Player 2"
406+
}
407+
templates.EndScreen(winnerName).Render(context.Background(), buf)
408+
return buf.String()
265409
}

0 commit comments

Comments
 (0)