《Java语言程序设计》–郭克华
网络打字游戏功能简介
这里将介绍一个网络对战的打字游戏。首先运行服务器,界面如图1所示。
图1
运行客户端,首先显示如图2所示的对话框。
图2
用户能够输入昵称,单击“确定”按钮,则连接到服务器。这里为了简单起见,服务器运行在本机,端口为9999。
连接成功,显示如图3.
图3
单击“确定”按钮,即可出现打字游戏界面。
实际上,多人可以加入打字对战。界面如图所示。
规则如下:
1.初始生命值为10分,字母随机落下。
2.用户按下键盘,如果输入的字符正确,则加1分,错误,减一分,然后重新落下新字母。
3.如果用户加1分,将其他所有用户分分数减一分。
4.字母掉到用户界面底部,用户减1分,重新出现新字母。
5.如果把生命值变为0分,则游戏退出,如图所示。
ps:此案例是很多网络对战游戏的基础,如网络打牌、网络赛车、网络五子棋等。
关键技术
1.如何组织界面
在这个项目中,服务器端界面比较简单。客户端也只有一个界面,但是最好将游戏的工作写在一个面板内,然后将面板加到一个JFrame中。
设计出来的类如下:
1.GamePanel:客户端游戏所在的面板。
2.GameFrame:客户端游戏面板所在的界面。
3.Server:服务器界面。
2.客户端如何掉下字母
可以通过画图技术在界面上画出字母。不过,在Java GUI中,还有一种更加简单的方法,那就是将面板设置为空布局之后,将字母放在一个JLabel中。
字母的掉下,实际上相当于调整JLabel的位置,代码如下:
.
.
.
public class GamePanel extends JPanel {
.
.
.
//掉下的字母Label
private JLabel lbMoveChar=new JLabel();
public GamePanel() {
this.setLayout(null);
.
.
.
this.add(lbMoveChar);
lbMoveChar.setFont(new Font("黑体",Font.BOLD,20));
lbMoveChar.setForeground(Color.yellow);
this.init();
.
.
.
}
public void init() { //字母的属性设置
.
.
.
//出现随机字母
String str=String.valueOf((char)('A'+rnd.nextInt(26)));
lbMoveChar.setText(str);
lbMoveChar.setBounds(rnd.nextInt(this.getWidth()),0,20,20);
}
.
.
.
//Timer事件对应的行为:实现掉下一个字母
@Override
public void actionPerformed(ActionEvent e) {
.
.
.
lbMoveChar.setLocation(lbMoveChar.getX(),lbMoveChar.getY()+10);
}
}
3.客户端如何实现加减分数
由于本项目输入网络通信应用,因此,分数的加减可以通过服务器转发。方法如下:
1.客户端输入正确,将自己加2分,然后讲一个字符串-1发给服务器。
2.服务器将-1发给所有在线客户端。
3.所有客户端(包括自己),获取-1之后,将相应的生命值减去1分。
代码如下:
package game;
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;
//掉下的字母Label
private JLabel lbMoveChar=new JLabel();
//当前生命值状态显示JLabel
private JLabel lbLife=new JLabel();
private Socket s=null;
private Timer timer=new Timer(100,this);
private Random rnd=new Random();
private BufferedReader br=null;
private PrintStream ps=null;
.
.
.
///线程读取网络信息
@Override
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);
}
}
.
.
.
//键盘操作事件对应的行为
@Override
public void keyPressed(KeyEvent e) {
keyChar=e.getKeyChar();
String strChar=String.valueOf(keyChar).toUpperCase();
try {
if(strChar.equals(lbMoveChar.getText())) {
//注意,这里加2分,然后发送-1给所有客户端
//本客户端又会收到,结果为加1分
life+=2;
ps.println("-1");
}
else life--;
checkFail();
}
catch (Exception ex) {
ex.printStackTrace();
javax.swing.JOptionPane.showMessageDialog(this,"游戏异常退出");
System.exit(0);
}
}
.
.
.
}
4.客户端如何判断输了
判断输了很简单,只需判断生命值是否小于等于0即可:
public void checkFail() {
init();
if(life<=0) {
timer.stop();
javax.swing.JOptionPane.showMessageDialog(this,"生命值耗尽,游戏失败");
System.exit(0);
}
}
代码编写
1.服务器端
首先是服务器类,代码如下:
package game;
import java.awt.Color;
import java.io.*;
import java.net.*;
import java.util.ArrayList;
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();
}
@Override
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 { //为某个Socket负责接收信息
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());
}
@Override
public void run() {
try {
while(canRun) {
String str=br.readLine(); //读取该Socket传来的信息
sendMessage(str); //将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 {
new Server();
}
}
2.客户端
首先是游戏面板类,代码如下:
package game;
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;
//掉下的字母Label
private JLabel lbMoveChar=new JLabel();
//当前生命值状态显示JLabel
private JLabel lbLife=new JLabel();
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);
}
@Override
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事件对应的行为:实现掉下一个字母
@Override
public void actionPerformed(ActionEvent e) {
if(lbMoveChar.getY()>=this.getHeight()) {
life--;
checkFail();
}
lbMoveChar.setLocation(lbMoveChar.getX(),lbMoveChar.getY()+10);
}
public void checkFail() {
init();
if(life<=0) {
timer.stop();
javax.swing.JOptionPane.showMessageDialog(this,"生命值耗尽,游戏失败");
System.exit(0);
}
}
//键盘操作事件对应的行为
@Override
public void keyPressed(KeyEvent e) {
keyChar=e.getKeyChar();
String strChar=String.valueOf(keyChar).toUpperCase();
try {
if(strChar.equals(lbMoveChar.getText())) {
//注意,这里加2分,然后发送-1给所有客户端
//本客户端又会收到,结果为加1分
life+=2;
ps.println("-1");
}
else life--;
checkFail();
}
catch (Exception ex) {
ex.printStackTrace();
javax.swing.JOptionPane.showMessageDialog(this,"游戏异常退出");
System.exit(0);
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}
接下来是面板所在的界面类,代码如下:
package game;
import javax.swing.JOptionPane;
import javax.swing.JFrame;
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(false);
this.setVisible(true);
}
//主函数入口
public static void main(String[] args) {
new GameFrame();
}
}
运行该服务器,在运行客户端类,则可以进行网络对战游戏。