Pular para o conteúdo principal

victorstein.dev

57/100 Dias de Golang - Jogo em UDP - Parte 3

Table of Contents

# Jogo em UDP - Parte 3

Hoje iremos enviar o grid para cada usuário conectado e receber os inputs de cada usuário. Já temos a função renderArene que retorna uma string formatada e já temos a lista de playes, o que vamos criar é uma função envia a arena para todos os usuário. Mais o menos nessa estrutura.

arena := renderArena()
sendArena(conn, arena)

A função sendArena vai fazer um for sobre a lista de usuários e enviar para cada usuário o grid no endereço salvo.

func sendArena(conn *net.UDPConn, arena string) {
	for _, p := range players {
		conn.WriteToUDP([]byte(arena), p.Addr)
	}
}

Temos que fazer essa renderização do grid e mandar para o usuário de X e X tempo. Vamos definir aqui uma taxa de atualização de a cada 200ms. E para isso vamos criar um ticker no nosso main com a função de render e sendArena.

ticker := time.NewTicker(200 * time.Millisecond)
for range ticker.C {
	arena := renderArena()
	sendArena(conn, arena)
}

Caso a taxa de atualização esteja muito alta, ou muito baixa, podemos somente alterar o 200 da função time.NewTicker. Então já temos uma estrutura básica, o usuário se conecta no servidor, manda os comandos WASD e o servidor retorna o grid. Mas ainda temos que receber esse dado e fazer algo com ele. Para isso vamos criar duas variáveis. Uma variável Comand que é uma struct do id do player e qual a action e um canal, pois teremos uma goroutine para tratar os dados recebidos. Poderia fazer na mesma função? Provavelmente, mas a ideia aqui é treinar conceitos do Golang então temos que colocar um canal para comunicação.

var commands = make(chan Command, 100)

type Command struct {
	PlayerID string
	Action   string
}

Veja como vai ficar a goroutine:

go func() {
	buf := make([]byte, 1024)
	for {
		n, clientAddr, _ := conn.ReadFromUDP(buf)
		msg := strings.TrimSpace(string(buf[:n]))
		playerID := clientAddr.String()

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

		commands <- Command{PlayerID: playerID, Action: msg}
	}
}()

Ela será um for infinito, que fica recebendo dados da conexão:

n, clientAddr, _ := conn.ReadFromUDP(buf)
msg := strings.TrimSpace(string(buf[:n]))
playerID := clientAddr.String()

Aqui temos que criar uma lógica para verificar se o player existe, caso não exista cria e salva o array de players.

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

O campo Char da struct é do tipo rune e cada player novo vai receber a próxima letra do alfabeto - Aqui temos um “erro”, caso 27 players se conectem, o 27 irá receber um ‘A’ como char, mas não vou corrigir isso. E o X e Y estão sendo iniciado com 1 e 1, pois nas posições 0, 0 temos “paredes”. E o Addr é o endereço do cliente.

E como criamos o canal, temos que criar o Command desse player:

commands <- Command{PlayerID: playerID, Action: msg}

Amanhã vamos implantar a função que lida com os Comandos e atualiza o grid.