1. 程式人生 > >【Java】網路打字對戰小遊戲

【Java】網路打字對戰小遊戲

//以下程式碼均出自於清華大學出版社(郭克華老師版java教材)的第二十五章練習章節

//以下為自己的對程式碼的理解和記錄

一、網路打字小遊戲

網路打字小遊戲需要實現的功能:

     執行伺服器端,之後執行客戶端,可實現多個使用者隨機進入和退出,進入之後出現登入介面,輸入使用者名稱,若連線成功,顯示連線成功介面。之後進入遊戲介面。

遊戲規則為26個大寫字母從上端每0.1秒向下墜落,從鍵盤鍵入此字母,若正確則自己加一分,其餘線上使用者減一分,若輸入錯誤或者錯過輸入時間,則自己減一分。

二、分析

 大致思路:建立三個.java。客戶端、伺服器端、遊戲面板類。

伺服器端:
  (1).首先每個客戶進入伺服器的時間是不可以確定的,所以需要一個總的執行緒來等待每個客戶的連入。
  (2).同時,每個客戶對伺服器的輸入輸出也是一個執行緒的問題,所以還需要為每一個客戶建立一個執行緒。為了方便操作,我們同時申請一個
      變長陣列來儲存每個連入伺服器的客戶端。

  (3).並且,客戶端之間的通訊也是通過這個變長的陣列來實現的。

於是就形成了,當一個客戶成功連入伺服器之後,伺服器在總的執行緒上為此使用者建立一個子執行緒,也就是2中的執行緒陣列。此子執行緒中,需要實現客戶端與伺服器的通訊,所以將匯流排程接收的套接字給子執行緒,並實現輸入輸出流的連線。也就是說,當一個客戶需要與另一個客戶實現通訊時,都需要通過主執行緒的套接字來實現,這樣更加的方便。

遊戲介面端:

   此介面需要考慮的方面是遊戲介面的形成(字母的下落以及鍵盤的鍵入)和生命值的修改(自己的修改以及自身行為對其他使用者生命值的影響)以及判斷結束的函式。

  (1)遊戲介面上關於字母隨機的從頂上下落,於是將字元設定成Label的形式,設定下落的起始位置的函式為:

setBounds(rnd.nextInt(this.getWidth()),0, 20, 20);//此函式是起始位置是橫座標隨機,縱座標從0,元件大小為20,20。

之後利用Timer類結合ActionListener的操作函式來實現含字母的移動標籤隨著時間的推移下落。同時利用Key監聽器來實現判斷是否操作正確。

  (2)在實現移動時,首先判斷使用者是否已經錯過此字母,若錯過,則自己減1。當鍵入時,如果匹配,則自己加2,給伺服器減一(伺服器將會使得所有子執行緒使用者減1,則此使用者正確是加1),否則自己減1。且為了接收伺服器給的其他使用者的-1,則需要建立執行緒,因為隨時可能有-1的時候。

    (3)判斷遊戲結束的函式是判斷生命值是否為0,為0則退出。當每次生命值減少時候都需要判斷是否為0。

客戶端:只需要顯示使用者登入介面即可。

其他需要注意的就是一些exception的處理以及退出時的一些符合使用者體驗的解說。

ERROR:當時在通訊時,只能用println,而不能用print。因為他們的套接字的讀取是一行一行讀取的。當時除錯了很久才發現這個地方。

三、程式碼

//伺服器端
package server;
import java.awt.Color;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;


public class Server extends JFrame implements Runnable {
     private Socket s=null;
     private ServerSocket ss=null;
     private ArrayList<ChatThread> clients=new ArrayList<ChatThread>();//儲存每個客戶端連入的變長陣列
	
     public Server()throws Exception{
    	 this.setTitle("伺服器端");
    	 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	 this.setBackground(Color.yellow);
    	 this.setSize(200, 100);
    	 this.setVisible(true);
    	 ss=new ServerSocket(9999);//伺服器開闢一個埠
    	 new Thread(this).start();//接受客戶連線的死迴圈開始執行 
     }
     
	//run函式的重寫
     //此執行緒是用來接收等待客戶端不斷連入時的執行緒
	public void run() {
		try {
			while(true)
			{
				s=ss.accept();//等待連入
				ChatThread ct=new ChatThread(s);//當有客戶端連入後,為此客戶端建立一個執行緒
				clients.add(ct);//並且將此執行緒加入到執行緒陣列中
				ct.start();//啟動此執行緒的執行緒,此後可以實現通訊
			}
		}catch(Exception ex) {
			ex.printStackTrace();
			javax.swing.JOptionPane.showMessageDialog(this, "遊戲異常退出!");
			System.exit(0);
		}
		
	}
	//類中類的建立,此執行緒來接收伺服器和一個客戶端的通訊的執行緒(針對於伺服器)
	class ChatThread extends Thread{
		private Socket s=null;
		private BufferedReader br=null;
		private PrintStream ps=null;
		private boolean canRun=true;
		
		public ChatThread(Socket s)throws Exception
		{//利用執行緒實現輸入輸出(通訊)
			this.s=s;
			br=new BufferedReader(
					new InputStreamReader(s.getInputStream()));
			ps=new PrintStream(s.getOutputStream());
		}
		
