(1) 在网上自行下载英语六级词汇表。存到文件中,格式自定。
(2)该游戏支持两人通过网络对战,测试双方对英语六级词汇的熟悉程度。
(3)首先运行服务器。服务器运行之后,两个客户可以加入对战游戏中。
(4)运行客户端。用户能够输入昵称,确定,则连接到服务器。连接成功,即可出现游戏界面。
(5)客户端游戏界面如下:系统随机选择一个六级英文词汇,同时从两个客户端顶部落下,底部有四个中文释义,分别为选项A、B、C、D。其中有一个正确答案。
规则如下:
(1)每人初始生命值为20分。
(2)英文词汇从界面顶部落下时,用户按下键盘,选择A、B、C或者D。选项快速射向单词。如果选项正确,则自己加1分,对方减1分;如果错误,自己减1分,对方加1分。
(3)一旦有用户选择了选项,该单词下落过程结束。重新出现新的单词从顶部落下,选项更新。
(4)词汇掉到用户界面底部,如果两个用户都还没做出选择,两个用户减1分,重新出现新词汇,选项更新。
(5)某用户如果生命值率先变为0分,则判输,游戏退出。
(6)游戏退出后,每个用户保存3个文件,分别存储判断正确的单词表、判断错误的单词表、未判断的单词表。便于用户复习。
硬件: Windows 10
软件: IntelliJ IDEA
介绍代码文件结构,每个文件中有哪些类和函数,各起到什么作用
首先是游戏的客户端,其中包含的类和函数如下:
继承了Runnable,ActionListener,KeyListener接口。 其中的函数有:
初始化整型life = 20,
搭建好用于与服务器收发消息的Socket、BufferedReader、PrintStream,
新建一个Timer,传入的参数(300,this)。
调用时传入两个字符串参数,第一个字符串表示需要写入的文件名,第二个字符串表示将写入的字符串,在这里用于写入判断正确或判断错误的词汇,分两个文件存储写入就好。
不需要传入参数,用于初始化掉落的单词JLabel:lbMoveChar,然后在其中调用readLineFile函数,并且传入两个参数,一个是“filename”,另一个是在词汇文档中用于随机按行读取词汇的整型数据il,接下来,再将四个选项JLabel;lbA,lbB,lbC,lbD,初始化一遍,设置好初始位置。并且lbMoveChar设置好标题,即将readLineFile函数中改写的存有掉落六级词汇的全局变量word,设为它的标题。最后,将lbMoveChar设置在掉落的初始位置。
无参数,由于程序框架结构,它用于在每次客户端实现全局变量生命值的整型数life改变后,重新设置一遍lbLife(即显示生命值的一个控件),并且在此函数中实现,若用户如果生命值变为零,则判输,游戏退出,并提示“生命值耗尽,游戏失败!”。
函数的作用就是实现若词汇掉到用户界面底部,如果两个用户都还没做出选择,两个用户减1分,即若if(lbMoveChar.getY() >= this.getHeight())判断为true,则既将这个掉到用户界面底部的词汇写入wrong.txt,然后调用checkFail(),并且向服务端发送“ASKRN#”;若判断为false,则lbMoveChar控价继续以一定的高度下掉。
这个函数用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中。
因为主类GamePanel主类继承了KeyListener,所以重写keyPressed函数。先把按的键的值赋给字符keyChar,再使用valueOf将其转换成字符串keyStr。判定keyStr是否和Opt(正确选项)相等,就调用writeFile,把strSave存入路径名为"D:\right.txt"的文档中,且life值自加2,并向服务器发送“LIFE#-1”。否则将其写入路径名为"D:\wrong.txt"的文档中,且life值自减2,并向服务器发送“LIFE#1”。
布置游戏界面,加入控件:lbLife,lbA,lbB,lbC,lbD,lbMoveChar,并调用init函数初始化,加入addKeyListener。
设置好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(),弹出标题为“游戏结束,你赢了!”,且退出。
调用 GameFrame函数。
设置好进入时的一个窗口:标题是输入名称,然后把输入的字符串设置为客户端的标题。
ps:服务器代码在MyGame文件夹下。
设置“服务器端”为窗口标题,设置好界面,具体见源码。
首先调用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的客户端,即发出此消息的客户端先已退出,所以此处不影响它的判断。