Pular para o conteúdo principal

victorstein.dev

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:

Grid do jogo

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