1、任务描述:
用JavaSocket编程开发英语六级词汇学习游戏系统
(1) 在网上自行下载英语六级词汇表。存到文件中,格式自定。
(2)系统支持两人连到服务器,测试学生对英语六级词汇的熟悉程度。
(3)首先运行服务器。服务器运行之后,两个客户可以加入。
(4)运行客户端。用户能够输入昵称,确定,则连接到服务器。连接成功,即可出现游戏界面。
(5)客户端游戏界面如下:用户点击开始之后,系统随机选择一个六级英文词汇(5个字母以上),空缺2个字母,用下划线,从客户端顶部落下,底部有其中文释义。
规则如下:
(1)每人初始生命值为20分。
(2)英文词汇(黑色)从界面顶部落下时,用户按下键盘,输入字母。字母(红色)快速射向单词。如果字母正确,则字母填入单词,该空字母下划线消失;错误,则该单词考试失败,出现下一个单词。用户输入正确字母,自己加1分;如果错误,自己减1分,对方加1分。
(3)用户正确将单词填写完毕,或者输入错误字母导致填写错误,该单词下落过程结束。重新出现新的单词从顶部落下,中文释义更新。
(4)词汇掉到用户界面底部,如果用户还没将单词填完,用户减1分,重新出现新词汇掉下来,中文释义更新。
(5)某用户如果生命值率先变为0分,则判输,游戏退出。
(6)游戏退出后,每个用户保存3个文件,分别存储输入正确的单词表、输入错误的单词表、未输入完即掉到底部的单词表。便于用户复习。
2、所用知识
总的来说,这次需要编写三个大类
①Server 作为服务器处理数据
②GamePanel 游戏面板,主要的游戏逻辑由他实现
③GameFrame 游戏界面,主要负责把GamePanel加上去,设置界面不可调整等微小工作
所用知识我们可以对照任务描述分条来看
①下载六次词汇表并做一定的处理:java.io.,为了方便我们后续处理,必须对文件中单词和中文释义有一定的格式要求,我的预期格式是一对单词和汉语解释占单独一行,中间有空格分开,这样方便我们用BufferedReader类提供的readLine函数,一次读取一对,同时要提前把小于5个字母的单词删掉
②连接服务器需要用java.net.
③系统随机选择单词落下,空缺两个字母,从顶端落下:由于这个任务我们要运行多次,所以我选择写一个Init函数,用来完成这个任务和其他任务。
(1)随机选择单词:随机选单词如果在读的阶段就随机很麻烦,所以我选择读入后再打乱顺序,我把读取的一行数据存入一个vector(java.util.Vector)中(注意这里一定不能用java.util. 后面说为什么),然后用经典的洗牌算法:按顺序迭代,如果到了第i个,就产生一个从i—n-1的随机数(是n-1而不是n,因为这里是索引,java String里面也是从0开始计数),代码如下:
// 打乱
for (int i = 0; i < words.size(); i++) {
String tmp = words.get(i);
int randIndex = (int) (Math.random() * (words.size() - i) + i);
words.set(i, words.get(randIndex));
words.set(randIndex, tmp);
}
(2)任意选择位置空缺:直接使用随机数生成两个位置,把这两个位置用int变量 lackPos1, lackPos2保存下来(之后判断是否填入正确单词要用),要注意,二者一定不能相等,而且要保证lackPos1<lackPos2,有一个小于的关系是因为之后要判断用户是否输入了正确单词,填写顺序肯定是从前到后的,为了判断方便,我们之间在这里让lackPos1<lackPos2,至于空缺,可以直接把lackPos1、lackPos2对应位置的字母换成下划线,但是因为String类对于改变操作并不是很方便,所以新建一个临时变量StringBuffer类,这样方便我们修改对应位置的字母
dic = words.get(wordIndex++ % words.size()).split(" ");
sb = new StringBuffer(dic[0]);
// 保证二者不相等
lackPos2 = lackPos1 = (int) (Math.random() * sb.length());
while (lackPos2 == lackPos1) {
lackPos2 = (int) (Math.random() * sb.length());
}
if (lackPos2 < lackPos1) {
int tmp = lackPos2;
lackPos2 = lackPos1;
lackPos1 = tmp;
}
sb.setCharAt(lackPos1, '_');
sb.setCharAt(lackPos2, '_');
(3)落下:首先设置GamePanel类的布局为空,单词我们把他加到一个JLabel上,落下这件事通过setLocation函数实现,由于落下要间隔一定的时间,但是程序并不能卡在这里,所以要用到javax.swing.Timer类,同时这个类要实现ActionListener接口,通过重写actionPerformed函数,告诉Timer间隔一定时间要做什么
//第一个参数是间隔时间,第二个参数是实现了ActionListener接口的对象
Timer timer = new Timer(250, this);
// 每隔100毫秒下落
public void actionPerformed(ActionEvent e) {
jlbChinese.setSize(dic[1].length() * 20, 20);
jlbChinese.setLocation((this.getWidth() - jlbChinese.getWidth()) / 2,
this.getHeight() - jlbChinese.getHeight());
jlbWord.setLocation(jlbWord.getX(), jlbWord.getY() + 10);
checkFail();
}
④字母射向单词:因为字母射向单词的时候别的程序也要运行,单词也不可能呆滞在空中,所以这里还要用到多线程的知识,但是原先的GamePanel类已经实现了Runnable接口,处理的是接受服务器信息的任务,所以我们再新建一个类,jlbFollow,用它实现Runnable接口,需要做的就是判断字母有没有飞上去,如果飞上去就结束,如果是第二个字母飞上去了,就调用Init函数
private class jlbFollow extends JLabel implements Runnable {
JLabel jlbWord;
int pos;
public jlbFollow(JLabel jlbWord, char ch, JPanel jpl, int pos) {
this.setFont(new Font("黑体", Font.BOLD, 20));
this.setForeground(Color.red);
this.setText(String.valueOf(ch));
this.setBounds((int) ((jpl.getWidth() - 20) * Math.random()), jpl.getHeight(), 20, 20);
this.jlbWord = jlbWord;
this.pos = pos;
}
public void run() {
while (Math.pow(jlbWord.getX() - this.getX(), 2) + Math.pow(jlbWord.getY() - this.getY(), 2) > 100) {
int x = (jlbWord.getX() - this.getX()) / 2, y = (jlbWord.getY() - this.getY()) / 2;
this.setLocation(x + this.getX(), y + this.getY());
try {
Thread.sleep(50);
} catch (Exception e) {
}
}
this.setVisible(false);
sb.setCharAt(pos, this.getText().charAt(0));
jlbWord.setText(sb.toString());
try {
Thread.sleep(100);
} catch (Exception e) {
}
if (lackPos1 == lackPos2)
Init();
}
}
⑤如果错误,自己生命-1,对方生命+1:如果单词错误,那就给自己生命-2,然后给服务器发送+1.服务器再给加入的每一个玩家发送+1即可
**注意:**如果想要成功运行,必须要修改GamePanel类里面的一个String类的对象ip,还要保证路径下有图片和文本文档。修改是因为要匹配你所在的网络环境(想要查看首先打开CMD窗口,然后输入ipconfig 敲回车),在“ IPv4 地址”那一行查看(或者直接改成127.0.0.1 表示本机)。图片是我用来设置背景和图标的,文本文档是读取单词所用。虽然我只用了四张图,但是存了很多图片,这是因为我原本想实现一个每次登陆随机背景图的功能,但是现在处于答辩前夕,不想更改代码了。(●’◡’●)
运行情况:
路径内容如下图:
3、代码
①Server类
package Game;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.*;
import javax.swing.*;
import java.util.*;
public class Server extends JFrame implements Runnable, ActionListener {
private ServerSocket ss = null;
private Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private Vector<ChatThread> client = new Vector<ChatThread>();
private MyJPanel jpl = new MyJPanel();
private JComboBox jcb = new JComboBox();
private JLabel jlb = new JLabel();
public Server() throws Exception {
Image img = Toolkit.getDefaultToolkit().getImage("serverIcon.jpg");
ss = new ServerSocket(9999);
this.setSize(350, 400);
jpl.setLayout(null);
jpl.add(jcb);
jpl.add(jlb);
jcb.setLocation(130, 100);
jcb.setSize(70, 20);
jlb.setSize(200, 20);
jlb.setFont(new Font("楷体", Font.BOLD, 20));
jlb.setForeground(Color.red);
jlb.setText("选择用户,请他离开");
jlb.setLocation(80, 80);
this.add(jpl);
this.setIconImage(img);
this.setLocation((int) ((screenSize.getWidth() - 350) / 2), (int) ((screenSize.getHeight() - 400) / 2));
this.setTitle("服务器端启动,未见客户加入");
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
jcb.addItem(" ");
jcb.setVisible(false);
jlb.setVisible(false);
jcb.addActionListener(this);
new Thread(this).start();
}
public void run() {
while (true) {
try {
Socket s = ss.accept();
this.setTitle("客户加入");
ChatThread ct = new ChatThread(s);
client.add(ct);
this.setTitle("用户" + ct.nickName + "连接成功");
jcb.addItem(ct.nickName);
jcb.setVisible(true);
jlb.setVisible(true);
ct.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class ChatThread extends Thread {
BufferedReader br = null;
PrintStream ps = null;
boolean canRun = true;
String nickName;
public ChatThread(Socket s) throws IOException {
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
ps = new PrintStream(s.getOutputStream());
nickName = br.readLine();
}
public void run() {
while (canRun) {
try {
String str = br.readLine();
sentMessage(str);
} catch (Exception e) {
canRun = false;
client.remove(this);
jcb.removeItem(nickName);
}
}
}
private void sentMessage(String str) {
for (ChatThread ct : client)
ct.ps.println(str);
}
}
class MyJPanel extends JPanel {
public void paintComponent(Graphics g) {
Image img = new ImageIcon("gra4.jpg").getImage();
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), jpl);
}
}
public static void main(String[] args) throws Exception {
new Server();
}
@Override
public void actionPerformed(ActionEvent e) {
Object name = jcb.getSelectedItem();
if (null == name || name.equals(" "))
return;
int choice = JOptionPane.showConfirmDialog(this, "您确定要踢出" + name + "吗?");
if (JOptionPane.YES_OPTION == choice) {
for (ChatThread c : client) {
if (name.equals(c.nickName)) {
c.ps.println("-9999");
this.setTitle("用户" + name + "踢出成功");
jcb.removeItem(name);
JOptionPane.showMessageDialog(null, "踢除成功!");
}
}
}
}
}
②GamePanel类
package Game;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import sun.security.util.math.ImmutableIntegerModuloP;
import java.io.*;
import java.net.*;
import java.util.Vector;
public class GamePanel extends JPanel implements ActionListener, KeyListener, Runnable {
final private String ip = "127.0.0.1";
String nickName = null;
Socket s = null;
JLabel jlbWord = new JLabel();
JLabel jlbChinese = new JLabel();
JLabel jlbLife = new JLabel();
BufferedReader br = new BufferedReader(new FileReader("word.txt"));
Vector<String> words = new Vector<String>();
String[] dic; // 单词及其中文释义
int wordIndex = 0; // 下一个该读取的单词的索引
int lackPos1, lackPos2;// 空出字母在单词内的索引
int life = 20;
PrintStream ps = null;
BufferedReader brServer = null;
StringBuffer sb = null;
StringBuffer filePath = new StringBuffer("C:\\Users\\86132\\Desktop\\java\\clientInfo\\");
File file = null;
PrintStream psCorrect = null;
PrintStream psFalse = null;
PrintStream psForget = null;
Timer timer = new Timer(250, this);
public GamePanel() throws Exception {
String str = null;
// 读取单词
while (null != (str = br.readLine())) {
words.add(str);
}
br.close();
// 打乱
for (int i = 0; i < words.size(); i++) {
String tmp = words.get(i);
int randIndex = (int) (Math.random() * (words.size() - i) + i);
words.set(i, words.get(randIndex));
words.set(randIndex, tmp);
}
nickName = JOptionPane.showInputDialog("请输入昵称");
if (null == nickName)
System.exit(0);
this.setLayout(null);
this.setSize(500, 550);
s = new Socket(ip, 9999);
this.add(jlbLife);
this.add(jlbWord);
this.add(jlbChinese);
this.setFocusable(true);
this.addKeyListener(this);
jlbLife.setFont(new Font("楷体", Font.BOLD, 20));
jlbWord.setFont(new Font("黑体", Font.BOLD, 20));
jlbChinese.setFont(new Font("楷体", Font.BOLD, 20));
ps = new PrintStream(s.getOutputStream());
brServer = new BufferedReader(new InputStreamReader(s.getInputStream()));
JOptionPane.showMessageDialog(null, "连接成功,游戏即将开始");
ps.println(nickName);
filePath.append(nickName);
file = new File(filePath.toString());
if (!file.exists())
file.mkdir();
filePath.append("\\");
psCorrect = new PrintStream(new FileOutputStream(new File(filePath.toString() + "CorrectWord.txt"), true));
psForget = new PrintStream(new FileOutputStream(new File(filePath.toString() + "ForgetWord.txt"), true));
psFalse = new PrintStream(new FileOutputStream(new File(filePath.toString() + "FalseWord.txt"), true));
Init();
timer.start();
new Thread(this).start();
}
public void paintComponent(Graphics g) {
Image img = new ImageIcon("gra1.jpg").getImage();
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
}
private void Init() {
dic = words.get(wordIndex++ % words.size()).split(" ");
sb = new StringBuffer(dic[0]);
// 保证二者不相等
lackPos2 = lackPos1 = (int) (Math.random() * sb.length());
while (lackPos2 == lackPos1) {
lackPos2 = (int) (Math.random() * sb.length());
}
if (lackPos2 < lackPos1) {
int tmp = lackPos2;
lackPos2 = lackPos1;
lackPos1 = tmp;
}
sb.setCharAt(lackPos1, '_');
sb.setCharAt(lackPos2, '_');
jlbLife.setText("剩余生命值:" + life);
jlbLife.setBounds(0, 0, 200, 20);
jlbWord.setText(sb.toString());
jlbWord.setBounds((int) ((this.getWidth() - 20 * dic[0].length()) * Math.random()), 20, 20 * dic[0].length(),
40);
jlbChinese.setText(dic[1]);
jlbChinese.setVisible(true);
jlbChinese.setSize(dic[1].length() * 20, 20);
jlbChinese.setLocation((this.getWidth() - jlbChinese.getWidth()) / 2,
this.getHeight() - jlbChinese.getHeight());
}
private void checkFail() {
if (jlbWord.getY() >= jlbChinese.getY() - 20) {
life--;
psForget.println(words.get(wordIndex - 1));
Init();
}
if (0 == life) {
JOptionPane.showMessageDialog(null, "游戏失败");
System.exit(0);
} else if (life <= -100) {
psCorrect.close();
psFalse.close();
psForget.close();
JOptionPane.showMessageDialog(null, "您被房主请出房间");
System.exit(0);
}
}
// 每隔250毫秒下落
public void actionPerformed(ActionEvent e) {
jlbChinese.setSize(dic[1].length() * 20, 20);
jlbChinese.setLocation((this.getWidth() - jlbChinese.getWidth()) / 2,
this.getHeight() - jlbChinese.getHeight());
jlbWord.setLocation(jlbWord.getX(), jlbWord.getY() + 10);
checkFail();
}
@Override
public void keyTyped(KeyEvent e) {
if (-1 != lackPos1) {
if (e.getKeyChar() == dic[0].charAt(lackPos1)) {
jlbFollow jlbfollow = new jlbFollow(jlbWord, e.getKeyChar(), this, lackPos1);
this.add(jlbfollow);
new Thread(jlbfollow).start();
lackPos1 = -1;
} else {
life -= 2;
ps.println("+1");
psFalse.println(words.get(wordIndex - 1));
Init();
}
} else {
//Jiayige防误触
if (e.getKeyChar() == dic[0].charAt(lackPos2)) {
life++;
psCorrect.println(words.get(wordIndex - 1));
jlbFollow jlbfollow = new jlbFollow(jlbWord, e.getKeyChar(), this, lackPos2);
this.add(jlbfollow);
lackPos2 = -1;
new Thread(jlbfollow).start();
} else {
life -= 2;
ps.println("+1");
psFalse.println(words.get(wordIndex - 1));
Init();
}
}
checkFail();
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void run() {
boolean canRun = true;
while (canRun) {
try {
String str = brServer.readLine();
life += Integer.parseInt(str);
jlbLife.setText("剩余生命值:" + life);
} catch (Exception e) {
canRun = false;
psCorrect.close();
psFalse.close();
psForget.close();
}
}
}
private class jlbFollow extends JLabel implements Runnable {
JLabel jlbWord;
int pos;
public jlbFollow(JLabel jlbWord, char ch, JPanel jpl, int pos) {
this.setFont(new Font("黑体", Font.BOLD, 20));
this.setForeground(Color.red);
this.setText(String.valueOf(ch));
this.setBounds((int) ((jpl.getWidth() - 20) * Math.random()), jpl.getHeight(), 20, 20);
this.jlbWord = jlbWord;
this.pos = pos;
}
public void run() {
while (Math.pow(jlbWord.getX() - this.getX(), 2) + Math.pow(jlbWord.getY() - this.getY(), 2) > 100) {
int x = (jlbWord.getX() - this.getX()) / 2, y = (jlbWord.getY() - this.getY()) / 2;
this.setLocation(x + this.getX(), y + this.getY());
try {
Thread.sleep(50);
} catch (Exception e) {
}
}
this.setVisible(false);
sb.setCharAt(pos, this.getText().charAt(0));
jlbWord.setText(sb.toString());
try {
Thread.sleep(100);
} catch (Exception e) {
}
if (lackPos1 == lackPos2)
Init();
}
}
}
③GameFrame类
package Game;
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.net.*;
public class GameFrame extends JFrame{
public GameFrame() throws Exception{
Image img = Toolkit.getDefaultToolkit().getImage("clientIcon.jpeg");
GamePanel gpl = new GamePanel();
this.setSize(gpl.getSize());
this.setTitle(gpl.nickName);
this.add(gpl);
this.setIconImage(img);
this.setTitle(gpl.nickName);
this.setVisible(true);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) throws Exception {
new GameFrame();
}
}