55/100 Dias de Golang - Jogo em UDP - Parte 1
Table of Contents
#
Jogo em UDP
Vamos criar um jogo no terminal, usando o protocolo UDP. para comunicação entre o client e o server. O jogo será baseado em um grid, onde o jogador poderá se movimentar livremente, o objetivo do jogo é chegar com o “personagem” em uma determinada coordenada do grid. O jogo suportará múltiplos jogadores e será executado diretamente no terminal. A cara dele vai ser mais ou menos essa:
Neste post, vamos implementar o cliente do jogo. Ele será responsável pela conexão com o servidor, validar os inputs do usuário e “renderizar” o tabuleiro.
Vamos começar definindo o endereço do servidor:
const serverAddr = "0.0.0.0:8000"
Em seguida, fazemos a conexão padrão via UDP:
if err != nil {
fmt.Println("Erro ao resolver endereço:", err)
return
}
conn, err := net.DialUDP("udp", nil, serverUDPAddr)
if err != nil {
fmt.Println("Erro ao conectar:", err)
return
}
defer conn.Close()
Depois disso teremos duas funções principais, a que fica escutando o servidor UDP e renderizando os elementos na tela - essa função será uma goroutine. E a outra função será um for infinito que fica lendo os inputs do usuário e enviando para o servidor.
Veja a construção da função que escuta o servidor UDP.
go func() {
buf := make([]byte, 2048)
for {
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
n, _, err := conn.ReadFromUDP(buf)
if err == nil {
fmt.Print("\033[H\033[2J")
fmt.Println(string(buf[:n]))
}
}
}()
Criamos um buffer de 2048 bytes para armazenar os dados recebidos buf := make([]byte, 2048)
. Depois entramos em um for infinito. Antes de ler os dados definimos um timeout para a conexão de 2 segundos conn.SetReadDeadline(time.Now().Add(2 * time.Second)).
Se recebermos algo da conexão, usamos o ["\033[H\0332J" para limpar o terminal (mesma coisa que o comando “clear” faz no terminal) - Esse comando só vai funcionar em terminal linux e depois ficamos printando o conteúdo do buffer fmt.Println(string(buf[:n]))
sempre printamos a conversão do buffer em string para entendermos o que está vindo, caso contrário, veríamos apenas os bytes e não entenderíamos a mensagem. E sempre printamos o :n
pois o buffer é o tamanho máximo da mensagem, mas não necessáriamente a mensagem tem toda essa informação.
Depois disso temos nosso loop principal, que fica lendo o input do usuário e enviando para o servidor. Antes de enviar, validamos os comandos inseridos pelo usuário.
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print(">> ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "w" || input == "s" || input == "a" || input == "d" {
_, err := conn.Write([]byte(input))
if err != nil {
fmt.Println("Erro ao enviar comando:", err)
}
} else {
fmt.Println("Comando inválido. Use: w, a, s, d")
}
}
O programa completo fica assim:
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
"time"
)
const serverAddr = "0.0.0.0:8080"
func main() {
serverUDPAddr, err := net.ResolveUDPAddr("udp", serverAddr)
if err != nil {
fmt.Println("Erro ao resolver endereço:", err)
return
}
conn, err := net.DialUDP("udp", nil, serverUDPAddr)
if err != nil {
fmt.Println("Erro ao conectar:", err)
return
}
defer conn.Close()
fmt.Println("Conectado ao servidor ASCIIArena.")
fmt.Println("Movimentação: WASD padrão.")
go func() {
buf := make([]byte, 2048)
for {
println(1)
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
n, _, err := conn.ReadFromUDP(buf)
if err == nil {
fmt.Print("\033[H\033[2J")
fmt.Println(string(buf[:n]))
}
}
}()
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print(">> ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "w" || input == "s" || input == "a" || input == "d" {
_, err := conn.Write([]byte(input))
if err != nil {
fmt.Println("Erro ao enviar comando:", err)
}
} else {
fmt.Println("Comando inválido. Use: up, down, left, right")
}
}
}