首先是游戏的客户端,其中包含的类和函数如下:
1、客户端的GamePanel主类
继承了Runnable,ActionListener,KeyListener接口。 其中的函数有:
初始化整型life = 20,
搭建好用于与服务器收发消息的Socket、BufferedReader、PrintStream,
新建一个Timer,传入的参数(300,this)。
(1)、writeFile函数:
调用时传入两个字符串参数,第一个字符串表示需要写入的文件名,第二个字符串表示将写入的字符串,在这里用于写入判断正确或判断错误的词汇,分两个文件存储写入就好。
(2)、init函数:
不需要传入参数,用于初始化掉落的单词JLabel:lbMoveChar,然后在其中调用readLineFile函数,并且传入两个参数,一个是“filename”,另一个是在词汇文档中用于随机按行读取词汇的整型数据il,接下来,再将四个选项JLabel;lbA,lbB,lbC,lbD,初始化一遍,设置好初始位置。并且lbMoveChar设置好标题,即将readLineFile函数中改写的存有掉落六级词汇的全局变量word,设为它的标题。最后,将lbMoveChar设置在掉落的初始位置。
(3)、checkFail函数
无参数,由于程序框架结构,它用于在每次客户端实现全局变量生命值的整型数life改变后,重新设置一遍lbLife(即显示生命值的一个控件),并且在此函数中实现,若用户如果生命值变为零,则判输,游戏退出,并提示“生命值耗尽,游戏失败!”。
(4)、actionPerformed函数
函数的作用就是实现若词汇掉到用户界面底部,如果两个用户都还没做出选择,两个用户减1分,即若if(lbMoveChar.getY() >= this.getHeight())判断为true,则既将这个掉到用户界面底部的词汇写入wrong.txt,然后调用checkFail(),并且向服务端发送“ASKRN#”;若判断为false,则lbMoveChar控价继续以一定的高度下掉。
(5)、readLineFile函数:
这个函数用synchronized同步锁锁定,因为在我进行编程的过程中,出现过:下落的词汇没有正确选项的情况,经过测试后发现,原来是因为多线程使用重复更新lbA,lbB,lbC,lbD,把四个选项A、B、C、D的值多次重复更新,然后就将本在后面调用的读取词汇中文的更新为此次的选项。
解决方法:直接把synchronized关键字直接加在函数的定义上,使得整个函数都同步,原理为:当某一线程运行同步代码段时,在“同步锁对象”上置一标记,运行完这段代码,标记消除。其他线程要想抢占CPU运行这段代码,必须在“同步锁对象”上先检查该标记,只有标记处于消除状态,才能抢占CPU。函数传入两个参数,一个为读取文件的路径,二个为整型的由服务器产生的随机数il,表示此次读取第几行的词汇。
接下来一个while循环的判断条件为(br.readLine() != null && il >= 0),即表示若文档中仍有下一行,且il大于等于0就继续执行,每一个il–,直到读取到指定行,就把此行存入一字符串中,String的split方法支持正则表达式;正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次。把分离了的词汇与翻译存入一个String数组中,把strs[0]赋给全局字符变量word中。
然后产生一个随机范围为[0,4)的整数,若为0,则将正确选项存入A中,若为1,则将正确选项存入B中,若为2,则将正确选项存入C中,若为3,则存入D中。
(6)、keyPressed函数:
因为主类GamePanel主类继承了KeyListener,所以重写keyPressed函数。先把按的键的值赋给字符keyChar,再使用valueOf将其转换成字符串keyStr。判定keyStr是否和Opt(正确选项)相等,就调用writeFile,把strSave存入路径名为”D:\right.txt”的文档中,且life值自加2,并向服务器发送“LIFE#-1”。否则将其写入路径名为”D:\wrong.txt”的文档中,且life值自减2,并向服务器发送“LIFE#1”。
(7)、GamePanel的构造函数:
布置游戏界面,加入控件:lbLife,lbA,lbB,lbC,lbD,lbMoveChar,并调用init函数初始化,加入addKeyListener。
(8)、run函数:
设置好canRun标识符,控制while循环,初设为true,直到运行时抛出异常,则设置canRun为false,结束循环,并且弹出提示为“游戏异常退出!”的窗口,点确定后退出。
while循环中,一直读取来自服务器端的消息br,并且存入字符串str中,将str用split将“#”分离开,存入strs字符串数组中。若(strs[0].equals(“START”))判断为真,即传来的消息开头为“START”,则把消息中服务器随机产生的数赋值给读取文档用的il,否则若(strs[0].equals(“LIFE”))判断为真,则此消息代表为生命值的增减,将strs[1]使用Integer.parseInt转换为整型,且存入score中。
接下来将生命值的增减加入life中,再调用checkFail()函数,将这个界面面板更新。
且若(strs[2].equals(“START”))判断为真,即代表此时strs[]的格式为“LIFE#-1#START#srn”,则将strs[3]存入il中,否则若(strs[0].equals(“UWIN”))判断为真,则timer.stop(),弹出标题为“游戏结束,你赢了!”,且退出。
(9)、main函数:
调用 GameFrame函数。
2、客户端的GameFrame类:
设置好进入时的一个窗口:标题是输入名称,然后把输入的字符串设置为客户端的标题。
3、服务器端的Server主类
(1)Server的构造函数:
设置“服务器端”为窗口标题,设置好界面,具体见源码。
(2)run函数:
首先调用sendMessage向所有的客户端发送“START#”+wn(随机生成的属于(0,640)的整型数)。然后一个while死循环,条件canRun初始值为true,循环中一直执行读取来自客户端发来的消息,存入字符串str中,然后用split把“#”分离开存入strs数组中。
若(strs[0].equals(“LIFE”))判断为真,即表示此为生命值消息,将生命值转发给所有的客户端,并且产生一个随机数,调用sendMessage把“LIFE#”+strs[1]+“#”+srn发送给所有的客户端。
否则若(strs[0].equals(“WIN”))为真,则说明有一方生命值已经归为0,服务器端将这个消息发送给所有的客户端,因为生命值先减为0的客户端,即发出此消息的客户端先已退出,所以此处不影响它的判断。
package client;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.ActionListener;
import java.awt.event.KeyListener;
import java.io.*;
import java.net.*;
import java.util.Random;
import javax.swing.*;
public class GamePanel extends JPanel implements Runnable, ActionListener,KeyListener {
private int life = 20;
private char keyChar;
private JLabel lbMoveChar = new JLabel();
private JLabel lbLife = new JLabel();
//选项ABCD
private JLabel lbA = new JLabel();
private JLabel lbB = new JLabel();
private JLabel lbC = new JLabel();
private JLabel lbD = new JLabel();
private Socket s = null;
private Timer timer = new Timer(300,this);
private Random rnd = new Random();
private BufferedReader br = null;
private PrintStream ps = null;
private String word = null;
private String Opt = null;
private int il;
String strSave = null;
String keyStr = null;
//int in = 0;
//int ij = 0;
//int ic = 0;
//canRun用来在异常或退出游戏时退出死循环
private boolean canRun = true;
public GamePanel(){
this.setLayout(null);
this.setBackground(Color.DARK_GRAY);
this.setSize(1200,500);
this.add(lbLife);
lbLife.setFont(new Font("黑体",Font.BOLD,20));
lbLife.setBackground(Color.yellow);
lbLife.setForeground(Color.PINK);
lbLife.setBounds(0,0,500,20);
this.add(lbA);
this.add(lbB);
this.add(lbC);
this.add(lbD);
lbA.setForeground(Color.PINK);
lbB.setForeground(Color.PINK);
lbC.setForeground(Color.PINK);
lbD.setForeground(Color.PINK);
lbA.setBackground(Color.yellow);
lbB.setBackground(Color.yellow);
lbC.setBackground(Color.yellow);
lbD.setBackground(Color.yellow);
lbA.setFont(new Font("黑体",Font.BOLD,15));
lbB.setFont(new Font("黑体",Font.BOLD,15));
lbC.setFont(new Font("黑体",Font.BOLD,15));
lbD.setFont(new Font("黑体",Font.BOLD,15));
lbA.setBounds(0,400,300,50);
lbB.setBounds(300,400,300,50);
lbC.setBounds(600,400,300,50);
lbD.setBounds(900,400,300,50);
//lbA.setText("选项A");
//lbB.setText("选项B");
//lbC.setText("选项C");
//lbD.setText("选项D");
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 writeFile(String filename, String str){
try {
FileOutputStream fos = new FileOutputStream(filename,true);
byte[] b = str.getBytes();
fos.write(b);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public synchronized void readLineFile(String filename, int il){
try{
FileInputStream fi = new FileInputStream(filename);
InputStreamReader isr = new InputStreamReader(fi, "UTF-8");
BufferedReader br = new BufferedReader(isr);
//br此处用来按行读取文档
//in++;
//System.out.println("readLineFile开始执行:"+in);
while(br.readLine() != null && il >= 0){
il--;
//String str;
if(il < 0){
//用来按行读取,并分割出单词和解释
//String的split方法支持正则表达式;
//正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次。
String str1 = br.readLine();
//保存的字符串
strSave = str1 + "\r\n";
String[] strs1 = str1.split("\\s+");
word = strs1[0];
System.out.println("1单词:"+word);
//将翻译分配给选项
System.out.println("1单词源:"+strs1[1]);
Random rnd1 = new Random();
//rnd.nextInt(300-10+1)+10-->[10,300)
//这里的4不能改!!!!
int swch = rnd1.nextInt(4);
switch (swch) {
case 0: {
lbA.setText("A:"+strs1[1]);
String str2 = br.readLine();
String[] strs2 = str2.split("\\s+");
lbB.setText("B:"+strs2[1]);
String str3 = br.readLine();
String[] strs3 = str3.split("\\s+");
lbC.setText("C:"+strs3[1]);
String str4 = br.readLine();
String[] strs4 = str4.split("\\s+");
lbD.setText("D:"+strs4[1]);
Opt = "A";
System.out.println(lbA.getText());
System.out.println(lbB.getText());
System.out.println(lbC.getText());
System.out.println(lbD.getText());
break;
}
case 1:{
lbB.setText("B:"+strs1[1]);
String str2 = br.readLine();
String[] strs2 = str2.split("\\s+");
lbA.setText("A:"+strs2[1]);
String str3 = br.readLine();
String[] strs3 = str3.split("\\s+");
lbC.setText("C:"+strs3[1]);
String str4 = br.readLine();
String[] strs4 = str4.split("\\s+");
lbD.setText("D:"+strs4[1]);
Opt = "B";
System.out.println(lbA.getText());
System.out.println(lbB.getText());
System.out.println(lbC.getText());
System.out.println(lbD.getText());
break;
}
case 2:{
lbC.setText("C:"+strs1[1]);
String str2 = br.readLine();
String[] strs2 = str2.split("\\s+");
lbA.setText("A:"+strs2[1]);
String str3 = br.readLine();
String[] strs3 = str3.split("\\s+");
lbB.setText("B:"+strs3[1]);
String str4 = br.readLine();
String[] strs4 = str4.split("\\s+");
lbD.setText("D:"+strs4[1]);
Opt = "C";
System.out.println(lbA.getText());
System.out.println(lbB.getText());
System.out.println(lbC.getText());
System.out.println(lbD.getText());
break;
}
case 3:{
lbD.setText("D:"+strs1[1]);
String str2 = br.readLine();
String[] strs2 = str2.split("\\s+");
lbA.setText("A:"+strs2[1]);
String str3 = br.readLine();
String[] strs3 = str3.split("\\s+");
lbB.setText("B:"+strs3[1]);
String str4 = br.readLine();
String[] strs4 = str4.split("\\s+");
lbC.setText("C:"+strs4[1]);
Opt = "D";
System.out.println(lbA.getText());
System.out.println(lbB.getText());
System.out.println(lbC.getText());
System.out.println(lbD.getText());
break;
}
default:{
System.out.println("Switch语句有误");
break;
}
}
System.out.println("正确选项是"+Opt);
break;
}
//System.out.println("readLineFile结束:"+in);
}
}catch (Exception e){
e.printStackTrace();
}
}
public void init() {
//System.out.println("init开始执行:"+ij);
//ij++;
lbLife.setText("当前生命值:" + life);
//System.out.println("设置生命值");
//String str = String.valueOf((char)('A'+rnd.nextInt(26)));
//lbMoveChar.setText(str);
//不需要客户端产生随机数了,服务器产生随机数
//int il = rnd.nextInt(10);
//ps.println("RND#"+il);
readLineFile("D:\\word.txt", il);
lbA.setBounds(0,400,300,50);
lbB.setBounds(300,400,300,50);
lbC.setBounds(600,400,300,50);
lbD.setBounds(900,400,300,50);
lbMoveChar.setText(word);
lbMoveChar.setBounds(500,0,200,50);
//System.out.println("init执行完成:"+ij);
}
//读取服务器发来的消息,操作生命值
public void run(){
try{
while(canRun){
String str = br.readLine();
String[] strs = str.split("#");
//判断从服务器接收到的消息是初始化number(用作随机读词)
if(strs[0].equals("START")){
il = Integer.parseInt(strs[1]);
//判断是减生命值的消息
}else if(strs[0].equals("LIFE")) {
//若消息是既包含LIFE又包含RND
int score = Integer.parseInt(strs[1]);
//实现生命值的减少
life+=score;
checkFail();
//System.out.println("life+=score;语句调用checkFail");
//如果strs[]的格式为“LIFE#-1#START#srn"
if(strs[2].equals("START")){
il = Integer.parseInt(strs[3]);
}
}else if(strs[0].equals("UWIN")){
//你赢了并退出游戏
timer.stop();
javax.swing.JOptionPane.showMessageDialog(this,"游戏结束,你赢了!");
System.exit(0);
}
init();
//checkFail();
//System.out.println("执行完服务器来的任务后调用init");
}
}catch (Exception ex){
canRun = false;
javax.swing.JOptionPane.showMessageDialog(this,"游戏异常退出!");
System.exit(0);
}
}
//某用户如果生命值率先变为0分,则判输,游戏退出。
public void checkFail(){
//init();
//System.out.println("开始执行checkFail:"+ic);
lbLife.setText("当前生命值:" + life);
//ic++;
//System.out.println("结束ccheckFail:"+ic);
if(life <= 0){
ps.println("WIN#");
timer.stop();
javax.swing.JOptionPane.showMessageDialog(this,"生命值耗尽,游戏失败!");
System.exit(0);
}
}
//若词汇掉到用户界面底部,如果两个用户都还没做出选择,两个用户减1分
public void actionPerformed(ActionEvent e){
if(lbMoveChar.getY() >= this.getHeight()){
writeFile("D:\\wrong.txt",strSave);
life--;
checkFail();
//随机数由服务器产生
ps.println("ASKRN#");
//System.out.println("底部调用checkFail");
}
lbMoveChar.setLocation(lbMoveChar.getX(),lbMoveChar.getY()+10);
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
keyChar = e.getKeyChar();
keyStr = String.valueOf(keyChar);
try{
//判断是否选对选项
if(keyStr.equalsIgnoreCase(Opt)) {
writeFile("D:\\right.txt",strSave);
life+=2;
//System.out.println("选正确了");
ps.println("LIFE#-1");
}
else{
writeFile("D:\\wrong.txt",strSave);
life-=2;
//用于向服务器标识需要随机数,对方生命值加一
ps.println("LIFE#1");
}
//实现选项射向lbMoveChar
//ProLocation();
//(Timer.scheduleAtFixedRate(TimerTask task,long delay,long period)
//安排指定的任务在指定的延迟后开始进行重复的固定速率执行.
/*timer.stop();
Timer timer2 = new Timer();
timer2.scheduleAtFixedRate()*/
init();
checkFail();
//System.out.println("向服务器发消息后checkFail");
}catch (Exception ex){
canRun = false;
javax.swing.JOptionPane.showMessageDialog(this,"游戏异常退出!");
System.exit(0);
}
}
@Override
public void keyReleased(KeyEvent e) {
}
public static void main(String[] args){
new GameFrame();
}
}
package client;
import javax.swing.*;
import java.awt.*;
public class GameFrame extends JFrame{
private GamePanel gp;
public GameFrame(){
this.setLayout(new BorderLayout());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//setAlwaysOnTop(true);//设置窗体的属性,总是在最上面显示
this.setAlwaysOnTop(true);
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);
}
}
import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.*;
import java.util.ArrayList;
import java.util.Random;
public class Server extends JFrame implements Runnable{
private Socket s = null;
private ServerSocket ss = null;
//private JTextField jtf = new JTextField();
private JTextArea jta = new JTextArea();
private Random rnd = new Random();
private ArrayListclients = new ArrayList();
public Server() throws Exception{
this.setTitle("服务器端");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(jta,BorderLayout.CENTER);
jta.setBackground(Color.yellow);
this.setSize(400,200);
this.setVisible(true);
ss = new ServerSocket(9999);
new Thread(this).start();
}
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{
//开始时,服务器首先生成一个大小有限制的随机数,并发给所有的客户端
int wn = rnd.nextInt(640);
System.out.println("Start:"+wn);
String swn = "START#" + Integer.toString(wn);
sendMessage(swn);
while (canRun){
//在这里
String str = br.readLine();
String[] strs = str.split("#");
if(strs[0].equals("LIFE")){
//将生命值转发给所有的客户端
//sendMessage(strs[1]);
int rn = rnd.nextInt(640);
System.out.println("RdNumber:"+rn);
System.out.println("减或加生命值");
String srn = "START#" + Integer.toString(rn);
sendMessage("LIFE#"+strs[1]+"#"+srn);
}else if(strs[0].equals("WIN")){
//有一方生命值已经归为0,另一方胜利
String msgWIN = "UWIN#";
sendMessage(msgWIN);
}else if(strs[0].equals("ASKRN")){
int rn1 = rnd.nextInt(640);
String swn1 = "START#" + Integer.toString(rn1);
System.out.println("仅用于同步");
sendMessage(swn1);
}
//将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{
new Server();
}
}
参考资料
《Java语言程序设计》(清华大学出版社)主编:郭克华