【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();
	}

}

四、界面的部分截图



  • 14
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值