이롭게 현명하게

[JAVA] Study Project / Tic-Tac-Toc 네트워크 게임 만들기 본문

JAVA

[JAVA] Study Project / Tic-Tac-Toc 네트워크 게임 만들기

dev_y.h 2023. 1. 7. 16:59
728x90
반응형


 

목차

서버 소프트웨어

클라이언트 소프트웨어

서버와 클라이언트

 



네트워크 기반의 게임은 네트워크를 통해 경기가 이루어진다.

하지만 경기자들은 서로의 포트 번호를 알 수 없기 때문에 직접 연결 될 수 없기 때문에 서버가 필요하다.

서버는 잘 알려진 포트로 경기자들에게 제공하고 경기 신청을 받는다. 따라서 서버와 클라이언트 소프트웨어가 필요하다.

 


[서버 소프트웨어]

여러명의 유저가 서버에 접속하여 게임을 플레이하려면 2명의 유저가 접속 할 때 마다 새로운 게임 객체와 2명의 경기자 객체를 생성하여 이들 객체를 서로 연결한다.

  • Game 클래스 : 현재 보드 상태
  • Player 클래스 : 경기자를 모델링

서버의 메인 루프

while(true){
	Game game = new Game();
    Player player1 = new Player(game,ss.accept(),'X');
    Player player2 = new Player(game,ss.accept(),'O');
    player1.setOther(player2);
    player2.setOther(player1);
    player1.start();
    player2.start();
    System.out.println("페어가 만들어 졌습니다.");
}

player 클래스는 서버 안에서 경기자를 나타낸다. 서버 안에 경기자는 여러 명일 수  있으므로 각각의경기자는 하나의 스레드로 구현하여 Thread 클래스를 상속받아 작성한다.

 


[클라이언트 소프트웨어]

사용자가 버튼을 클릭하면 버튼의 텍스트를변경하고 게임 서버로 새로운 수가 두어졌다는것을 알린다.

각각의 클라이언트는 Thread로 작성된다. run()메소드 안에서 서버가 보내는 명령어를 처리한다. 가장 중요한 명령어는 OTHER으로서 상대방 경기자가 어디에 두었는지 서버가 알려주는 명령어 이다. 이 명려어가 수식된면현재 보드에 상대방의 수를 표시한다.

만약 사용자가 보드에 수를 두면(클릭하면) 이것을 MOVE 명령어를 사용하여 게임 서버로 보낸다.

 


[서버와 클라이언트]

서버와 클라이언트 간에는 정보를 주고 받는 프로토콜이 있어야한다. 많은 방법이 있다.

새로운 수가 두어질 대 마다 전체 보드를서버로 버낼 수도 있다. 아니면 간단한 텍스트 기반의 프로토콜을 만드는 방법도 있다. 여기서는 다음과 같은 아주 간단한 프로토콜을 가정한다.

  • 클라이언트 -> 서버

MOVE x,y : (x,y) 위치에 새로운 수를두었음

QUIT : 게임을 종료한다.

  • 서버 <->클라이언트

START 'X' 또는 START 'O' : 게임ㅇ르 시작하고 클라이언트의 글자는 X또는 O

PRINT <메시지> : 디음과 같은 메시지를 화면에 출력한다.

 


TicTacTocServer.java

package StudyProject2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TicTacTocServer {

	public static void main(String[] args) throws Exception {
		ServerSocket ss = new ServerSocket(9101);// 포트번호 9101번으로 게임서버의 포트로 사용
		System.out.println("Tic Tac Toc 서버가 시작되었습니다.");
		try {
			while (true) {
				Game game = new Game();
				// 경기자를 나타내는 객체2개 생성
				Player player1 = new Player(game, ss.accept(), 'X');
				Player player2 = new Player(game, ss.accept(), 'O');

				// 상대방이 누구인지 알림
				player1.setOther(player2);
				player2.setOther(player1);

				// 경기자가 2명 모집되면 게임 시작
				player1.start();
				player2.start();
				System.out.println("페어가 만들어 졌습니다.");
			}
		} finally {
			ss.close();
		}
	}

}

//하나의 게임을 나타내는 클래스
class Game {
	char[][] boards = new char[3][3];// 2차원 배열을사용하여 보드를 나타낸다.

	public void setBoards(int i, int j, char playerMark) {
		this.boards[i][j] = playerMark;
	}

	public char getBoards(int i, int j) {
		return boards[i][j];
	}

	// 보드의 현재 상태를 콘솔에 출력
	public void printBoard() {
		for (int k = 0; k < 3; k++) {
			System.out.println(" " + boards[k][0] + "| " + boards[k][1] + "| " + boards[k][2]);
			if (k != 2) {
				System.out.println("---|---|---");
			}
		}
	}
}

// 하나의 경기자를 나타내는 클래스
class Player extends Thread {
	Game game; // 경기자가 속한 게임
	Socket socket;// 현재 경기에 연결된 소켓
	BufferedReader input;// 소켓에서 얻은 입력 스트림
	PrintWriter output;// 소켓에서 얻은 출력 스트림
	char playerMark;// 현재 경기자가 'X'인지 'O'인지를 나타낸다.
	Player other;// 상대방 경기자 객체

