Configuration

In codemaxxerConnections/src/main/java/com/nighthawk/spring_portfolio/mvc/config/WebSocketConfig.java


@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(myHandler(), "/myhandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

Parts of the configuration

  • @configuration –> sets the Java file as a configuration
  • @enablewebsocket –> enables websocket
  • webSocketConfigurer –> interface that allows you to register websocket handlers
  • registerwebsockethandlers –> method that allows you to register websocket handlers
  • myhandler –> bean that is a websocket handler
  • webSocketHandlerRegistry –> object that allows you to register websocket handlers
  • myhandler –> bean that is a websocket handler

Overview

Basically, this file sets the configuation for the websocket. There can be multiple websockets, but in this case, we only set one, and we only have one bean because we do not have any security concerns or logical divisions. The bean takes a handler from its template and sets the endpoint to /myhandler.

Dependencies


package com.nighthawk.spring_portfolio.mvc.config;

import com.nighthawk.spring_portfolio.mvc.handler.MyHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

Handler

Code

This is the updated version for multiplayer:

In codemaxxerConnections/src/main/java/com/nighthawk/spring_portfolio/mvc/handler/MyHandler.java


@Slf4j
public class MyHandler extends TextWebSocketHandler {

    private final LobbyManager lobbyManager = new LobbyManager();
    @Getter
    private final Map<String, Player> players = new HashMap<>();
    private final Map<String, BiConsumer<WebSocketSession, String>> commandMap = new HashMap<>();

    public MyHandler() {
        commandMap.put("hello", (t, u) -> {
            try {
                handleHello(t, u);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        commandMap.put("register", (t, u) -> {
            try {
                handleRegister(t, u);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        commandMap.put("attack", (t, u) -> {
            try {
                handleAttack(t, u);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        commandMap.put("createLobby", (t, u) -> {
            try {
                handleCreateLobby(t, u);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        commandMap.put("joinLobby", (t, u) -> {
            try {
                handleJoinLobby(t, u);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
    }

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
        log.warn("Received message: {}", message.getPayload());
        String payload = message.getPayload();
        int indexOfColon = payload.indexOf(':');
        String command = payload.substring(0, indexOfColon != -1 ? indexOfColon : payload.length());
        String params = indexOfColon != -1 ? payload.substring(indexOfColon + 1) : "";

        BiConsumer<WebSocketSession, String> handler = commandMap.get(command);
        if (handler != null) {
            handler.accept(session, params.trim());
        } else {
            session.sendMessage(new TextMessage("Unknown command: " + command));
        }
    }

    private void handleHello(WebSocketSession session, String params) throws IOException {
        session.sendMessage(new TextMessage("Hello, " + params));
    }

    private void handleRegister(WebSocketSession session, String params) throws IOException {
        String[] parts = params.split(";");
        if (parts.length < 3) {
            session.sendMessage(new TextMessage("Invalid input format. Expecting 'playerName;attack;health'."));
            return;
        }

        String playerName = parts[0];
        int attack = Integer.parseInt(parts[1]);
        int health = Integer.parseInt(parts[2]);

        players.put(playerName, new Player(playerName, attack, health, session));
        session.sendMessage(new TextMessage("Player " + playerName + " registered with Attack: " + attack + ", Health: " + health));
    }

    private void handleAttack(WebSocketSession session, String params) throws IOException {
        String[] parts = params.split(";");
        if (parts.length < 2) {
            session.sendMessage(new TextMessage("Invalid input format. Expecting 'attackerName;targetName'."));
            return;
        }

        String attackerName = parts[0];
        String targetName = parts[1];

        Player attacker = players.get(attackerName);
        Player target = players.get(targetName);

        if (attacker == null || target == null) {
            session.sendMessage(new TextMessage("Invalid player names."));
            return;
        }

        target.setHealth(target.getHealth() - attacker.getAttack());
        session.sendMessage(new TextMessage("Player " + targetName + " was attacked by " + attackerName + ". New Health: " + target.getHealth()));
        target.getSession().sendMessage(new TextMessage("You were attacked by " + attackerName + ". Your new Health: " + target.getHealth()));
    }

    private void handleCreateLobby(WebSocketSession session, String params) throws IOException {
        String lobbyId = params.trim();
        lobbyManager.createLobby(lobbyId);
        session.sendMessage(new TextMessage("Lobby " + lobbyId + " created."));
    }

    private void handleJoinLobby(WebSocketSession session, String params) throws IOException {
        String[] parts = params.split(";");
        if (parts.length < 2) {
            session.sendMessage(new TextMessage("Invalid input format. Expecting 'lobbyId;playerName'."));
            return;
        }

        String lobbyId = parts[0];
        String playerName = parts[1];
        Player player = players.get(playerName);

        if (player == null) {
            session.sendMessage(new TextMessage("Player " + playerName + " not found."));
            return;
        }

        LobbyManager.Lobby lobby = lobbyManager.getLobby(lobbyId);
        if (lobby == null) {
            session.sendMessage(new TextMessage("Lobby " + lobbyId + " not found."));
            return;
        }

        if (lobby.isFull()) {
            session.sendMessage(new TextMessage("Lobby " + lobbyId + " is full."));
            return;
        }

        lobby.addPlayer(player);
        session.sendMessage(new TextMessage("Player " + playerName + " joined lobby " + lobbyId + "."));
        player.getSession().sendMessage(new TextMessage("You joined lobby " + lobbyId + "."));
    }
}

Hashmap


private final Map<String, BiConsumer<WebSocketSession, String>> commandMap = new HashMap<>();

Hashmap maps each command to its specific void function using the string input, the websocket session input, and the string. commandMap is used to store command handlers. Each command (represented by a String key) is associated with a BiConsumer (the value) that defines how to handle that command.

When a message is received, handleTextMessage is called. This method extracts the command and parameters from the message, looks up the command in the commandMap, and invokes the corresponding handler if it exists:

Tl;dr The commandMap is a key component that maps command strings to their respective handlers. This design allows for a flexible and maintainable way to handle different commands by simply adding new entries to the map.

HandleTextMessage


@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
    String payload = message.getPayload();
    int indexOfColon = payload.indexOf(':');
    String command = payload.substring(0, indexOfColon != -1 ? indexOfColon : payload.length());
    String params = indexOfColon != -1 ? payload.substring(indexOfColon + 1) : "";

    BiConsumer<WebSocketSession, String> handler = commandMap.get(command);
    if (handler != null) {
        handler.accept(session, params.trim());
    } else {
        session.sendMessage(new TextMessage("Unknown command: " + command));
    }
}

The handleTextMessage method processes an incoming WebSocket message by:

  1. Logging the received message.
  2. Extracting the command and its parameters from the message payload.
  3. Looking up the appropriate command handler from the commandMap.
  4. Executing the handler if it exists, or sending an error message if the command is unknown.

This method ensures that different commands are handled appropriately, and it provides a way to easily add new commands by updating the commandMap in the constructor.

Other Voids

All the other voids are different functions that are the commands that commandMap points to. They point to other files to show the complexity, and other files may have different database handlers.