59/100 Dias de Golang - Jogo em UDP - Parte 5
Table of Contents
#
Jogo em UDP - Parte 5
Hoje vamos corrigir um problema no jogo e mostrar o código completo. Como várias goroutines acessam a lista de players ou um player específico temos que usar um Mutex para não termos race condition. Vamos começar criando o mutex.
var players = make(map[string]*Player)
var playersMutex sync.Mutex
E sempre que acessarmos essa variável, temos que dar um Lock e Unlock nela. Vou colocar somente um exemplo aqui:
func sendArena(conn *net.UDPConn, arena string) {
playersMutex.Lock()
for _, p := range players {
conn.WriteToUDP([]byte(arena), p.Addr)
}
playersMutex.Unlock()
}
Veja o código completo do server.go
package main
import (
"fmt"
"net"
"strings"
"sync"
"time"
)
const arenaRows = 20
const arenaCols = 60
var grid = make([][]rune, arenaRows)
type Player struct {
ID string
Char rune
X, Y int
Addr *net.UDPAddr
}
var players = make(map[string]*Player)
var playersMutex sync.Mutex
var commands = make(chan Command, 100)
type Command struct {
PlayerID string
Action string
}
func main() {
addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:8080")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()
fmt.Println("Servidor iniciado na porta 8080...")
go func() {
buf := make([]byte, 1024)
for {
n, clientAddr, _ := conn.ReadFromUDP(buf)
msg := strings.TrimSpace(string(buf[:n]))
playerID := clientAddr.String()
playersMutex.Lock()
if _, exists := players[playerID]; !exists {
players[playerID] = &Player{
ID: playerID,
Char: rune('A' + len(players)%26),
X: 1,
Y: 1,
Addr: clientAddr,
}
fmt.Printf("Novo jogador %s (%c)\n", playerID, players[playerID].Char)
}
playersMutex.Unlock()
commands <- Command{PlayerID: playerID, Action: msg}
}
}()
go func() {
for cmd := range commands {
playersMutex.Lock()
player := players[cmd.PlayerID]
switch cmd.Action {
case "w": // Para cima
if player.Y > 0 && grid[player.Y-1][player.X] != '*' {
player.Y--
}
case "s": // Para baixo
if player.Y < arenaRows-1 && grid[player.Y+1][player.X] != '*' {
player.Y++
}
case "a": // Para a esquerda
if player.X > 0 && grid[player.Y][player.X-1] != '*' {
player.X--
}
case "d": // Para a direita
if player.X < arenaCols-1 && grid[player.Y][player.X+1] != '*' {
player.X++
}
}
playersMutex.Unlock()
}
}()
ticker := time.NewTicker(200 * time.Millisecond)
for range ticker.C {
arena := renderArena()
sendArena(conn, arena)
}
}
func renderArena() string {
for i := 0; i < arenaRows; i++ {
grid[i] = make([]rune, arenaCols)
for j := range grid[i] {
if i == 0 {
grid[i][j] = '*'
continue
}
if i == arenaRows-1 {
grid[i][j] = '*'
continue
}
if j == 0 {
grid[i][j] = '*'
continue
}
if j == arenaCols-1 {
grid[i][j] = '*'
continue
}
grid[i][j] = '.'
}
}
playersMutex.Lock()
for _, p := range players {
grid[p.Y][p.X] = p.Char
}
playersMutex.Unlock()
var sb strings.Builder
for _, row := range grid {
for _, cell := range row {
sb.WriteRune(cell)
}
sb.WriteRune('\n')
}
return sb.String()
}
func sendArena(conn *net.UDPConn, arena string) {
playersMutex.Lock()
for _, p := range players {
conn.WriteToUDP([]byte(arena), p.Addr)
}
playersMutex.Unlock()
}