Pular para o conteúdo principal

victorstein.dev

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()
}