JAVA 프로그래밍

문제

단일 클라이언트용 가위바위보 프로그램에서 네트워크 클라이언트 부분의 문제입니다 이를 해결하는 다음 프로그램을 해석하세요 
     
 

알고리즘

가위바위보 클라이언트 초기화 
   네트워크 클라이언트 연결 초기화
      클라이언트에서 서버로 연결 요청
      네트워크 입출력 연결 후 데이터가 도착할 때까지 대기

가위바위보 선택시 게임 업데이트
   먼저, 선택 결과를 바탕으로 게임 업데이트
   상대편에게 네트워크로 데이터 전송

가위바위보 도착시 게임 업데이트
   상대편이 네트워크로 보낸 데이터를 읽기
   읽은 데이터를 바탕으로 게임 업데이트

가위바위보 네트워크 종료
   네트워크 종료

  멀티스레드 기반 클라이언트-서버의 네트워크 통신 구조

 

  가위바위보 프로그램의 클래스 다이어그램

 

  가위바위보 클라이언트 프로그램의 네트워크 연결 초기화 주요 흐름

 

  가위바위보 클라이언트 프로그램의 데이터 전송 주요 흐름

 

 

  가위바위보 클라이언트 프로그램의 네트워크 종료 주요 흐름

 
 

프로그램 코드

