교수님이 쓰라고 하셔서 쓰는 그런데 은근 재밌는 듯

JAVA

[마피아] Join Sequence

shinyunha 2025. 11. 21. 11:25

Tag: "Join:"

Sequence Diagram

 

1. 사용자는 Test 파일을 실행시켜서 Lobby를 생성한다. 

2. 생성된 Lobby는 자신의 ClientManager를 생성한다. 

3. 모든 사용자가 자신의 Lobby를 생성하고 서버 연결이 완료되면 화면에 각자 닉네임을 입력한다. 

    - 지금은 누가 뒤늦게 서버에 연결하면 화면에 이전에 입장한 플레이어의 닉네임이 보이지 않는다.

    - 닉네임 하나를 입력할 때마다 그 String 하나씩 BroadCasting 하기 때문이다.

    - 나중에 이 부분을 지금까지 입력된 플레이어 닉네임 리스트를 BroadCasting 하는 방식으로 업데이트할 수 있다.

4. Lobby가 입력받은 닉네임을 ClientManager에게 보낸다.

//닉네임 입력받기
nicknameInputField.addActionListener(new ActionListener() { 			
    @Override
    public void actionPerformed(ActionEvent e) {				
        JTextField nicknameField = (JTextField) e.getSource(); 	
        String nickname = nicknameField.getText();
        nicknameField.setText("");
        //Join 시퀀스 시작 
        clientManager.sendMessage("Join:" + nickname);						
    }
});

Lobby

5. ClientManager는 받은 닉네임을 ClientThread를 거쳐서 OutputThread를 통해 Server에게 보낸다.

// 서버로 메세지 전송
public void sendMessage(String message) {
    if (clientThread != null) {
        clientThread.sendMessage(message);
    } else {
        System.err.println("서버에 연결되지 않았습니다.");
    }
    
    //Join: 이면 내 이름으로 저장
    if (message.startsWith("Join:")) {
        String nickname = message.substring(5); // "Join:" 제거하고 닉네임만 추출
        
        this.myName = nickname;            	
        사회자.setLobby(lobby);
    }
}

ClientManager

public void sendMessage(String message) {
    if (outputThread != null) {
        outputThread.sendMessage(message);
    }
}

ClientThread

public class OutputThread extends Thread {

    private final PrintWriter out;
    private final BlockingQueue<String> queue; // 메시지 전송 대기열

    public OutputThread(PrintWriter out) {
        this.out = out;
        this.queue = new LinkedBlockingQueue<>();
    }

    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 큐에서 메시지를 꺼내 서버로 전송 (큐가 비면 대기)
                String message = queue.take();
                out.println(message);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    // 외부에서 호출하여 메시지를 큐에 넣음
    public void sendMessage(String message) {
        try {
            queue.put(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

OutputThread

6. Server는 ServerThread로 메시지를 받아와서 CommandManager로 처리한 후 JoinCommand를 실행한다.

String request="";
while ((request = bf.readLine()) != null) {
   networkBrain.processMessage(this, request);
}

ServerThread

// 클라이언트가 보내는 메세지 처리 메소드
public synchronized void processMessage(ServerThread sender, String rawMessage) {
    if (rawMessage == null || rawMessage.equals("")) return;
    System.out.println("[CommandManager] 메시지 받음: " + rawMessage);

    String[] tokens = rawMessage.split(":", 2);
    String commandKey = tokens[0]; // "Start", "Join", "Message", "Mafia_message" 중 하나
    String payload; // 유저가 보낸 메세지(실제 내용)
        
    if (tokens.length > 1) {
        payload = tokens[1];
    } else {
        payload = "";
    }

    ICommand command = commandMap.get(commandKey);

    if (command != null) {
        command.execute(sender, payload, logicBrain.getState());
    } else {
        // Start 명령어일 시 모든 클라이언트에게 게임이 시작되었다고 알려주기.
        logicBrain.init_game();
        broadcastAll("Start:");
    }
}

CommandManager

7. JoinCommand는 받은 닉네임으로 사회자한테 Player 생성하라고 하고 CommandManager, ServerThread를 통해서 모든 Client들에게 BroadCasting한다.

public void execute(ServerThread sender, String payload, IState currentState) {
    logicBrain.createNewPlayer(payload);	
    networkBrain.broadcastAll("Join:"+payload); // 여기서 payload 플레이어의 닉네임
}

JoinCommand

public void broadcastAll(String message) {
    // 동기화 리스트긴한데 혹시 반복 중에 캐릭터가 제거되거나 하면 에러나니까 동기화 블록으로 묶었습니다.
    synchronized (allClients) {
        for (ServerThread client : allClients) {
            client.sendMessage(message);
        }
    }
}

CommandManager

// networkBrain가 메세지 보내라 하면 각자의 소켓으로 메세지를 보냄
public void sendMessage(String message) {
    if (pw != null) {
        pw.println(message);
    } else {
        log("PrintWriter 초기화 안 됨");
    }
}

ServerThread


8 -1. InputThread는 Server가 BroadCasting한 메세지를 받아서 ClientManager에게 준다.

public void run() {
    String message;
    try {
        // 서버로부터 메시지가 올 때까지 대기하다가 읽음
        while ((message = in.readLine()) != null) {
            // 메시지를 읽자마자 Manager에게 전달
            clientManager.handleMessage(message);
        }
    } catch (IOException e) {
        System.out.println("수신 스레드 종료 (연결 끊김)");
    }
}

InputThread

9. ClientManager는 Lobby가 화면에 입장한 플레이어 이름을 띄우도록 한다.

// Case 1: 플레이어 입장 (서버가 "Join:닉네임"을 보냄)
if (message.startsWith("Join:")) {
    String nickname = message.substring(5); // "Join:" 제거하고 닉네임만 추출
    
    // 로비 화면의 리스트에 닉네임 추가
    lobby.newPlayerEntered(nickname);
    System.out.println("view 함수 호출");
}

ClientManeger

//Join 시퀀스의 끝
public void newPlayerEntered(String nickname) {
    enteredPlayer.addElement(nickname);
		
    if(enteredPlayer.getSize() >= 4) {
        startButton.setEnabled(true);
    }
}

Lobby


8-2. 사회자는 서버가 준 닉네임을 RoleRactory로 보내서 플레이어를 생성한다.

public Player createNewPlayer(String nickname) {
    Player newPlayer = roleFactory.createPlayer(nickname, players.size()+1);
    addPlayer(newPlayer);
    return newPlayer;
}

사회자

public Player createPlayer(String nickname, int id) {
    Player player = new Player(nickname, id);
    return player;
}

RoleFactory

'JAVA' 카테고리의 다른 글

[JAVA] Spring Boot - 2  (0) 2025.11.25
[마피아] Start Sequence  (0) 2025.11.21
[JAVA] Spring Boot - 1  (0) 2025.11.18
[JAVA] DB  (0) 2025.11.16
[JAVA] Network & Socket  (0) 2025.11.04