11package game
22
33import (
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 (
3944const RoundTime = 30 * time .Second
4045
4146type 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
4652type 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
8284func (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+
168202func (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
203271func (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
214287func (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+
232333func (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