E001	// [클라이언트]에서 [서버]에게 접속 요청하면 네트워크 연결 초기화
E002	// [서버]에서 보낸 데이터가 도착할 때까지 계속 대기
E003	// (수신전용스레드와 별개로) 메인 스레드 N41 이후 실행중
E004	// [클라이언트]에서 버튼을 누를 때까지 계속 대기(Event Dispatch Thread)
E005	// [서버]가 보낸 데이터([RPSN] [서버] 1(바위)) 도착
E006	// [클라이언트]에서 가위(0)를 선택
E007	// [클라이언트]에서 [서버]에게 데이터([RPSN] [클라이언트] 0(가위))를 전송
E008	// [클라이언트]에서 종료버튼(우측상단 X) 선택
E009	// 메인스레드와 수신전용스레드가 모두 종료하여 프로그램 종료
	 
	// 파일명 : ./Chapter17/RockPaperScissorsClient.java
	import javax.swing.*;
	import java.awt.event.*;
	import rpsGame.RockPaperScissorsNetwork;
	 
	// 가위바위보 프로그램의 네트워크 클라이언트 버전
	public class RockPaperScissorsClient
	{
1		public static void main( String[] args ) {
			final String imagePath = "C:\\Users\\user\\Downloads\\JAVA-main\\src\\rpsGame\\image\\";
			String serverIP = "localhost";
2			RockPaperScissorsNetwork panel =
3			                                 new RockPaperScissorsNetwork( imagePath, serverIP );
	 
			JFrame frame = new JFrame( "가위바위보(클라이언트)" );
			frame.getContentPane().add( panel );
			frame.addWindowListener( new WindowAdapter() {
Wb				public void windowClosing( WindowEvent event ) {
W1					panel.close();
We				}
			} );
			frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
			frame.pack();
			frame.setVisible(true);
4		}
	}
	 
	// 파일명 : ./src/rpsGame/RockPaperScissorsNetwork.java
	package rpsGame;
	import java.awt.event.ActionEvent;
	import javax.swing.JOptionPane;
	import network.Network;
	 
	// 가위바위보 네트워크 클래스
	public class RockPaperScissorsNetwork extends RockPaperScissorsPanel implements Runnable
	{
		private Network network;
		private int counterpart;
		private final String header = "[RPSN] ";
		private final String disconnectMessage = " -1";
	 
		// 가위바위보 네트워크 초기화
PN1b		public RockPaperScissorsNetwork( String imgPath ) {
PN11			super( imgPath );
			super.timer.stop();
			super.show( QUESTIONMARK, QUESTIONMARK );
			super.strPlayers.setText( "<html><body style='text-align:center;'>상대편이 준비중입니다 기다려주세요<br>[나]</body></html>" );
PN12			network =
PN13			          new Network();
PN1e		}
		 
		// 가위바위보 클라이언트 초기화 
PN2b		public RockPaperScissorsNetwork( String imgPath, String serverIP ) {
PN21			this( imgPath );
PN22			network.connectAsClient( serverIP, this );
			network.write( header + " " + super.PAPER );
PN2e		}
	 
		// 가위바위보 서버 초기화 
PN3b		public void connectAsServer() {
PN31			network.connectAsServer( this );
PN3e		}
	 
		// 가위바위보 선택시 게임 업데이트 
		@Override
PN4b		public void actionPerformed( ActionEvent event ) {
			// 먼저, 선택 결과를 바탕으로 게임 업데이트 
			int player = super.select( event );
PN41			super.show( player, counterpart );
			// 상대편에게 네트워크로 선택 결과를 전송
PN42			network.write( header + " " + player );
PN4e		}
	 
		// 가위바위보 도착시 게임 업데이트 
		@Override
PN5b		public void run() {
			// 상대편이 네트워크로 보낸 데이터를 읽기 
PN51			for ( String message = null; ( message =
PN52			                                         network.read() ) != null; ) {
				// 가위바위보 메시지가 아니면 무시
				if ( !message.contains( header ) )
					continue;
				// 상대편이 네트워크 종료했으면 안내
				else if ( message.contains( disconnectMessage ) ) {
					close();
					super.strPlayers.setText( "<html><body style='text-align:center;'>상대편이 종료하였습니다<br>[나]</body></html>" );
PN53					super.show( QUESTIONMARK, QUESTIONMARK );
PN54					break;
				}
				else {
					// 읽은 데이터를 바탕으로 게임 업데이트 
					super.strPlayers.setText( "<html><body style='text-align:center;'>[너]<br>[나]</body></html>" );
PN55					super.ready();
PN56					counterpart = Integer.parseInt( message.replace( header, "" ).trim() );
				}
			}
PN5e		}
		 
		// 가위바위보 네트워크 종료  
PN6b		public void close() {
			network.write( header + " " + disconnectMessage );
			// 네트워크 종료
PN61			network.close();
PN6e		}
	}
 
 
	// 파일명 : ./src/network/Network.java
	package network;
	import java.io.*;
	import java.net.*;
	 
	// 네트워크 연결 및 입출력 클래스
	public class Network
	{
		public static final int serverPort = 7700;
		protected ServerSocket serverSocket;
		protected Socket socket;
		private BufferedReader in;
		private PrintWriter out;
		private Thread waitForCounterpart;
	 
		// 네트워크 클래스 초기화
N1b		public Network() {
			serverSocket = null;
			socket = null;
			in = null;
			out = null;
			waitForCounterpart = null;
N1e		}
		 
		// 네트워크 서버 연결 초기화 
N2b		public void connectAsServer( Runnable obj ) {
			try {
				// 서버 소켓 연결 
				serverSocket = new ServerSocket( serverPort );
				// 클라이언트에서 서버로 연결 요청시 연결 활성화 
N21				socket =
N22				         serverSocket.accept();
				// 네트워크 입출력 연결 후 데이터가 도착할 때까지 대기 
N23				connectInOut( obj );
			} catch ( Exception e ) {
				e.printStackTrace();
			}
N2e		}
		 
		// 네트워크 클라이언트 연결 초기화 
N3b		public void connectAsClient( String serverIP, Runnable obj ) {
			try {
				// 클라이언트에서 서버로 연결 요청 
N31				socket =
N32				         new Socket( serverIP, serverPort );
				// 네트워크 입출력 연결 후 데이터가 도착할 때까지 대기 
N33				connectInOut( obj );
			} catch ( Exception e ) {
				e.printStackTrace();
			}
N3e		}
	 
		// 네트워크 입출력 초기화
N4b		public void connectInOut( Runnable obj ) {
			try {
				// 네트워크 입출력 연결
				in = new BufferedReader( new InputStreamReader( socket.getInputStream() ));
				out = new PrintWriter( socket.getOutputStream(), true );
				// 네트워크에 데이터가 도착할 때까지 대기 시작
				waitForCounterpart = new Thread( obj );
N41				waitForCounterpart.start();
			} catch ( Exception e ) {
				e.printStackTrace();
			}
N4e		}
		 
		// 상대편이 네트워크로 보낸 데이터 읽기
N5b		public String read() {
			try {
				if ( this.isConnecting() == true )
N51					return
N52					       in.readLine();
			} catch ( Exception e ) {
				e.printStackTrace();
			}
			 
			return null;
N5e		}
		 
		// 상대편에게 네트워크로 데이터 전송 
N6b		public void write( String data ) {
			try {
				if ( this.isConnecting() == true )
N61					out.println( data );
			} catch ( Exception e ) {
				e.printStackTrace();
			}
N6e		}
	 
		// 네트워크 연결 상태 확인
		public boolean isConnecting() {
			if ( ( socket != null )
					&& ( in != null )
					&& ( out != null )
					&& ( waitForCounterpart != null )
					&& ( waitForCounterpart.getState() != Thread.State.TERMINATED ) )
				return true;
			else
				return false;
		}
		 
		// 네트워크 종료 
N7b		public void close() {
			try {
				if ( waitForCounterpart != null ) {
					waitForCounterpart.interrupt();
					waitForCounterpart = null;
				}
				if ( in != null ) {
					in.close();
					in=null;
				}
				if ( out != null ) {
					out.flush();
					out.close();
					out = null;
				}
				if ( socket != null ) {
					socket.close();
					socket = null;
				}
				if ( serverSocket != null ) {
					serverSocket.close();
					serverSocket = null;
				}
			} catch ( Exception e ) {
				e.printStackTrace();
			}
N7e		}
	}
 
 
	// 파일명 : ./src/rpsGame/RockPaperScissorsPanel.java
	package rpsGame;
	import java.awt.*;
	import java.awt.event.*;
	import javax.swing.*;
	 
	// 가위바위보 클래스
	public class RockPaperScissorsPanel extends JPanel implements ActionListener
	{
		protected final int SCISSORS = 0, ROCK = 1, PAPER = 2, QUESTIONMARK = 3;
		protected final int BASE = 0,  WIN = 1;
		protected JLabel imgPlayerA, imgPlayerB, strPlayers;
		protected JButton[] button;
		protected ImageIcon[][] image;
		protected Timer timer;
	 
		// 가위바위보 초기화
P1b		public RockPaperScissorsPanel( final String imgPath ) {
			// 가위바위보 3가지, 미선택 1가지, 승패 2가지를 고려하여 이미지를 준비
			image = new ImageIcon[4][2];
			image[SCISSORS    ][BASE] = new ImageIcon( new ImageIcon( imgPath + "scissors.gif"     ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[SCISSORS    ][WIN]  = new ImageIcon( new ImageIcon( imgPath + "scissors_win.gif" ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[ROCK        ][BASE] = new ImageIcon( new ImageIcon( imgPath + "rock.gif"         ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[ROCK        ][WIN]  = new ImageIcon( new ImageIcon( imgPath + "rock_win.gif"     ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[PAPER       ][BASE] = new ImageIcon( new ImageIcon( imgPath + "paper.gif"        ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[PAPER       ][WIN]  = new ImageIcon( new ImageIcon( imgPath + "paper_win.gif"    ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[QUESTIONMARK][BASE] = new ImageIcon( new ImageIcon( imgPath + "questionmark.gif" ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
			image[QUESTIONMARK][WIN]  = new ImageIcon( new ImageIcon( imgPath + "questionmark.gif" ).getImage().getScaledInstance( 200, 200, Image.SCALE_SMOOTH ) );
				 
			// 화면 위쪽에 가위바위보 이미지 초기화
			this.imgPlayerA = new JLabel( image[QUESTIONMARK][BASE] );
			this.imgPlayerB = new JLabel( image[QUESTIONMARK][BASE] );
			this.strPlayers = new JLabel( "<html><body>[너]<br>[나]</body></html>", JLabel.CENTER );
			this.add( this.imgPlayerB );
			this.add( this.strPlayers );
			this.add( this.imgPlayerA );
	 
			// 화면 아래쪽에 가위바위보 입력 버튼 초기화
			button = new JButton[3];
			button[SCISSORS] = new JButton( new ImageIcon( image[SCISSORS][BASE].getImage().getScaledInstance( 30, 30, Image.SCALE_SMOOTH ) ) );
			button[ROCK    ] = new JButton( new ImageIcon( image[ROCK    ][BASE].getImage().getScaledInstance( 30, 30, Image.SCALE_SMOOTH ) ) );
			button[PAPER   ] = new JButton( new ImageIcon( image[PAPER   ][BASE].getImage().getScaledInstance( 30, 30, Image.SCALE_SMOOTH ) ) );
			button[SCISSORS].addActionListener( this );
			button[ROCK    ].addActionListener( this );
			button[PAPER   ].addActionListener( this );
			this.add( new JLabel("  가위 바위 보를 선택하세요  ") );
			this.add( button[SCISSORS] );
			this.add( button[ROCK] );
			this.add( button[PAPER] );
			this.setBackground( Color.cyan );
			this.setPreferredSize( new Dimension(220, 530) );
	 
			// 가위바위보 준비
			this.timer = new Timer( 1000, new ActionListener() {
				@Override
				public void actionPerformed( ActionEvent event ) {
					ready();
					timer.stop();
				}
			});
P1e		}
	 
		// 물음표 제시하고 버튼 활성화
P2b		public void ready() {
			imgPlayerA.setIcon( image[QUESTIONMARK][BASE] );
			imgPlayerB.setIcon( image[QUESTIONMARK][BASE] );
			button[0].setEnabled( true );
			button[1].setEnabled( true );
			button[2].setEnabled( true );
P2e		}
	 
		// 가위바위보 내기
		@Override
		public void actionPerformed( ActionEvent event ) {
			// 승패 결과 제시
			int playerA = select( event );
			int playerB = (int)( Math.random() * 3 );
			show( playerA, playerB );
			// 다음 판은 잠시 대기
			timer.start();
		}
			 
		// 승패 결과 제시하고 버튼 비활성화
P3b		public void show( int playerA, int playerB ) {
			int playerA_winOrLose = BASE, playerB_winOrLose = BASE;
			if ( ( ( playerA == SCISSORS ) && ( playerB == PAPER ) )
					|| ( ( playerA == ROCK ) && ( playerB == SCISSORS ) )
					|| ( ( playerA == PAPER ) && ( playerB == ROCK ) ) ) {
				playerA_winOrLose = WIN;
			}
			else if ( ( ( playerA == PAPER ) && ( playerB == SCISSORS ) )
					|| ( ( playerA == SCISSORS ) && ( playerB == ROCK ) )
					|| ( ( playerA == ROCK ) && ( playerB == PAPER ) ) ) {
				playerB_winOrLose = WIN;
			}
	 
			imgPlayerA.setIcon( image[ playerA ][ playerA_winOrLose ] );
			imgPlayerB.setIcon( image[ playerB ][ playerB_winOrLose ] );
			button[0].setEnabled( false );
			button[1].setEnabled( false );
			button[2].setEnabled( false );
P3e		}
		 
		public int select( ActionEvent event ) {
			if( event.getSource() == button[SCISSORS] )
				return SCISSORS;
			else if ( event.getSource() == button[ROCK] )
				return ROCK;
			else
				return PAPER;
		}
	}

실행 순서

 
 					※ 실행순서 및 메모리상태는 A키(이전) 및 D키(다음)를 눌러도 확인할 수 있습니다