	public Player(Game game, Socket socket, char playerMark) {
		this.game = game;
		this.socket = socket;
		this.playerMark = playerMark;
		try {
			input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			output = new PrintWriter(socket.getOutputStream(), true);
			output.println("START " + playerMark);
			output.println("PRINT 다른 경기자를 기다립니다.");
		} catch (IOException e) {
			System.out.println("연결이 끊어졌습니다." + e);
		}

	}

	public void setOther(Player other) {
		this.other = other;
	}

	@Override
	public void run() {
		try {
			output.println("PRINT 모든 경기자가 연결되었습니다.");

			if (playerMark == 'X') {
				output.println("PRINT 당신 차례 입니다.");
			}

			// 클라이언트로부터 명령어를받아서 처리
			while (true) {
				String command = input.readLine();
				if (command == null) {
					continue;
				}
				if (command.startsWith("MOVE")) {// MOVE 명령어
					int i = Integer.parseInt(command.substring(5, 6));
					int j = Integer.parseInt(command.substring(7, 8));
					game.setBoards(i, j, playerMark);
					game.printBoard();
					other.output.println("OTHER " + i + " " + j);
					output.println("PRINT 기다리세요!");
					other.output.println("PRINT 당신 차례 입니다.");

				} else if (command.startsWith("QUIT")) {// QUIT 명령어
					return;
				}
			}

		} catch (IOException e) {
			System.out.println("연결이 끊어졌습니다. " + e);

		} finally {
			try {
				socket.close();
			} catch (IOException e) {

			}
		}
	}

}

 

TicTacTocClient.java

package StudyProject2;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class TicTacTocClient extends Thread {
	private JButton[][] buttons = new JButton[3][3];//버튼들의 배열로 보드를 표현
	
	private char me,other; // 나의 문자와 상대방 문자를 여기에 저장
	private JFrame frame;//프레임 객체
	private JPanel panel;//패널 객체, 여기에 보드가 보여진다.
	private JLabel message;//서버로부터 오는 메시지르 보여주는 레이블
	private Socket socket;//서버오 ㅏ연결된 소켓
	private BufferedReader input;//입력 스트림
	private PrintWriter output;//출력 스트림
	
	public TicTacTocClient() throws UnknownHostException,IOException{
		//현재는 로컬 컴퓨터에서만 실행
		socket = new Socket("localhost",9101);
		//소켓으로부터 입력 스트림과 출력 스트림을 구한다.
		input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		output = new PrintWriter(socket.getOutputStream(),true);
		
		//애플리케이션의 GUI를 생성한다.
		
		frame = new JFrame();
		panel = new JPanel();
		panel.setLayout(new GridLayout(3,3,5,5));
		Font font = new Font("Dialog",Font.ITALIC,50);
		message = new JLabel("여기에 메시지가 표시됩니다.");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(panel,BorderLayout.CENTER);
		frame.add(message,BorderLayout.SOUTH);
		frame.setSize(300,300);
		frame.setVisible(true);
		
		//패널에 3X 격자 형식으로 버튼을 추가
		for(int i =0;i<3;i++) {
			for(int j=0;j<3;j++) {
				final int ii = i;
				final int jj = j;
				buttons[i][j] = new JButton(" ");
				buttons[i][j].setFont(font);
				//각 버튼에 이벤트 처리기를 붙인다. 람다식을 사용
				buttons[i][j].addActionListener(e->{
					buttons[ii][jj].setText(""+me);
					output.println("MOVE "+ii+" "+jj);
				});
				panel.add(buttons[i][j]);
			}
		}
		panel.repaint();
		
	}
	
	
	//스레드 클래스에서 작업을 기술하는 메소드이다.
	public void run(){
		String response;
		try {
			response = input.readLine();//서버로부터 첫 번째 명령어를읽는다.
			
			if(response.startsWith("START")) {
				//START 명령어이면 경기를 시작한다.
				me = response.charAt(6);
				other = (me=='X')?'O':'X';
				message.setText("경기가 시작됩니다.");
				frame.setTitle("현재 경기자는 " + me);
			}
			//서버로부터 반복적으로 명령어를 읽어서 처리한다.
			while(true) {
				response = input.readLine();
				if(response.startsWith("OTHER")) {
					//상대방의 수를 보드에 표시
					int i = Integer.parseInt(response.substring(6, 7));
					int j = Integer.parseInt(response.substring(8, 9));
					buttons[i][j].setText("" + other);
					message.setText("상대방이 두었습니다.");
				
				}else if(response.startsWith("PRINT")) {
					//메시지를 화면에 출력한다.
					message.setText(response.substring(6));
				}
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				socket.close();
			} catch (IOException e2) {
				e2.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) throws UnknownHostException, IOException {
		TicTacTocClient client = new TicTacTocClient();
		client.start();
	}

}

 


잘못된 정보는 댓글에 남겨주시면 감사하겠습니다!😊

댓글과 좋아요는 큰 힘이 됩니다!

 

더보기

[ 참고자료 ]

 

 

 

 

728x90
반응형
Comments