		public void run() {//把從客戶那裡得到的資訊,穿送給其他客戶
			try {
				while(canRun) {
					String str=br.readLine();//讀取該Socket傳來的資訊,
					System.out.println(str);
					sendMessage(str);
					
				}
			}catch (Exception ex) {
				canRun=false;
				clients.remove(this);//將此執行緒從客戶端的陣列中刪除
			}
		}
	}
	//將資訊傳送給其他的客戶端,實現客戶端之間的通訊
	public void sendMessage(String msg) {
		for(ChatThread ct: clients) {
			ct.ps.println(msg);
		}
	}
	
	public static void main(String[] args) throws Exception {
         Server server=new Server();

	}
}
/*遊戲面板。
  Timer類和Random類的使用。
*/
package client;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import java.util.Random;
import javax.swing.*;

public class GamePanel extends JPanel
              implements ActionListener,KeyListener,Runnable{
	private int life=10;//生命值的初始化
	private char keyChar;//按下的字母記錄
	private JLabel lbMoveChar=new JLabel();//掉下來的字母Label
	private JLabel lbLife=new JLabel();//顯示當前生命值的Label
	private Socket s=null;
	private Timer timer=new Timer(100,this);
	private Random rnd=new Random();
	private BufferedReader br=null;
	private PrintStream ps=null;
	private boolean canRun=true;

	public GamePanel() {
		
		/**將面板的格式置成空,由此之後將會對所有進行重新設定**/
		this.setLayout(null);
		this.setBackground(Color.DARK_GRAY);
		this.setSize(240,320);
		
		/**設定顯示生命值的標籤的樣式**/
		this.add(lbLife);
		lbLife.setFont(new Font("黑體",Font.BOLD,20));
		lbLife.setBackground(Color.YELLOW);
		lbLife.setForeground(Color.PINK);
		lbLife.setBounds(0,0,this.getWidth(),20);//設定了標籤的大小
		
		/**設定掉下來的標籤**/
		this.add(lbMoveChar);
		lbMoveChar.setFont(new Font("黑體",Font.BOLD,20));
		lbMoveChar.setForeground(Color.YELLOW);
		this.init();
		this.addKeyListener(this);
		
		try {
			s=new Socket("127.0.0.1",9999);
		    JOptionPane.showMessageDialog(this, "連線成功");
		    InputStream is=s.getInputStream();
		    br=new BufferedReader(new InputStreamReader(is));
		    OutputStream os=s.getOutputStream();
		    ps=new PrintStream(os);
		    new Thread(this).start();
		    
		}catch (Exception ex) {
			javax.swing.JOptionPane.showMessageDialog(this, "遊戲退出異常!");
			System.exit(0);
		}
		timer.start();
	}
	
	//實現掉落的字母起始位置的隨機
	public void init() {
		lbLife.setText("當生命值為:"+life);
		String str=String.valueOf((char)('A'+rnd.nextInt(26)));
		lbMoveChar.setText(str);
		lbMoveChar.setBounds(rnd.nextInt(this.getWidth()),0, 20, 20);//起始位置是橫座標隨機,縱座標從0,元件大小為20,20
	}
	
	public void run() {
		try {
			while(canRun) {
				String str=br.readLine();
				int score=Integer.parseInt(str);
				life+=score;
				checkFail();
			}
		}catch(Exception ex) {
			canRun=false;
			javax.swing.JOptionPane.showMessageDialog(this, "遊戲退出異常!");
			System.exit(0);
		}
	}
	
//Timer來控制移動字母的下落,每100ms則執行一次此操作
	public void actionPerformed(ActionEvent e) {
		if(lbMoveChar.getY()>=this.getHeight())	{
			life--;
			checkFail();
		}
		lbMoveChar.setLocation(lbMoveChar.getX(),lbMoveChar.getY()+10);//實現這個字母自己下墜
	}
	
	public void checkFail()//檢驗生命值是否小於0,如果小於0則退出遊戲。
	{
		init();
		if(life<=0) {
			timer.stop();
			javax.swing.JOptionPane.showMessageDialog(this, "生命值耗盡,遊戲失敗!");
			System.exit(0);
		}
	}
	//鍵盤操作事件對應行為
	public void keyPressed(KeyEvent e) {
		keyChar=e.getKeyChar();//記錄鍵盤輸入值
		String keyStr=String.valueOf(keyChar).toUpperCase();//將此值轉化成大寫的字元
		try {
			if(keyStr.equals(lbMoveChar.getText())) {
				life+=2;
				ps.println("-1");
			}else {
				life--;
			}
			checkFail();
		}catch(Exception ex) {
			ex.printStackTrace();
			javax.swing.JOptionPane.showMessageDialog(this, "遊戲異常退出!");
			System.exit(0);
		}	
	}

	public void keyReleased(KeyEvent arg0) {}

	public void keyTyped(KeyEvent arg0) {}
}
//客戶端
package client;
import javax.swing.*;

public class GameFrame extends JFrame {
	private GamePanel gp;
	
	public GameFrame()
	{
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		String nickName=JOptionPane.showInputDialog("輸入暱稱");
		this.setTitle(nickName);
		gp=new GamePanel();
		this.add(gp);
		gp.setFocusable(true);
		this.setSize(gp.getWidth(), gp.getHeight());
		this.setResizable(true);
		this.setVisible(true);
	}
	
	public static void main(String[] args) {
		new GameFrame();
	}

}

四、介面的部分截圖